tor-browser

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

rawtest.rs (52951B)


      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::{point2, size2, rect, Box2D};
      6 use std::sync::Arc;
      7 use std::sync::atomic::{AtomicIsize, Ordering};
      8 use std::sync::mpsc::Receiver;
      9 use webrender::api::*;
     10 use webrender::render_api::*;
     11 use webrender::api::units::*;
     12 use crate::{WindowWrapper, NotifierEvent};
     13 use crate::blob;
     14 use crate::reftest::{ReftestImage, ReftestImageComparison};
     15 use crate::wrench::Wrench;
     16 
     17 pub struct RawtestHarness<'a> {
     18    pub wrench: &'a mut Wrench,
     19    pub rx: &'a Receiver<NotifierEvent>,
     20    pub window: &'a mut WindowWrapper,
     21 }
     22 
     23 
     24 impl<'a> RawtestHarness<'a> {
     25    pub fn new(wrench: &'a mut Wrench,
     26               window: &'a mut WindowWrapper,
     27               rx: &'a Receiver<NotifierEvent>) -> Self {
     28        RawtestHarness {
     29            wrench,
     30            rx,
     31            window,
     32        }
     33    }
     34 
     35    pub fn run(mut self) {
     36        self.test_snapping();
     37        self.test_hit_testing();
     38        self.test_resize_image();
     39        self.test_retained_blob_images_test();
     40        self.test_blob_update_test();
     41        self.test_blob_update_epoch_test();
     42        self.test_tile_decomposition();
     43        self.test_very_large_blob();
     44        self.test_blob_visible_area();
     45        self.test_blob_set_visible_area();
     46        self.test_offscreen_blob();
     47        self.test_save_restore();
     48        self.test_blur_cache();
     49        self.test_capture();
     50        self.test_zero_height_window();
     51        self.test_clear_cache();
     52    }
     53 
     54    #[allow(dead_code)]
     55    pub fn render_display_list_and_get_pixels(
     56        &mut self,
     57        builder: DisplayListBuilder,
     58        test_size: FramebufferIntSize,
     59    ) -> Vec<u8> {
     60        let window_size = self.window.get_inner_size();
     61        let window_rect = FramebufferIntRect::from_origin_and_size(
     62            point2(0, window_size.height - test_size.height),
     63            test_size,
     64        );
     65        let txn = Transaction::new();
     66        self.submit_dl(&mut Epoch(0), builder, txn);
     67        self.render_and_get_pixels(window_rect)
     68    }
     69 
     70    pub fn render_and_get_pixels(&mut self, window_rect: FramebufferIntRect) -> Vec<u8> {
     71        self.rx.recv().unwrap();
     72        self.wrench.render();
     73        self.wrench.renderer.read_pixels_rgba8(window_rect)
     74    }
     75 
     76    fn compare_pixels(&self, data1: Vec<u8>, data2: Vec<u8>, size: FramebufferIntSize) {
     77        let size = DeviceIntSize::new(size.width, size.height);
     78        let image1 = ReftestImage {
     79            data: data1,
     80            size,
     81        };
     82        let image2 = ReftestImage {
     83            data: data2,
     84            size,
     85        };
     86 
     87        match image1.compare(&image2) {
     88            ReftestImageComparison::Equal => {}
     89            ReftestImageComparison::NotEqual { max_difference, count_different, .. } => {
     90                let t = "rawtest";
     91                println!(
     92                    "REFTEST TEST-UNEXPECTED-FAIL | {t} \
     93                     | image comparison, max difference: {max_difference}, \
     94                     number of differing pixels: {count_different}");
     95                println!("REFTEST   IMAGE 1: {}", image1.create_data_uri());
     96                println!("REFTEST   IMAGE 2: {}", image2.create_data_uri());
     97                println!("REFTEST TEST-END | {}", t);
     98                panic!();
     99            }
    100        }
    101    }
    102 
    103    pub fn submit_dl(
    104        &mut self,
    105        epoch: &mut Epoch,
    106        mut builder: DisplayListBuilder,
    107        mut txn: Transaction,
    108    ) {
    109        txn.use_scene_builder_thread();
    110 
    111        txn.set_display_list(
    112            *epoch,
    113            builder.end(),
    114        );
    115        epoch.0 += 1;
    116 
    117        txn.generate_frame(0, true, false, RenderReasons::TESTING);
    118        self.wrench.api.send_transaction(self.wrench.document_id, txn);
    119    }
    120 
    121    pub fn make_common_properties(&self, clip_rect: LayoutRect) -> CommonItemProperties {
    122        let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
    123        CommonItemProperties {
    124            clip_rect,
    125            clip_chain_id: space_and_clip.clip_chain_id,
    126            spatial_id: space_and_clip.spatial_id,
    127            flags: PrimitiveFlags::default(),
    128        }
    129    }
    130 
    131    pub fn make_common_properties_with_clip_and_spatial(
    132        &self,
    133        clip_rect: LayoutRect,
    134        clip_chain_id: ClipChainId,
    135        spatial_id: SpatialId
    136    ) -> CommonItemProperties {
    137        CommonItemProperties {
    138            clip_rect,
    139            clip_chain_id,
    140            spatial_id,
    141            flags: PrimitiveFlags::default(),
    142        }
    143    }
    144 
    145    fn test_resize_image(&mut self) {
    146        println!("\tresize image...");
    147        // This test changes the size of an image to make it go switch back and forth
    148        // between tiled and non-tiled.
    149        // The resource cache should be able to handle this without crashing.
    150 
    151        let mut txn = Transaction::new();
    152        let img = self.wrench.api.generate_image_key();
    153 
    154        // Start with a non-tiled image.
    155        txn.add_image(
    156            img,
    157            ImageDescriptor::new(64, 64, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
    158            ImageData::new(vec![255; 64 * 64 * 4]),
    159            None,
    160        );
    161 
    162        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
    163        builder.begin();
    164        let info = self.make_common_properties(rect(0.0, 0.0, 64.0, 64.0).to_box2d());
    165 
    166        builder.push_image(
    167            &info,
    168            info.clip_rect,
    169            ImageRendering::Auto,
    170            AlphaType::PremultipliedAlpha,
    171            img,
    172            ColorF::WHITE,
    173        );
    174 
    175        let mut epoch = Epoch(0);
    176 
    177        self.submit_dl(&mut epoch, builder, txn);
    178        self.rx.recv().unwrap();
    179        self.wrench.render();
    180 
    181        let mut txn = Transaction::new();
    182        // Resize the image to something bigger than the max texture size (8196) to force tiling.
    183        txn.update_image(
    184            img,
    185            ImageDescriptor::new(8200, 32, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
    186            ImageData::new(vec![255; 8200 * 32 * 4]),
    187            &DirtyRect::All,
    188        );
    189 
    190        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
    191        builder.begin();
    192        let info = self.make_common_properties(rect(0.0, 0.0, 1024.0, 1024.0).to_box2d());
    193 
    194        builder.push_image(
    195            &info,
    196            info.clip_rect,
    197            ImageRendering::Auto,
    198            AlphaType::PremultipliedAlpha,
    199            img,
    200            ColorF::WHITE,
    201        );
    202 
    203        self.submit_dl(&mut epoch, builder, txn);
    204        self.rx.recv().unwrap();
    205        self.wrench.render();
    206 
    207        let mut txn = Transaction::new();
    208        // Resize back to something doesn't require tiling.
    209        txn.update_image(
    210            img,
    211            ImageDescriptor::new(64, 64, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
    212            ImageData::new(vec![64; 64 * 64 * 4]),
    213            &DirtyRect::All,
    214        );
    215 
    216        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
    217        builder.begin();
    218        let info = self.make_common_properties(rect(0.0, 0.0, 1024.0, 1024.0).to_box2d());
    219 
    220        builder.push_image(
    221            &info,
    222            info.clip_rect,
    223            ImageRendering::Auto,
    224            AlphaType::PremultipliedAlpha,
    225            img,
    226            ColorF::WHITE,
    227        );
    228 
    229        self.submit_dl(&mut epoch, builder, txn);
    230        self.rx.recv().unwrap();
    231        self.wrench.render();
    232 
    233        txn = Transaction::new();
    234        txn.delete_image(img);
    235        self.wrench.api.send_transaction(self.wrench.document_id, txn);
    236    }
    237 
    238    fn test_tile_decomposition(&mut self) {
    239        println!("\ttile decomposition...");
    240        // This exposes a crash in tile decomposition
    241        let mut txn = Transaction::new();
    242 
    243        let blob_img = self.wrench.api.generate_blob_image_key();
    244        txn.add_blob_image(
    245            blob_img,
    246            ImageDescriptor::new(151, 56, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
    247            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
    248            DeviceIntRect::from_size(DeviceIntSize::new(151, 56)),
    249            Some(128),
    250        );
    251 
    252        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
    253        builder.begin();
    254 
    255        let info = self.make_common_properties(rect(448.9, 74.0, 151.000_03, 56.).to_box2d());
    256 
    257        // setup some malicious image size parameters
    258        builder.push_repeating_image(
    259            &info,
    260            info.clip_rect,
    261            size2(151., 56.0),
    262            size2(151.0, 56.0),
    263            ImageRendering::Auto,
    264            AlphaType::PremultipliedAlpha,
    265            blob_img.as_image(),
    266            ColorF::WHITE,
    267        );
    268 
    269        let mut epoch = Epoch(0);
    270 
    271        self.submit_dl(&mut epoch, builder, txn);
    272 
    273        self.rx.recv().unwrap();
    274        self.wrench.render();
    275 
    276        // Leaving a tiled blob image in the resource cache
    277        // confuses the `test_capture`. TODO: remove this
    278        txn = Transaction::new();
    279        txn.delete_blob_image(blob_img);
    280        self.wrench.api.send_transaction(self.wrench.document_id, txn);
    281    }
    282 
    283    fn test_very_large_blob(&mut self) {
    284        println!("\tvery large blob...");
    285 
    286        let window_size = self.window.get_inner_size();
    287 
    288        let test_size = FramebufferIntSize::new(800, 800);
    289 
    290        let window_rect = FramebufferIntRect::from_origin_and_size(
    291            FramebufferIntPoint::new(0, window_size.height - test_size.height),
    292            test_size,
    293        );
    294 
    295        // This exposes a crash in tile decomposition
    296        let mut txn = Transaction::new();
    297 
    298        let blob_img = self.wrench.api.generate_blob_image_key();
    299        txn.add_blob_image(
    300            blob_img,
    301            ImageDescriptor::new(15000, 15000, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
    302            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
    303            DeviceIntRect::from_size(DeviceIntSize::new(15000, 15000)),
    304            Some(100),
    305        );
    306 
    307        let called = Arc::new(AtomicIsize::new(0));
    308        let called_inner = Arc::clone(&called);
    309 
    310        self.wrench.callbacks.lock().unwrap().request = Box::new(move |_| {
    311            called_inner.fetch_add(1, Ordering::SeqCst);
    312        });
    313 
    314        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
    315        builder.begin();
    316 
    317        let root_space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
    318        let clip_id = builder.define_clip_rect(
    319            root_space_and_clip.spatial_id,
    320            rect(40., 41., 200., 201.).to_box2d(),
    321        );
    322        let clip_chain_id = builder.define_clip_chain(None, [clip_id]);
    323 
    324        let info = CommonItemProperties {
    325            clip_rect: rect(0.0, 0.0, 800.0, 800.0).to_box2d(),
    326            clip_chain_id,
    327            spatial_id: root_space_and_clip.spatial_id,
    328            flags: PrimitiveFlags::default(),
    329        };
    330 
    331        // setup some malicious image size parameters
    332        builder.push_repeating_image(
    333            &info,
    334            size2(15000.0, 15000.0).into(),
    335            size2(15000.0, 15000.0),
    336            size2(0.0, 0.0),
    337            ImageRendering::Auto,
    338            AlphaType::PremultipliedAlpha,
    339            blob_img.as_image(),
    340            ColorF::WHITE,
    341        );
    342 
    343        let mut epoch = Epoch(0);
    344 
    345        self.submit_dl(&mut epoch, builder, txn);
    346 
    347        let pixels = self.render_and_get_pixels(window_rect);
    348 
    349        // make sure we didn't request too many blobs
    350        assert!(called.load(Ordering::SeqCst) < 20);
    351 
    352        //use crate::png;
    353        //png::save_flipped("out.png", pixels.clone(), size2(window_rect.size.width, window_rect.size.height));
    354 
    355        // make sure things are in the right spot
    356        let w = window_rect.width() as usize;
    357        let h = window_rect.height() as usize;
    358        let p1 = (40 + (h - 100) * w) * 4;
    359        assert_eq!(pixels[p1    ], 50);
    360        assert_eq!(pixels[p1 + 1], 50);
    361        assert_eq!(pixels[p1 + 2], 150);
    362        assert_eq!(pixels[p1 + 3], 255);
    363 
    364        // Leaving a tiled blob image in the resource cache
    365        // confuses the `test_capture`. TODO: remove this
    366        txn = Transaction::new();
    367        txn.delete_blob_image(blob_img);
    368        self.wrench.api.send_transaction(self.wrench.document_id, txn);
    369 
    370        *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
    371    }
    372 
    373    fn test_blob_visible_area(&mut self) {
    374        println!("\tblob visible area...");
    375 
    376        let window_size = self.window.get_inner_size();
    377        let test_size = FramebufferIntSize::new(800, 800);
    378        let window_rect = FramebufferIntRect::from_origin_and_size(
    379            FramebufferIntPoint::new(0, window_size.height - test_size.height),
    380            test_size,
    381        );
    382        let mut txn = Transaction::new();
    383 
    384        let blob_img = self.wrench.api.generate_blob_image_key();
    385        txn.add_blob_image(
    386            blob_img,
    387            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
    388            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
    389            DeviceIntRect {
    390                min: point2(50, 20),
    391                max: point2(450, 420),
    392            },
    393            Some(100),
    394        );
    395 
    396        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
    397        builder.begin();
    398 
    399        let image_size = size2(400.0, 400.0);
    400 
    401        let root_space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
    402        let clip_id = builder.define_clip_rect(
    403            root_space_and_clip.spatial_id,
    404            rect(-1000.0, -1000.0, 2000.0, 2000.0).to_box2d(),
    405        );
    406        let clip_chain_id = builder.define_clip_chain(None, [clip_id]);
    407 
    408        let info = CommonItemProperties {
    409            clip_rect: rect(10.0, 10.0, 400.0, 400.0).to_box2d(),
    410            clip_chain_id,
    411            spatial_id: root_space_and_clip.spatial_id,
    412            flags: PrimitiveFlags::default(),
    413        };
    414 
    415        builder.push_repeating_image(
    416            &info,
    417            info.clip_rect,
    418            image_size,
    419            image_size,
    420            ImageRendering::Auto,
    421            AlphaType::PremultipliedAlpha,
    422            blob_img.as_image(),
    423            ColorF::WHITE,
    424        );
    425        let mut epoch = Epoch(0);
    426 
    427        self.submit_dl(&mut epoch, builder, txn);
    428 
    429        let pixels = self.render_and_get_pixels(window_rect);
    430 
    431        //use super::png;
    432        //png::save_flipped("out.png", pixels.clone(), size2(window_rect.size.width, window_rect.size.height));
    433 
    434 
    435        // make sure things are in the right spot
    436        let w = window_rect.width() as usize;
    437        let h = window_rect.height() as usize;
    438        let p1 = (65 + (h - 15) * w) * 4;
    439        assert_eq!(pixels[p1    ], 255);
    440        assert_eq!(pixels[p1 + 1], 255);
    441        assert_eq!(pixels[p1 + 2], 255);
    442        assert_eq!(pixels[p1 + 3], 255);
    443 
    444        let p2 = (25 + (h - 15) * w) * 4;
    445        assert_eq!(pixels[p2    ], 221);
    446        assert_eq!(pixels[p2 + 1], 221);
    447        assert_eq!(pixels[p2 + 2], 221);
    448        assert_eq!(pixels[p2 + 3], 255);
    449 
    450        let p3 = (15 + (h - 15) * w) * 4;
    451        assert_eq!(pixels[p3    ], 50);
    452        assert_eq!(pixels[p3 + 1], 50);
    453        assert_eq!(pixels[p3 + 2], 150);
    454        assert_eq!(pixels[p3 + 3], 255);
    455 
    456        // Leaving a tiled blob image in the resource cache
    457        // confuses the `test_capture`. TODO: remove this
    458        txn = Transaction::new();
    459        txn.delete_blob_image(blob_img);
    460        self.wrench.api.send_transaction(self.wrench.document_id, txn);
    461 
    462        *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
    463    }
    464 
    465    fn test_blob_set_visible_area(&mut self) {
    466        // In this test we first render a blob with a certain visible area,
    467        // then change the visible area without updating the blob image.
    468 
    469        println!("\tblob visible area update...");
    470 
    471        let window_size = self.window.get_inner_size();
    472        let test_size = FramebufferIntSize::new(800, 800);
    473        let window_rect = FramebufferIntRect::from_origin_and_size(
    474            FramebufferIntPoint::new(0, window_size.height - test_size.height),
    475            test_size,
    476        );
    477        let mut txn = Transaction::new();
    478 
    479        let blob_img = self.wrench.api.generate_blob_image_key();
    480        txn.add_blob_image(
    481            blob_img,
    482            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
    483            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
    484            DeviceIntRect {
    485                min: point2(0, 0),
    486                max: point2(500, 500),
    487            },
    488            Some(128),
    489        );
    490 
    491        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
    492        builder.begin();
    493 
    494        let root_space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
    495        let clip_id = builder.define_clip_rect(
    496            root_space_and_clip.spatial_id,
    497            rect(-1000.0, -1000.0, 2000.0, 2000.0).to_box2d(),
    498        );
    499        let clip_chain_id = builder.define_clip_chain(None, [clip_id]);
    500 
    501        let info = CommonItemProperties {
    502            clip_rect: rect(0.0, 0.0, 1000.0, 1000.0).to_box2d(),
    503            clip_chain_id,
    504            spatial_id: root_space_and_clip.spatial_id,
    505            flags: PrimitiveFlags::default(),
    506        };
    507 
    508        builder.push_repeating_image(
    509            &info,
    510            rect(0.0, 0.0, 500.0, 500.0).to_box2d(),
    511            size2(500.0, 500.0),
    512            size2(500.0, 500.0),
    513            ImageRendering::Auto,
    514            AlphaType::PremultipliedAlpha,
    515            blob_img.as_image(),
    516            ColorF::WHITE,
    517        );
    518        let mut epoch = Epoch(0);
    519 
    520        // Render the first display list. We don't care about the result but we
    521        // want to make sure the next display list updates an already rendered
    522        // state.
    523        self.submit_dl(&mut epoch, builder, txn);
    524        let _ = self.render_and_get_pixels(window_rect);
    525 
    526        // Now render a similar scene with an updated blob visible area.
    527        // In this test we care about the fact that the visible area was updated
    528        // without using update_blob_image.
    529 
    530        let mut txn = Transaction::new();
    531 
    532        txn.set_blob_image_visible_area(blob_img, DeviceIntRect {
    533            min: point2(50, 50),
    534            max: point2(450, 450),
    535        });
    536 
    537        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
    538        builder.begin();
    539 
    540        let root_space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
    541        let clip_id = builder.define_clip_rect(
    542            root_space_and_clip.spatial_id,
    543            rect(-1000.0, -1000.0, 2000.0, 2000.0).to_box2d(),
    544        );
    545        let clip_chain_id = builder.define_clip_chain(None, [clip_id]);
    546 
    547        let info = CommonItemProperties {
    548            clip_rect: rect(0.0, 0.0, 1000.0, 1000.0).to_box2d(),
    549            clip_chain_id,
    550            spatial_id: root_space_and_clip.spatial_id,
    551            flags: PrimitiveFlags::default(),
    552        };
    553 
    554        builder.push_repeating_image(
    555            &info,
    556            rect(50.0, 50.0, 400.0, 400.0).to_box2d(),
    557            size2(400.0, 400.0),
    558            size2(400.0, 400.0),
    559            ImageRendering::Auto,
    560            AlphaType::PremultipliedAlpha,
    561            blob_img.as_image(),
    562            ColorF::WHITE,
    563        );
    564 
    565        self.submit_dl(&mut epoch, builder, txn);
    566        let resized_pixels = self.render_and_get_pixels(window_rect);
    567 
    568        // Now render the same scene with a new blob image created with the same
    569        // visible area as the previous scene, without going through an update.
    570 
    571        let mut txn = Transaction::new();
    572 
    573        let blob_img2 = self.wrench.api.generate_blob_image_key();
    574        txn.add_blob_image(
    575            blob_img2,
    576            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
    577            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
    578            DeviceIntRect {
    579                min: point2(50, 50),
    580                max: point2(450, 450),
    581            },
    582            Some(128),
    583        );
    584 
    585        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
    586        builder.begin();
    587 
    588        let root_space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
    589        let clip_id = builder.define_clip_rect(
    590            root_space_and_clip.spatial_id,
    591            rect(-1000.0, -1000.0, 2000.0, 2000.0).to_box2d(),
    592        );
    593        let clip_chain_id = builder.define_clip_chain(None, [clip_id]);
    594 
    595        let info = CommonItemProperties {
    596            clip_rect: rect(0.0, 0.0, 1000.0, 1000.0).to_box2d(),
    597            clip_chain_id,
    598            spatial_id: root_space_and_clip.spatial_id,
    599            flags: PrimitiveFlags::default(),
    600        };
    601 
    602        builder.push_repeating_image(
    603            &info,
    604            rect(50.0, 50.0, 400.0, 400.0).to_box2d(),
    605            size2(400.0, 400.0),
    606            size2(400.0, 400.0),
    607            ImageRendering::Auto,
    608            AlphaType::PremultipliedAlpha,
    609            blob_img2.as_image(),
    610            ColorF::WHITE,
    611        );
    612        let mut epoch = Epoch(0);
    613 
    614        self.submit_dl(&mut epoch, builder, txn);
    615 
    616        let reference_pixels = self.render_and_get_pixels(window_rect);
    617 
    618        assert_eq!(resized_pixels, reference_pixels);
    619 
    620        txn = Transaction::new();
    621        txn.delete_blob_image(blob_img);
    622        txn.delete_blob_image(blob_img2);
    623        self.wrench.api.send_transaction(self.wrench.document_id, txn);
    624    }
    625 
    626    fn test_offscreen_blob(&mut self) {
    627        println!("\toffscreen blob update...");
    628 
    629        let window_size = self.window.get_inner_size();
    630 
    631        let test_size = FramebufferIntSize::new(800, 800);
    632        let window_rect = FramebufferIntRect::from_origin_and_size(
    633            point2(0, window_size.height - test_size.height),
    634            test_size,
    635        );
    636 
    637        // This exposes a crash in tile decomposition
    638        let mut txn = Transaction::new();
    639 
    640        let blob_img = self.wrench.api.generate_blob_image_key();
    641        txn.add_blob_image(
    642            blob_img,
    643            ImageDescriptor::new(1510, 1510, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
    644            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
    645            DeviceIntRect::from_size(size2(1510, 1510)),
    646            None,
    647        );
    648 
    649        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
    650        builder.begin();
    651 
    652        let info = self.make_common_properties(rect(0., 0.0, 1510., 1510.).to_box2d());
    653 
    654        let image_size = size2(1510., 1510.);
    655 
    656        // setup some malicious image size parameters
    657        builder.push_repeating_image(
    658            &info,
    659            info.clip_rect,
    660            image_size,
    661            image_size,
    662            ImageRendering::Auto,
    663            AlphaType::PremultipliedAlpha,
    664            blob_img.as_image(),
    665            ColorF::WHITE,
    666        );
    667 
    668        let mut epoch = Epoch(0);
    669 
    670        self.submit_dl(&mut epoch, builder, txn);
    671 
    672        let original_pixels = self.render_and_get_pixels(window_rect);
    673 
    674        let mut epoch = Epoch(1);
    675 
    676        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
    677        builder.begin();
    678 
    679        let info = self.make_common_properties(rect(-10000., 0.0, 1510., 1510.).to_box2d());
    680 
    681        let image_size = size2(1510., 1510.);
    682 
    683        // setup some malicious image size parameters
    684        builder.push_repeating_image(
    685            &info,
    686            info.clip_rect,
    687            image_size,
    688            image_size,
    689            ImageRendering::Auto,
    690            AlphaType::PremultipliedAlpha,
    691            blob_img.as_image(),
    692            ColorF::WHITE,
    693        );
    694 
    695        self.submit_dl(&mut epoch, builder, Transaction::new());
    696 
    697        let _offscreen_pixels = self.render_and_get_pixels(window_rect);
    698 
    699        let mut txn = Transaction::new();
    700 
    701        txn.update_blob_image(
    702            blob_img,
    703            ImageDescriptor::new(1510, 1510, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
    704            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
    705            DeviceIntRect::from_size(size2(1510, 1510)),
    706            &Box2D { min: point2(10, 10), max: point2(110, 110) }.into(),
    707        );
    708 
    709        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
    710        builder.begin();
    711 
    712        let info = self.make_common_properties(rect(0., 0.0, 1510., 1510.).to_box2d());
    713 
    714        let image_size = size2(1510., 1510.);
    715 
    716        // setup some malicious image size parameters
    717        builder.push_repeating_image(
    718            &info,
    719            info.clip_rect,
    720            image_size,
    721            image_size,
    722            ImageRendering::Auto,
    723            AlphaType::PremultipliedAlpha,
    724            blob_img.as_image(),
    725            ColorF::WHITE,
    726        );
    727 
    728        let mut epoch = Epoch(2);
    729 
    730        self.submit_dl(&mut epoch, builder, txn);
    731 
    732        let pixels = self.render_and_get_pixels(window_rect);
    733 
    734        self.compare_pixels(original_pixels, pixels, window_rect.size());
    735 
    736        // Leaving a tiled blob image in the resource cache
    737        // confuses the `test_capture`. TODO: remove this
    738        txn = Transaction::new();
    739        txn.delete_blob_image(blob_img);
    740        self.wrench.api.send_transaction(self.wrench.document_id, txn);
    741    }
    742 
    743    fn test_retained_blob_images_test(&mut self) {
    744        println!("\tretained blob images test...");
    745        let blob_img;
    746        let window_size = self.window.get_inner_size();
    747 
    748        let test_size = FramebufferIntSize::new(400, 400);
    749        let window_rect = FramebufferIntRect::from_origin_and_size(
    750            FramebufferIntPoint::new(0, window_size.height - test_size.height),
    751            test_size,
    752        );
    753 
    754        let mut txn = Transaction::new();
    755        {
    756            let api = &self.wrench.api;
    757 
    758            blob_img = api.generate_blob_image_key();
    759            txn.add_blob_image(
    760                blob_img,
    761                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
    762                blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
    763                DeviceIntRect::from_size(size2(500, 500)),
    764                None,
    765            );
    766        }
    767 
    768        let called = Arc::new(AtomicIsize::new(0));
    769        let called_inner = Arc::clone(&called);
    770 
    771        self.wrench.callbacks.lock().unwrap().request = Box::new(move |_| {
    772            assert_eq!(0, called_inner.fetch_add(1, Ordering::SeqCst));
    773        });
    774 
    775        // draw the blob the first time
    776        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
    777        builder.begin();
    778        let info = self.make_common_properties(rect(0.0, 60.0, 200.0, 200.0).to_box2d());
    779 
    780        builder.push_image(
    781            &info,
    782            info.clip_rect,
    783            ImageRendering::Auto,
    784            AlphaType::PremultipliedAlpha,
    785            blob_img.as_image(),
    786            ColorF::WHITE,
    787        );
    788 
    789        let mut epoch = Epoch(0);
    790 
    791        self.submit_dl(&mut epoch, builder, txn);
    792 
    793        let pixels_first = self.render_and_get_pixels(window_rect);
    794 
    795        assert_eq!(1, called.load(Ordering::SeqCst));
    796 
    797        // draw the blob image a second time at a different location
    798 
    799        // make a new display list that refers to the first image
    800        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
    801        builder.begin();
    802        let info = self.make_common_properties(rect(1.0, 60.0, 200.0, 200.0).to_box2d());
    803        builder.push_image(
    804            &info,
    805            info.clip_rect,
    806            ImageRendering::Auto,
    807            AlphaType::PremultipliedAlpha,
    808            blob_img.as_image(),
    809            ColorF::WHITE,
    810        );
    811 
    812        let mut txn = Transaction::new();
    813        txn.resource_updates.clear();
    814 
    815        self.submit_dl(&mut epoch, builder, txn);
    816 
    817        let pixels_second = self.render_and_get_pixels(window_rect);
    818 
    819        // make sure we only requested once
    820        assert_eq!(1, called.load(Ordering::SeqCst));
    821 
    822        // use png;
    823        // png::save_flipped("out1.png", &pixels_first, window_rect.size);
    824        // png::save_flipped("out2.png", &pixels_second, window_rect.size);
    825        assert!(pixels_first != pixels_second);
    826 
    827        // cleanup
    828        *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
    829    }
    830 
    831    fn test_blob_update_epoch_test(&mut self) {
    832        println!("\tblob update epoch test...");
    833        let (blob_img, blob_img2);
    834        let window_size = self.window.get_inner_size();
    835 
    836        let test_size = FramebufferIntSize::new(400, 400);
    837        let window_rect = FramebufferIntRect::from_origin_and_size(
    838            point2(0, window_size.height - test_size.height),
    839            test_size,
    840        );
    841 
    842        let mut txn = Transaction::new();
    843        let (blob_img, blob_img2) = {
    844            let api = &self.wrench.api;
    845 
    846            blob_img = api.generate_blob_image_key();
    847            txn.add_blob_image(
    848                blob_img,
    849                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
    850                blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
    851                DeviceIntRect::from_size(size2(500, 500)),
    852                None,
    853            );
    854            blob_img2 = api.generate_blob_image_key();
    855            txn.add_blob_image(
    856                blob_img2,
    857                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
    858                blob::serialize_blob(ColorU::new(80, 50, 150, 255)),
    859                DeviceIntRect::from_size(size2(500, 500)),
    860                None,
    861            );
    862            (blob_img, blob_img2)
    863        };
    864 
    865        // setup some counters to count how many times each image is requested
    866        let img1_requested = Arc::new(AtomicIsize::new(0));
    867        let img1_requested_inner = Arc::clone(&img1_requested);
    868        let img2_requested = Arc::new(AtomicIsize::new(0));
    869        let img2_requested_inner = Arc::clone(&img2_requested);
    870 
    871        // track the number of times that the second image has been requested
    872        self.wrench.callbacks.lock().unwrap().request = Box::new(move |requests| {
    873            for item in requests {
    874                if item.request.key == blob_img {
    875                    img1_requested_inner.fetch_add(1, Ordering::SeqCst);
    876                }
    877                if item.request.key == blob_img2 {
    878                    img2_requested_inner.fetch_add(1, Ordering::SeqCst);
    879                }
    880            }
    881        });
    882 
    883        // create two blob images and draw them
    884        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
    885        builder.begin();
    886        let info = self.make_common_properties(rect(0.0, 60.0, 200.0, 200.0).to_box2d());
    887        let info2 = self.make_common_properties(rect(200.0, 60.0, 200.0, 200.0).to_box2d());
    888        let push_images = |builder: &mut DisplayListBuilder| {
    889            builder.push_image(
    890                &info,
    891                info.clip_rect,
    892                ImageRendering::Auto,
    893                AlphaType::PremultipliedAlpha,
    894                blob_img.as_image(),
    895                ColorF::WHITE,
    896            );
    897            builder.push_image(
    898                &info2,
    899                info2.clip_rect,
    900                ImageRendering::Auto,
    901                AlphaType::PremultipliedAlpha,
    902                blob_img2.as_image(),
    903                ColorF::WHITE,
    904            );
    905        };
    906 
    907        push_images(&mut builder);
    908 
    909        let mut epoch = Epoch(0);
    910 
    911        self.submit_dl(&mut epoch, builder, txn);
    912        let _pixels_first = self.render_and_get_pixels(window_rect);
    913 
    914        // update and redraw both images
    915        let mut txn = Transaction::new();
    916        txn.update_blob_image(
    917            blob_img,
    918            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
    919            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
    920            DeviceIntRect::from_size(size2(500, 500)),
    921            &Box2D { min: point2(100, 100), max: point2(200, 200) }.into(),
    922        );
    923        txn.update_blob_image(
    924            blob_img2,
    925            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
    926            blob::serialize_blob(ColorU::new(59, 50, 150, 255)),
    927            DeviceIntRect::from_size(size2(500, 500)),
    928            &Box2D { min: point2(100, 100), max: point2(200, 200) }.into(),
    929        );
    930 
    931        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
    932        builder.begin();
    933        push_images(&mut builder);
    934        self.submit_dl(&mut epoch, builder, txn);
    935        let _pixels_second = self.render_and_get_pixels(window_rect);
    936 
    937        // only update the first image
    938        let mut txn = Transaction::new();
    939        txn.update_blob_image(
    940            blob_img,
    941            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
    942            blob::serialize_blob(ColorU::new(50, 150, 150, 255)),
    943            DeviceIntRect::from_size(size2(500, 500)),
    944            &Box2D { min: point2(200, 200), max: point2(300, 300) }.into(),
    945        );
    946 
    947        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
    948        builder.begin();
    949        push_images(&mut builder);
    950        self.submit_dl(&mut epoch, builder, txn);
    951        let _pixels_third = self.render_and_get_pixels(window_rect);
    952 
    953        // the first image should be requested 3 times
    954        assert_eq!(img1_requested.load(Ordering::SeqCst), 3);
    955        // the second image should've been requested twice
    956        assert_eq!(img2_requested.load(Ordering::SeqCst), 2);
    957 
    958        // cleanup
    959        *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
    960    }
    961 
    962    fn test_blob_update_test(&mut self) {
    963        println!("\tblob update test...");
    964        let window_size = self.window.get_inner_size();
    965 
    966        let test_size = FramebufferIntSize::new(400, 400);
    967        let window_rect = FramebufferIntRect::from_origin_and_size(
    968            point2(0, window_size.height - test_size.height),
    969            test_size,
    970        );
    971        let mut txn = Transaction::new();
    972 
    973        let blob_img = {
    974            let img = self.wrench.api.generate_blob_image_key();
    975            txn.add_blob_image(
    976                img,
    977                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
    978                blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
    979                DeviceIntRect::from_size(size2(500, 500)),
    980                None,
    981            );
    982            img
    983        };
    984 
    985        // draw the blobs the first time
    986        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
    987        builder.begin();
    988        let info = self.make_common_properties(rect(0.0, 60.0, 200.0, 200.0).to_box2d());
    989 
    990        builder.push_image(
    991            &info,
    992            info.clip_rect,
    993            ImageRendering::Auto,
    994            AlphaType::PremultipliedAlpha,
    995            blob_img.as_image(),
    996            ColorF::WHITE,
    997        );
    998 
    999        let mut epoch = Epoch(0);
   1000 
   1001        self.submit_dl(&mut epoch, builder, txn);
   1002        let pixels_first = self.render_and_get_pixels(window_rect);
   1003 
   1004        // draw the blob image a second time after updating it with the same color
   1005        let mut txn = Transaction::new();
   1006        txn.update_blob_image(
   1007            blob_img,
   1008            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
   1009            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
   1010            DeviceIntRect::from_size(size2(500, 500)),
   1011            &Box2D { min: point2(100, 100), max: point2(200, 200) }.into(),
   1012        );
   1013 
   1014        // make a new display list that refers to the first image
   1015        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
   1016        builder.begin();
   1017        let info = self.make_common_properties(rect(0.0, 60.0, 200.0, 200.0).to_box2d());
   1018        builder.push_image(
   1019            &info,
   1020            info.clip_rect,
   1021            ImageRendering::Auto,
   1022            AlphaType::PremultipliedAlpha,
   1023            blob_img.as_image(),
   1024            ColorF::WHITE,
   1025        );
   1026 
   1027        self.submit_dl(&mut epoch, builder, txn);
   1028        let pixels_second = self.render_and_get_pixels(window_rect);
   1029 
   1030        // draw the blob image a third time after updating it with a different color
   1031        let mut txn = Transaction::new();
   1032        txn.update_blob_image(
   1033            blob_img,
   1034            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
   1035            blob::serialize_blob(ColorU::new(50, 150, 150, 255)),
   1036            DeviceIntRect::from_size(size2(500, 500)),
   1037            &Box2D { min: point2(200, 200), max: point2(300, 300) }.into(),
   1038        );
   1039 
   1040        // make a new display list that refers to the first image
   1041        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
   1042        builder.begin();
   1043        let info = self.make_common_properties(rect(0.0, 60.0, 200.0, 200.0).to_box2d());
   1044        builder.push_image(
   1045            &info,
   1046            info.clip_rect,
   1047            ImageRendering::Auto,
   1048            AlphaType::PremultipliedAlpha,
   1049            blob_img.as_image(),
   1050            ColorF::WHITE,
   1051        );
   1052 
   1053        self.submit_dl(&mut epoch, builder, txn);
   1054        let pixels_third = self.render_and_get_pixels(window_rect);
   1055 
   1056        assert!(pixels_first != pixels_third);
   1057        self.compare_pixels(pixels_first, pixels_second, window_rect.size());
   1058    }
   1059 
   1060    // Ensures that content doing a save-restore produces the same results as not
   1061    fn test_save_restore(&mut self) {
   1062        println!("\tsave/restore...");
   1063        let window_size = self.window.get_inner_size();
   1064 
   1065        let test_size = FramebufferIntSize::new(400, 400);
   1066        let window_rect = FramebufferIntRect::from_origin_and_size(
   1067            point2(0, window_size.height - test_size.height),
   1068            test_size,
   1069        );
   1070 
   1071        let mut do_test = |should_try_and_fail| {
   1072            let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
   1073            builder.begin();
   1074 
   1075            let spatial_id = SpatialId::root_scroll_node(self.wrench.root_pipeline_id);
   1076            let clip_id = builder.define_clip_rect(
   1077                SpatialId::root_scroll_node(self.wrench.root_pipeline_id),
   1078                rect(110., 120., 200., 200.).to_box2d(),
   1079            );
   1080            let clip_chain_id = builder.define_clip_chain(None, [clip_id]);
   1081            builder.push_rect(
   1082                &self.make_common_properties_with_clip_and_spatial(
   1083                    rect(100., 100., 100., 100.).to_box2d(),
   1084                    clip_chain_id,
   1085                    spatial_id),
   1086                rect(100., 100., 100., 100.).to_box2d(),
   1087                ColorF::new(0.0, 0.0, 1.0, 1.0),
   1088            );
   1089 
   1090            if should_try_and_fail {
   1091                builder.save();
   1092                let clip_id = builder.define_clip_rect(
   1093                    spatial_id,
   1094                    rect(80., 80., 90., 90.).to_box2d(),
   1095                );
   1096                let clip_chain_id = builder.define_clip_chain(None, [clip_id]);
   1097                let space_and_clip = SpaceAndClipInfo {
   1098                    spatial_id,
   1099                    clip_chain_id,
   1100                };
   1101                builder.push_rect(
   1102                    &self.make_common_properties_with_clip_and_spatial(
   1103                        rect(110., 110., 50., 50.).to_box2d(),
   1104                        clip_chain_id,
   1105                        spatial_id),
   1106                    rect(110., 110., 50., 50.).to_box2d(),
   1107                    ColorF::new(0.0, 1.0, 0.0, 1.0),
   1108                );
   1109                builder.push_shadow(
   1110                    &space_and_clip,
   1111                    Shadow {
   1112                        offset: LayoutVector2D::new(1.0, 1.0),
   1113                        blur_radius: 1.0,
   1114                        color: ColorF::new(0.0, 0.0, 0.0, 1.0),
   1115                    },
   1116                    true,
   1117                );
   1118                let info = CommonItemProperties {
   1119                    clip_rect: rect(110., 110., 50., 2.).to_box2d(),
   1120                    clip_chain_id,
   1121                    spatial_id,
   1122                    flags: PrimitiveFlags::default(),
   1123                };
   1124                builder.push_line(
   1125                    &info,
   1126                    &info.clip_rect,
   1127                    0.0, LineOrientation::Horizontal,
   1128                    &ColorF::new(0.0, 0.0, 0.0, 1.0),
   1129                    LineStyle::Solid,
   1130                );
   1131                builder.restore();
   1132            }
   1133 
   1134            {
   1135                builder.save();
   1136                let clip_id = builder.define_clip_rect(
   1137                    spatial_id,
   1138                    rect(80., 80., 100., 100.).to_box2d(),
   1139                );
   1140                let clip_chain_id = builder.define_clip_chain(None, [clip_id]);
   1141                builder.push_rect(
   1142                    &self.make_common_properties_with_clip_and_spatial(
   1143                        rect(150., 150., 100., 100.).to_box2d(),
   1144                        clip_chain_id,
   1145                        spatial_id),
   1146                    rect(150., 150., 100., 100.).to_box2d(),
   1147                    ColorF::new(0.0, 0.0, 1.0, 1.0),
   1148                );
   1149                builder.clear_save();
   1150            }
   1151 
   1152            let txn = Transaction::new();
   1153 
   1154            self.submit_dl(&mut Epoch(0), builder, txn);
   1155 
   1156            self.render_and_get_pixels(window_rect)
   1157        };
   1158 
   1159        let first = do_test(false);
   1160        let second = do_test(true);
   1161 
   1162        self.compare_pixels(first, second, window_rect.size());
   1163    }
   1164 
   1165    // regression test for #2769
   1166    // "async scene building: cache collisions from reused picture ids"
   1167    fn test_blur_cache(&mut self) {
   1168        println!("\tblur cache...");
   1169        let window_size = self.window.get_inner_size();
   1170 
   1171        let test_size = FramebufferIntSize::new(400, 400);
   1172        let window_rect = FramebufferIntRect::from_origin_and_size(
   1173            point2(0, window_size.height - test_size.height),
   1174            test_size,
   1175        );
   1176 
   1177        let mut do_test = |shadow_is_red| {
   1178            let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
   1179            builder.begin();
   1180            let shadow_color = if shadow_is_red {
   1181                ColorF::new(1.0, 0.0, 0.0, 1.0)
   1182            } else {
   1183                ColorF::new(0.0, 1.0, 0.0, 1.0)
   1184            };
   1185 
   1186            builder.push_shadow(
   1187                &SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id),
   1188                Shadow {
   1189                    offset: LayoutVector2D::new(1.0, 1.0),
   1190                    blur_radius: 1.0,
   1191                    color: shadow_color,
   1192                },
   1193                true,
   1194            );
   1195            let info = self.make_common_properties(rect(110., 110., 50., 2.).to_box2d());
   1196            builder.push_line(
   1197                &info,
   1198                &info.clip_rect,
   1199                0.0, LineOrientation::Horizontal,
   1200                &ColorF::new(0.0, 0.0, 0.0, 1.0),
   1201                LineStyle::Solid,
   1202            );
   1203            builder.pop_all_shadows();
   1204 
   1205            let txn = Transaction::new();
   1206            self.submit_dl(&mut Epoch(0), builder, txn);
   1207 
   1208            self.render_and_get_pixels(window_rect)
   1209        };
   1210 
   1211        let first = do_test(false);
   1212        let second = do_test(true);
   1213 
   1214        assert_ne!(first, second);
   1215    }
   1216 
   1217    fn test_capture(&mut self) {
   1218        println!("\tcapture...");
   1219        let path = "../captures/test";
   1220        let layout_size = LayoutSize::new(400., 400.);
   1221        let dim = self.window.get_inner_size();
   1222        let window_rect = FramebufferIntRect::from_origin_and_size(
   1223            point2(0, dim.height - layout_size.height as i32),
   1224            size2(layout_size.width as i32, layout_size.height as i32),
   1225        );
   1226 
   1227        // 1. render some scene
   1228 
   1229        let mut txn = Transaction::new();
   1230        let image = self.wrench.api.generate_image_key();
   1231        txn.add_image(
   1232            image,
   1233            ImageDescriptor::new(1, 1, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
   1234            ImageData::new(vec![0xFF, 0, 0, 0xFF]),
   1235            None,
   1236        );
   1237 
   1238        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
   1239        builder.begin();
   1240 
   1241        let info = self.make_common_properties(rect(300.0, 70.0, 150.0, 50.0).to_box2d());
   1242        builder.push_image(
   1243            &info,
   1244            info.clip_rect,
   1245            ImageRendering::Auto,
   1246            AlphaType::PremultipliedAlpha,
   1247            image,
   1248            ColorF::WHITE,
   1249        );
   1250 
   1251        let mut txn = Transaction::new();
   1252 
   1253        txn.set_display_list(
   1254            Epoch(0),
   1255            builder.end(),
   1256        );
   1257        txn.generate_frame(0, true, false, RenderReasons::TESTING);
   1258 
   1259        self.wrench.api.send_transaction(self.wrench.document_id, txn);
   1260 
   1261        let pixels0 = self.render_and_get_pixels(window_rect);
   1262 
   1263        // 2. capture it
   1264        self.wrench.api.save_capture(path.into(), CaptureBits::all());
   1265 
   1266        // 3. set a different scene
   1267 
   1268        builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
   1269        builder.begin();
   1270 
   1271        let mut txn = Transaction::new();
   1272        txn.set_display_list(
   1273            Epoch(1),
   1274            builder.end(),
   1275        );
   1276        self.wrench.api.send_transaction(self.wrench.document_id, txn);
   1277 
   1278        // 4. load the first one
   1279 
   1280        let mut documents = self.wrench.api.load_capture(path.into(), None);
   1281        let captured = documents.swap_remove(0);
   1282 
   1283        // 5. render the built frame and compare
   1284        let pixels1 = self.render_and_get_pixels(window_rect);
   1285        self.compare_pixels(pixels0.clone(), pixels1, window_rect.size());
   1286 
   1287        // 6. rebuild the scene and compare again
   1288        let mut txn = Transaction::new();
   1289        txn.set_root_pipeline(captured.root_pipeline_id.unwrap());
   1290        txn.generate_frame(0, true, false, RenderReasons::TESTING);
   1291        self.wrench.api.send_transaction(captured.document_id, txn);
   1292        let pixels2 = self.render_and_get_pixels(window_rect);
   1293        self.compare_pixels(pixels0, pixels2, window_rect.size());
   1294    }
   1295 
   1296    fn test_zero_height_window(&mut self) {
   1297        println!("\tzero height test...");
   1298 
   1299        let layout_size = LayoutSize::new(120.0, 0.0);
   1300        let window_size = DeviceIntSize::new(layout_size.width as i32, layout_size.height as i32);
   1301        let doc_id = self.wrench.api.add_document(window_size);
   1302 
   1303        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
   1304        builder.begin();
   1305        let info = self.make_common_properties(
   1306            LayoutRect::from_size(LayoutSize::new(100.0, 100.0))
   1307        );
   1308        builder.push_rect(
   1309            &info,
   1310            info.clip_rect,
   1311            ColorF::new(0.0, 1.0, 0.0, 1.0),
   1312        );
   1313 
   1314        let mut txn = Transaction::new();
   1315        txn.set_root_pipeline(self.wrench.root_pipeline_id);
   1316        txn.set_display_list(
   1317            Epoch(1),
   1318            builder.end(),
   1319        );
   1320        txn.generate_frame(0, true, false, RenderReasons::TESTING);
   1321        self.wrench.api.send_transaction(doc_id, txn);
   1322 
   1323        // Ensure we get a notification from rendering the above, even though
   1324        // there are zero visible pixels
   1325        assert!(self.rx.recv().unwrap() == NotifierEvent::WakeUp { composite_needed: true });
   1326    }
   1327 
   1328 
   1329    fn test_hit_testing(&mut self) {
   1330        println!("\thit testing test...");
   1331 
   1332        let layout_size = LayoutSize::new(400., 400.);
   1333        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
   1334        builder.begin();
   1335 
   1336        // Add a rectangle that covers the entire scene.
   1337        let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
   1338        builder.push_hit_test(
   1339            LayoutRect::from_size(layout_size),
   1340            ClipChainId::INVALID,
   1341            space_and_clip.spatial_id,
   1342            PrimitiveFlags::default(),
   1343            (0, 1),
   1344        );
   1345 
   1346        // Add a simple 100x100 rectangle at 100,0.
   1347        builder.push_hit_test(
   1348            LayoutRect::from_origin_and_size(
   1349                LayoutPoint::new(100., 0.),
   1350                LayoutSize::new(100., 100.)
   1351            ),
   1352            ClipChainId::INVALID,
   1353            space_and_clip.spatial_id,
   1354            PrimitiveFlags::default(),
   1355            (0, 2),
   1356        );
   1357 
   1358        let make_rounded_complex_clip = |rect: &LayoutRect, radius: f32| -> ComplexClipRegion {
   1359            ComplexClipRegion::new(
   1360                *rect,
   1361                BorderRadius::uniform_size(LayoutSize::new(radius, radius)),
   1362                ClipMode::Clip
   1363            )
   1364        };
   1365 
   1366        // Add a rectangle that is clipped by a rounded rect clip item.
   1367        let rect = LayoutRect::from_origin_and_size(LayoutPoint::new(100., 100.), LayoutSize::new(100., 100.));
   1368        let temp_clip_id = builder.define_clip_rounded_rect(
   1369            space_and_clip.spatial_id,
   1370            make_rounded_complex_clip(&rect, 20.),
   1371        );
   1372        let clip_chain_id = builder.define_clip_chain(None, vec![temp_clip_id]);
   1373        builder.push_hit_test(
   1374            rect,
   1375            clip_chain_id,
   1376            space_and_clip.spatial_id,
   1377            PrimitiveFlags::default(),
   1378            (0, 4),
   1379        );
   1380 
   1381        // Add a rectangle that is clipped by a ClipChain containing a rounded rect.
   1382        let rect = LayoutRect::from_origin_and_size(LayoutPoint::new(200., 100.), LayoutSize::new(100., 100.));
   1383        let clip_id = builder.define_clip_rounded_rect(
   1384            space_and_clip.spatial_id,
   1385            make_rounded_complex_clip(&rect, 20.),
   1386        );
   1387        let clip_chain_id = builder.define_clip_chain(None, vec![clip_id]);
   1388        builder.push_hit_test(
   1389            rect,
   1390            clip_chain_id,
   1391            space_and_clip.spatial_id,
   1392            PrimitiveFlags::default(),
   1393            (0, 5),
   1394        );
   1395 
   1396        let mut epoch = Epoch(0);
   1397        let txn = Transaction::new();
   1398        self.submit_dl(&mut epoch, builder, txn);
   1399 
   1400        // We render to ensure that the hit tester is up to date with the current scene.
   1401        self.rx.recv().unwrap();
   1402        self.wrench.render();
   1403 
   1404        let hit_test = |point: WorldPoint| -> HitTestResult {
   1405            self.wrench.api.hit_test(
   1406                self.wrench.document_id,
   1407                point,
   1408            )
   1409        };
   1410 
   1411        let assert_hit_test = |point: WorldPoint, tags: Vec<ItemTag>| {
   1412            let result = hit_test(point);
   1413            assert_eq!(result.items.len(), tags.len());
   1414 
   1415            for (hit_test_item, item_b) in result.items.iter().zip(tags.iter()) {
   1416                assert_eq!(hit_test_item.tag, *item_b);
   1417            }
   1418        };
   1419 
   1420        // We should not have any hits outside the boundaries of the scene.
   1421        assert_hit_test(WorldPoint::new(-10., -10.), Vec::new());
   1422        assert_hit_test(WorldPoint::new(-10., 10.), Vec::new());
   1423        assert_hit_test(WorldPoint::new(450., 450.), Vec::new());
   1424        assert_hit_test(WorldPoint::new(100., 450.), Vec::new());
   1425 
   1426        // The top left corner of the scene should only contain the background.
   1427        assert_hit_test(WorldPoint::new(50., 50.), vec![(0, 1)]);
   1428 
   1429        // The middle of the normal rectangle should be hit.
   1430        assert_hit_test(WorldPoint::new(150., 50.), vec![(0, 2), (0, 1)]);
   1431 
   1432        let test_rounded_rectangle = |point: WorldPoint, size: WorldSize, tag: ItemTag| {
   1433            // The cut out corners of the rounded rectangle should not be hit.
   1434            let top_left = point + WorldVector2D::new(5., 5.);
   1435            let bottom_right = point + size.to_vector() - WorldVector2D::new(5., 5.);
   1436 
   1437            assert_hit_test(
   1438                WorldPoint::new(point.x + (size.width / 2.), point.y + (size.height / 2.)),
   1439                vec![tag, (0, 1)]
   1440            );
   1441 
   1442            assert_hit_test(top_left, vec![(0, 1)]);
   1443            assert_hit_test(WorldPoint::new(bottom_right.x, top_left.y), vec![(0, 1)]);
   1444            assert_hit_test(WorldPoint::new(top_left.x, bottom_right.y), vec![(0, 1)]);
   1445            assert_hit_test(bottom_right, vec![(0, 1)]);
   1446        };
   1447 
   1448        test_rounded_rectangle(WorldPoint::new(100., 100.), WorldSize::new(100., 100.), (0, 4));
   1449        test_rounded_rectangle(WorldPoint::new(200., 100.), WorldSize::new(100., 100.), (0, 5));
   1450    }
   1451 
   1452    fn test_clear_cache(&mut self) {
   1453        println!("\tclear cache test...");
   1454 
   1455        self.wrench.api.send_message(ApiMsg::DebugCommand(DebugCommand::ClearCaches(ClearCache::all())));
   1456 
   1457        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
   1458        builder.begin();
   1459 
   1460        let txn = Transaction::new();
   1461        let mut epoch = Epoch(0);
   1462        self.submit_dl(&mut epoch, builder, txn);
   1463 
   1464        self.rx.recv().unwrap();
   1465        self.wrench.render();
   1466    }
   1467 }