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 }