Merge mozilla-inbound to mozilla-central. a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Tue, 30 Apr 2019 13:33:07 +0200
changeset 530711 83950e03831430c0a00db8b83890d51aad0ff43c
parent 530700 ee0dd3b092d03d28da65f5ee686db942b9ce8ec0 (current diff)
parent 530710 2d94a6f65792aff0b4638dd8a94ed23213c27626 (diff)
child 530712 90234f4c094dcc794df28fdd464793dfe065f943
child 530736 979af2efdbf9b83afb1ee3ea7bcae263c3ffb3a0
child 530891 25e7e6e1239ad796626095f9c80ed19709b791de
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-inbound to mozilla-central. a=merge
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2810,16 +2810,21 @@ dependencies = [
  "size_of_test 0.0.1",
  "smallvec 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "style 0.0.1",
  "style_traits 0.0.1",
  "to_shmem 0.0.1",
 ]
 
 [[package]]
+name = "svg_fmt"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
 name = "syn"
 version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "proc-macro2 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -3340,16 +3345,17 @@ dependencies = [
  "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "plane-split 0.13.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "ron 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
  "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "smallvec 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "svg_fmt 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "thread_profiler 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_api 0.60.0",
  "webrender_build 0.0.1",
  "wr_malloc_size_of 0.0.1",
  "ws 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -3809,16 +3815,17 @@ dependencies = [
 "checksum smallbitvec 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1764fe2b30ee783bfe3b9b37b2649d8d590b3148bb12e0079715d4d5c673562e"
 "checksum smallvec 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "622df2d454c29a4d89b30dc3b27b42d7d90d6b9e587dbf8f67652eb7514da484"
 "checksum stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15132e0e364248108c5e2c02e3ab539be8d6f5d52a01ca9bbf27ed657316f02b"
 "checksum string 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00caf261d6f90f588f8450b8e1230fa0d5be49ee6140fdfbcb55335aff350970"
 "checksum string_cache 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25d70109977172b127fe834e5449e5ab1740b9ba49fa18a2020f509174f25423"
 "checksum string_cache_codegen 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eea1eee654ef80933142157fdad9dd8bc43cf7c74e999e369263496f04ff4da"
 "checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc"
 "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
+"checksum svg_fmt 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c666f0fed8e1e20e057af770af9077d72f3d5a33157b8537c1475dd8ffd6d32b"
 "checksum syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "91b52877572087400e83d24b9178488541e3d535259e04ff17a63df1e5ceff59"
 "checksum syn 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4e4b5274d4a0a3d2749d5c158dc64d3403e60554dc61194648787ada5212473d"
 "checksum syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)" = "66c8865bf5a7cbb662d8b011950060b3c8743dca141b054bf7195b20d314d8e2"
 "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
 "checksum target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1b0ab4982b8945c35cc1c46a83a9094c414f6828a099ce5dcaa8ee2b04642dcb"
 "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
 "checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1"
 "checksum term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b6b55df3198cc93372e85dd2ed817f0e38ce8cc0f22eb32391bfad9c4bf209"
--- a/gfx/wr/Cargo.lock
+++ b/gfx/wr/Cargo.lock
@@ -1388,16 +1388,21 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "strsim"
 version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
+name = "svg_fmt"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
 name = "syn"
 version = "0.15.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "proc-macro2 0.4.25 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -1656,16 +1661,17 @@ dependencies = [
  "png 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "ron 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
  "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "smallvec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "svg_fmt 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "thread_profiler 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_api 0.60.0",
  "webrender_build 0.0.1",
  "wr_malloc_size_of 0.0.1",
  "ws 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -2032,16 +2038,17 @@ dependencies = [
 "checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c"
 "checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d"
 "checksum shared_library 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8254bf098ce4d8d7cc7cc6de438c5488adc5297e5b7ffef88816c0a91bd289c1"
 "checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23"
 "checksum smallvec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "26df3bb03ca5eac2e64192b723d51f56c1b1e0860e7c766281f4598f181acdc8"
 "checksum smithay-client-toolkit 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "428d6c019bb92753be9670367e3f483e4fcef396180a9b59e813b69b20014881"
 "checksum stable_deref_trait 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ffbc596e092fe5f598b12ef46cc03754085ac2f4d8c739ad61c4ae266cc3b3fa"
 "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
+"checksum svg_fmt 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c666f0fed8e1e20e057af770af9077d72f3d5a33157b8537c1475dd8ffd6d32b"
 "checksum syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)" = "66c8865bf5a7cbb662d8b011950060b3c8743dca141b054bf7195b20d314d8e2"
 "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
 "checksum tempfile 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "47776f63b85777d984a50ce49d6b9e58826b6a3766a449fc95bc66cd5663c15b"
 "checksum termcolor 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "adc4587ead41bf016f11af03e55a624c06568b5a19db4e90fde573d805074f83"
 "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
 "checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
 "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
 "checksum thread_profiler 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5920e77802b177479ab5795767fa48e68f61b2f516c2ac0041e2978dd8efe483"
--- a/gfx/wr/webrender/Cargo.toml
+++ b/gfx/wr/webrender/Cargo.toml
@@ -45,16 +45,17 @@ serde_json = { optional = true, version 
 sha2 = "0.8"
 smallvec = "0.6"
 thread_profiler = "0.1.1"
 time = "0.1"
 webrender_api = { version = "0.60.0", path = "../webrender_api" }
 webrender_build = { version = "0.0.1", path = "../webrender_build" }
 wr_malloc_size_of = { version = "0.0.1", path = "../wr_malloc_size_of" }
 ws = { optional = true, version = "0.7.3" }
+svg_fmt = "0.4"
 
 [dependencies.pathfinder_font_renderer]
 git = "https://github.com/pcwalton/pathfinder"
 branch = "webrender"
 optional = true
 # Uncomment to test FreeType on macOS:
 # features = ["freetype"]
 
--- a/gfx/wr/webrender/src/capture.rs
+++ b/gfx/wr/webrender/src/capture.rs
@@ -35,28 +35,32 @@ impl CaptureConfig {
             pretty: ron::ser::PrettyConfig {
                 enumerate_arrays: true,
                 .. ron::ser::PrettyConfig::default()
             },
         }
     }
 
     #[cfg(feature = "capture")]
+    pub fn file_path<P>(&self, name: P, ext: &str) -> PathBuf
+    where P: AsRef<Path> {
+        self.root.join(name).with_extension(ext)
+    }
+
+    #[cfg(feature = "capture")]
     pub fn serialize<T, P>(&self, data: &T, name: P)
     where
         T: serde::Serialize,
         P: AsRef<Path>,
     {
         use std::io::Write;
 
         let ron = ron::ser::to_string_pretty(data, self.pretty.clone())
             .unwrap();
-        let path = self.root
-            .join(name)
-            .with_extension("ron");
+        let path = self.file_path(name, "ron");
         let mut file = File::create(path)
             .unwrap();
         write!(file, "{}\n", ron)
             .unwrap();
     }
 
     #[cfg(feature = "capture")]
     pub fn serialize_tree<T, P>(&self, data: &T, name: P)
--- a/gfx/wr/webrender/src/lib.rs
+++ b/gfx/wr/webrender/src/lib.rs
@@ -65,16 +65,17 @@ extern crate log;
 extern crate malloc_size_of_derive;
 #[cfg(any(feature = "serde"))]
 #[macro_use]
 extern crate serde;
 #[macro_use]
 extern crate thread_profiler;
 
 extern crate wr_malloc_size_of;
+extern crate svg_fmt;
 use wr_malloc_size_of as malloc_size_of;
 
 #[macro_use]
 mod profiler;
 
 mod batch;
 mod border;
 mod box_shadow;
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -1639,16 +1639,17 @@ impl RenderBackend {
     fn save_capture(
         &mut self,
         root: PathBuf,
         bits: CaptureBits,
         profile_counters: &mut BackendProfileCounters,
     ) -> DebugOutput {
         use std::fs;
         use capture::CaptureConfig;
+        use render_task::dump_render_tasks_as_svg;
 
         debug!("capture: saving {:?}", root);
         if !root.is_dir() {
             if let Err(e) = fs::create_dir_all(&root) {
                 panic!("Unable to create capture dir: {:?}", e);
             }
         }
         let config = CaptureConfig::new(root, bits);
@@ -1675,16 +1676,24 @@ impl RenderBackend {
                 // it has `pipeline_epoch_map`,
                 // which may capture necessary details for some cases.
                 let file_name = format!("frame-{}-{}", id.namespace_id.0, id.id);
                 config.serialize(&rendered_document.frame, file_name);
                 let file_name = format!("clip-scroll-{}-{}", id.namespace_id.0, id.id);
                 config.serialize_tree(&doc.clip_scroll_tree, file_name);
                 let file_name = format!("builder-{}-{}", id.namespace_id.0, id.id);
                 config.serialize(doc.frame_builder.as_ref().unwrap(), file_name);
+                let file_name = format!("render-tasks-{}-{}.svg", id.namespace_id.0, id.id);
+                let mut svg_file = fs::File::create(&config.file_path(file_name, "svg"))
+                    .expect("Failed to open the SVG file.");
+                dump_render_tasks_as_svg(
+                    &rendered_document.frame.render_tasks,
+                    &rendered_document.frame.passes,
+                    &mut svg_file
+                ).unwrap();
             }
 
             let data_stores_name = format!("data-stores-{}-{}", id.namespace_id.0, id.id);
             config.serialize(&doc.data_stores, data_stores_name);
         }
 
         debug!("\tscene builder");
         self.scene_tx.send(SceneBuilderRequest::SaveScene(config.clone())).unwrap();
--- a/gfx/wr/webrender/src/render_task.rs
+++ b/gfx/wr/webrender/src/render_task.rs
@@ -29,16 +29,17 @@ use prim_store::line_dec::LineDecoration
 #[cfg(feature = "debugger")]
 use print_tree::{PrintTreePrinter};
 use render_backend::FrameId;
 use resource_cache::{CacheItem, ResourceCache};
 use std::{ops, mem, usize, f32, i32, u32};
 use texture_cache::{TextureCache, TextureCacheHandle, Eviction};
 use tiling::{RenderPass, RenderTargetIndex};
 use tiling::{RenderTargetKind};
+use std::io;
 
 
 const RENDER_TASK_SIZE_SANITY_CHECK: i32 = 16000;
 const FLOATS_PER_RENDER_TASK_INFO: usize = 8;
 pub const MAX_BLUR_STD_DEVIATION: f32 = 4.0;
 pub const MIN_DOWNSCALING_RT_SIZE: i32 = 8;
 
 fn render_task_sanity_check(size: &DeviceIntSize) {
@@ -255,16 +256,24 @@ pub enum RenderTaskLocation {
 impl RenderTaskLocation {
     /// Returns true if this is a dynamic location.
     pub fn is_dynamic(&self) -> bool {
         match *self {
             RenderTaskLocation::Dynamic(..) => true,
             _ => false,
         }
     }
+
+    pub fn size(&self) -> DeviceIntSize {
+        match self {
+            RenderTaskLocation::Fixed(rect) => rect.size,
+            RenderTaskLocation::Dynamic(_, size) => *size,
+            RenderTaskLocation::TextureCache { rect, .. } => rect.size,
+        }
+    }
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct CacheMaskTask {
     pub actual_rect: DeviceIntRect,
     pub root_spatial_node_index: SpatialNodeIndex,
@@ -420,16 +429,35 @@ pub enum RenderTaskKind {
     Readback(DeviceIntRect),
     Scaling(ScalingTask),
     Blit(BlitTask),
     Border(BorderTask),
     LineDecoration(LineDecorationTask),
     Gradient(GradientTask),
 }
 
+impl RenderTaskKind {
+    pub fn as_str(&self) -> &'static str {
+        match *self {
+            RenderTaskKind::Picture(..) => "Picture",
+            RenderTaskKind::CacheMask(..) => "CacheMask",
+            RenderTaskKind::ClipRegion(..) => "ClipRegion",
+            RenderTaskKind::VerticalBlur(..) => "VerticalBlur",
+            RenderTaskKind::HorizontalBlur(..) => "HorizontalBlur",
+            RenderTaskKind::Glyph(..) => "Glyph",
+            RenderTaskKind::Readback(..) => "Readback",
+            RenderTaskKind::Scaling(..) => "Scaling",
+            RenderTaskKind::Blit(..) => "Blit",
+            RenderTaskKind::Border(..) => "Border",
+            RenderTaskKind::LineDecoration(..) => "LineDecoration",
+            RenderTaskKind::Gradient(..) => "Gradient",
+        }
+    }
+}
+
 #[derive(Debug, Copy, Clone, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum ClearMode {
     // Applicable to color and alpha targets.
     Zero,
     One,
     /// This task doesn't care what it is cleared to - it will completely overwrite it.
@@ -1465,8 +1493,190 @@ impl RenderTaskCache {
 // Note: zero-square tasks are prohibited in WR task tree, so
 // we ensure each dimension to be at least the length of 1 after rounding.
 pub fn to_cache_size(size: DeviceSize) -> DeviceIntSize {
     DeviceIntSize::new(
         1.max(size.width.round() as i32),
         1.max(size.height.round() as i32),
     )
 }
+
+// Dump an SVG visualization of the render graph for debugging purposes
+#[allow(dead_code)]
+pub fn dump_render_tasks_as_svg(
+    render_tasks: &RenderTaskTree,
+    passes: &[RenderPass],
+    output: &mut dyn io::Write,
+) -> io::Result<()> {
+    use svg_fmt::*;
+
+    let node_width = 80.0;
+    let node_height = 30.0;
+    let vertical_spacing = 8.0;
+    let horizontal_spacing = 20.0;
+    let margin = 10.0;
+    let text_size = 10.0;
+
+    let mut pass_rects = Vec::new();
+    let mut nodes = vec![None; render_tasks.tasks.len()];
+
+    let mut x = margin;
+    let mut max_y: f32 = 0.0;
+
+    #[derive(Clone)]
+    struct Node {
+        rect: Rectangle,
+        label: Text,
+        size: Text,
+    }
+
+    for pass in passes {
+        let mut layout = VerticalLayout::new(x, margin, node_width);
+
+        for task_id in &pass.tasks {
+            let task_index = task_id.index as usize;
+            let task = &render_tasks.tasks[task_index];
+
+            let rect = layout.push_rectangle(node_height);
+
+            let tx = rect.x + rect.w / 2.0;
+            let ty = rect.y + 10.0;
+
+            let label = text(tx, ty, task.kind.as_str());
+            let size = text(tx, ty + 12.0, format!("{}", task.location.size()));
+
+            nodes[task_index] = Some(Node { rect, label, size });
+
+            layout.advance(vertical_spacing);
+        }
+
+        pass_rects.push(layout.total_rectangle());
+
+        x += node_width + horizontal_spacing;
+        max_y = max_y.max(layout.y + margin);
+    }
+
+    let mut links = Vec::new();
+    for node_index in 0..nodes.len() {
+        if nodes[node_index].is_none() {
+            continue;
+        }
+
+        let task = &render_tasks.tasks[node_index];
+        for dep in &task.children {
+            let dep_index = dep.index as usize;
+
+            if let (&Some(ref node), &Some(ref dep_node)) = (&nodes[node_index], &nodes[dep_index]) {
+                links.push((
+                    dep_node.rect.x + dep_node.rect.w,
+                    dep_node.rect.y + dep_node.rect.h / 2.0,
+                    node.rect.x,
+                    node.rect.y + node.rect.h / 2.0,
+                ));
+            }
+        }
+    }
+
+    let svg_w = x + margin;
+    let svg_h = max_y + margin;
+    writeln!(output, "{}", BeginSvg { w: svg_w, h: svg_h })?;
+
+    // Background.
+    writeln!(output,
+        "    {}",
+        rectangle(0.0, 0.0, svg_w, svg_h)
+            .inflate(1.0, 1.0)
+            .fill(rgb(50, 50, 50))
+    )?;
+
+    // Passes.
+    for rect in pass_rects {
+        writeln!(output,
+            "    {}",
+            rect.inflate(3.0, 3.0)
+                .border_radius(4.0)
+                .opacity(0.4)
+                .fill(black())
+        )?;
+    }
+
+    // Links.
+    for (x1, y1, x2, y2) in links {
+        dump_task_dependency_link(output, x1, y1, x2, y2);
+    }
+
+    // Tasks.
+    for node in &nodes {
+        if let Some(node) = node {
+            writeln!(output,
+                "    {}",
+                node.rect
+                    .clone()
+                    .fill(black())
+                    .border_radius(3.0)
+                    .opacity(0.5)
+                    .offset(0.0, 2.0)
+            )?;
+            writeln!(output,
+                "    {}",
+                node.rect
+                    .clone()
+                    .fill(rgb(200, 200, 200))
+                    .border_radius(3.0)
+                    .opacity(0.8)
+            )?;
+
+            writeln!(output,
+                "    {}",
+                node.label
+                    .clone()
+                    .size(text_size)
+                    .align(Align::Center)
+                    .color(rgb(50, 50, 50))
+            )?;
+            writeln!(output,
+                "    {}",
+                node.size
+                    .clone()
+                    .size(text_size * 0.7)
+                    .align(Align::Center)
+                    .color(rgb(50, 50, 50))
+            )?;
+        }
+    }
+
+    writeln!(output, "{}", EndSvg)
+}
+
+#[allow(dead_code)]
+fn dump_task_dependency_link(
+    output: &mut io::Write,
+    x1: f32, y1: f32,
+    x2: f32, y2: f32,
+) {
+    use svg_fmt::*;
+
+    // If the link is a straight horizontal line and spans over multiple passes, it
+    // is likely to go straight though unrelated nodes in a way that makes it look like
+    // they are connected, so we bend the line upward a bit to avoid that.
+    let simple_path = (y1 - y2).abs() > 1.0 || (x2 - x1) < 45.0;
+
+    let mid_x = (x1 + x2) / 2.0;
+    if simple_path {
+        write!(output, "    {}",
+            path().move_to(x1, y1)
+                .cubic_bezier_to(mid_x, y1, mid_x, y2, x2, y2)
+                .fill(Fill::None)
+                .stroke(Stroke::Color(rgb(100, 100, 100), 3.0))
+        ).unwrap();
+    } else {
+        let ctrl1_x = (mid_x + x1) / 2.0;
+        let ctrl2_x = (mid_x + x2) / 2.0;
+        let ctrl_y = y1 - 25.0;
+        write!(output, "    {}",
+            path().move_to(x1, y1)
+                .cubic_bezier_to(ctrl1_x, y1, ctrl1_x, ctrl_y, mid_x, ctrl_y)
+                .cubic_bezier_to(ctrl2_x, ctrl_y, ctrl2_x, y2, x2, y2)
+                .fill(Fill::None)
+                .stroke(Stroke::Color(rgb(100, 100, 100), 3.0))
+        ).unwrap();
+    }
+}
--- a/gfx/wr/webrender/src/tiling.rs
+++ b/gfx/wr/webrender/src/tiling.rs
@@ -907,17 +907,17 @@ pub enum RenderPassKind {
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderPass {
     /// The kind of pass, as well as the set of targets associated with that
     /// kind of pass.
     pub kind: RenderPassKind,
     /// The set of tasks to be performed in this pass, as indices into the
     /// `RenderTaskTree`.
-    tasks: Vec<RenderTaskId>,
+    pub tasks: Vec<RenderTaskId>,
 }
 
 impl RenderPass {
     /// Creates a pass for the main framebuffer. There is only one of these, and
     /// it is always the last pass.
     pub fn new_main_framebuffer(
         screen_size: DeviceIntSize,
         gpu_supports_fast_clears: bool,
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -1659,43 +1659,38 @@ class ScriptSource::LoadSourceMatcher {
   bool* const loaded_;
 
  public:
   explicit LoadSourceMatcher(JSContext* cx, ScriptSource* ss, bool* loaded)
       : cx_(cx), ss_(ss), loaded_(loaded) {}
 
   template <typename Unit>
   bool operator()(const Compressed<Unit>&) const {
-    return sourceAlreadyLoaded();
+    *loaded_ = true;
+    return true;
   }
 
   template <typename Unit>
   bool operator()(const Uncompressed<Unit>&) const {
-    return sourceAlreadyLoaded();
+    *loaded_ = true;
+    return true;
   }
 
   template <typename Unit>
   bool operator()(const Retrievable<Unit>&) {
-    // Establish the default outcome first.
-    *loaded_ = false;
-
     MOZ_ASSERT(ss_->sourceRetrievable(),
                "should be retrievable if Retrievable");
 
     if (!cx_->runtime()->sourceHook.ref()) {
+      *loaded_ = false;
       return true;
     }
 
-    // The argument here is just for overloading -- its value doesn't matter.
-    if (!tryLoadAndSetSource(Unit('0'))) {
-      return false;
-    }
-
-    *loaded_ = true;
-    return true;
+    // The argument is just for overloading -- its value doesn't matter.
+    return tryLoadAndSetSource(Unit('0'));
   }
 
   bool operator()(const Missing&) const {
     MOZ_ASSERT(!ss_->sourceRetrievable(),
                "should have Retrievable<Unit> source, not Missing source, if "
                "retrievable");
     *loaded_ = false;
     return true;
@@ -1703,41 +1698,60 @@ class ScriptSource::LoadSourceMatcher {
 
   bool operator()(const BinAST&) const {
     MOZ_ASSERT(!ss_->sourceRetrievable(), "binast source is never retrievable");
     *loaded_ = false;
     return true;
   }
 
  private:
-  bool sourceAlreadyLoaded() const {
-    *loaded_ = true;
-    return true;
-  }
-
   bool tryLoadAndSetSource(const Utf8Unit&) const {
     char* utf8Source;
     size_t length;
-    return cx_->runtime()->sourceHook->load(cx_, ss_->filename(), nullptr,
-                                            &utf8Source, &length) &&
-           utf8Source &&
-           ss_->setRetrievedSource(
-               cx_,
-               EntryUnits<Utf8Unit>(reinterpret_cast<Utf8Unit*>(utf8Source)),
-               length);
+    if (!cx_->runtime()->sourceHook->load(cx_, ss_->filename(), nullptr,
+                                          &utf8Source, &length)) {
+      return false;
+    }
+
+    if (!utf8Source) {
+      *loaded_ = false;
+      return true;
+    }
+
+    if (!ss_->setRetrievedSource(
+             cx_,
+             EntryUnits<Utf8Unit>(reinterpret_cast<Utf8Unit*>(utf8Source)),
+             length)) {
+      return false;
+    }
+
+    *loaded_ = true;
+    return true;
   }
 
   bool tryLoadAndSetSource(const char16_t&) const {
     char16_t* utf16Source;
     size_t length;
-    return cx_->runtime()->sourceHook->load(cx_, ss_->filename(), &utf16Source,
-                                            nullptr, &length) &&
-           utf16Source &&
-           ss_->setRetrievedSource(cx_, EntryUnits<char16_t>(utf16Source),
-                                   length);
+    if (!cx_->runtime()->sourceHook->load(cx_, ss_->filename(), &utf16Source,
+                                          nullptr, &length)) {
+      return false;
+    }
+
+    if (!utf16Source) {
+      *loaded_ = false;
+      return true;
+    }
+
+    if (!ss_->setRetrievedSource(cx_, EntryUnits<char16_t>(utf16Source),
+                                 length)) {
+      return false;
+    }
+
+    *loaded_ = true;
+    return true;
   }
 };
 
 /* static */
 bool ScriptSource::loadSource(JSContext* cx, ScriptSource* ss, bool* loaded) {
   return ss->data.match(LoadSourceMatcher(cx, ss, loaded));
 }
 
@@ -2623,16 +2637,230 @@ XDRResult ScriptSource::xdrUncompressedS
   }
 
   SourceEncoder<char16_t> encoder(xdr, this, uncompressedLength);
   return encoder.encode();
 }
 
 }  // namespace js
 
+template <typename Unit, XDRMode mode>
+/* static */
+XDRResult ScriptSource::codeUncompressedData(XDRState<mode>* const xdr,
+                                             ScriptSource* const ss,
+                                             bool retrievable) {
+  static_assert(std::is_same<Unit, Utf8Unit>::value ||
+                    std::is_same<Unit, char16_t>::value,
+                "should handle UTF-8 and UTF-16");
+
+  if (mode == XDR_ENCODE) {
+    MOZ_ASSERT(ss->data.is<Uncompressed<Unit>>());
+  }
+
+  MOZ_ASSERT(retrievable == ss->sourceRetrievable());
+
+  if (retrievable) {
+    // It's unnecessary to code uncompressed data if it can just be retrieved
+    // using the source hook.
+    if (mode == XDR_DECODE) {
+      ss->data = SourceType(Retrievable<Unit>());
+    }
+    return Ok();
+  }
+
+  uint32_t uncompressedLength;
+  if (mode == XDR_ENCODE) {
+    uncompressedLength = ss->data.as<Uncompressed<Unit>>().length();
+  }
+  MOZ_TRY(xdr->codeUint32(&uncompressedLength));
+
+  return ss->xdrUncompressedSource(xdr, sizeof(Unit), uncompressedLength);
+}
+
+template <typename Unit, XDRMode mode>
+/* static */
+XDRResult ScriptSource::codeCompressedData(XDRState<mode>* const xdr,
+                                           ScriptSource* const ss,
+                                           bool retrievable) {
+  static_assert(std::is_same<Unit, Utf8Unit>::value ||
+                    std::is_same<Unit, char16_t>::value,
+                "should handle UTF-8 and UTF-16");
+
+  if (mode == XDR_ENCODE) {
+    MOZ_ASSERT(ss->data.is<Compressed<Unit>>());
+  }
+
+  MOZ_ASSERT(retrievable == ss->sourceRetrievable());
+
+  if (retrievable) {
+    // It's unnecessary to code compressed data if it can just be retrieved
+    // using the source hook.
+    if (mode == XDR_DECODE) {
+      ss->data = SourceType(Retrievable<Unit>());
+    }
+    return Ok();
+  }
+
+  uint32_t uncompressedLength;
+  if (mode == XDR_ENCODE) {
+    uncompressedLength = ss->data.as<Compressed<Unit>>().uncompressedLength;
+  }
+  MOZ_TRY(xdr->codeUint32(&uncompressedLength));
+
+  uint32_t compressedLength;
+  if (mode == XDR_ENCODE) {
+    compressedLength = ss->data.as<Compressed<Unit>>().raw.length();
+  }
+  MOZ_TRY(xdr->codeUint32(&compressedLength));
+
+  if (mode == XDR_DECODE) {
+    // Compressed data is always single-byte chars.
+    auto bytes = xdr->cx()->template make_pod_array<char>(compressedLength);
+    if (!bytes) {
+      return xdr->fail(JS::TranscodeResult_Throw);
+    }
+    MOZ_TRY(xdr->codeBytes(bytes.get(), compressedLength));
+
+    if (!ss->initializeWithCompressedSource<Unit>(xdr->cx(), std::move(bytes),
+                                                  compressedLength,
+                                                  uncompressedLength)) {
+      return xdr->fail(JS::TranscodeResult_Throw);
+    }
+  } else {
+    void* bytes =
+        const_cast<char*>(ss->data.as<Compressed<Unit>>().raw.chars());
+    MOZ_TRY(xdr->codeBytes(bytes, compressedLength));
+  }
+
+  return Ok();
+}
+
+template <XDRMode mode>
+/* static */
+XDRResult ScriptSource::codeBinASTData(XDRState<mode>* const xdr,
+                                       ScriptSource* const ss) {
+#if !defined(JS_BUILD_BINAST)
+  return xdr->fail(JS::TranscodeResult_Throw);
+#else
+  // XDR the length of the BinAST data.
+  uint32_t binASTLength;
+  if (mode == XDR_ENCODE) {
+    binASTLength = ss->data.as<BinAST>().string.length();
+  }
+  MOZ_TRY(xdr->codeUint32(&binASTLength));
+
+  // XDR the BinAST data.
+  UniquePtr<char[], JS::FreePolicy> bytes;
+  if (mode == XDR_DECODE) {
+    bytes =
+        xdr->cx()->template make_pod_array<char>(Max<size_t>(binASTLength, 1));
+    if (!bytes) {
+      return xdr->fail(JS::TranscodeResult_Throw);
+    }
+    MOZ_TRY(xdr->codeBytes(bytes.get(), binASTLength));
+  } else {
+    void* bytes = ss->binASTData();
+    MOZ_TRY(xdr->codeBytes(bytes, binASTLength));
+  }
+
+  // XDR any BinAST metadata.
+  uint8_t hasMetadata;
+  if (mode == XDR_ENCODE) {
+    hasMetadata = ss->binASTMetadata_ != nullptr;
+  }
+  MOZ_TRY(xdr->codeUint8(&hasMetadata));
+
+  UniquePtr<frontend::BinASTSourceMetadata> freshMetadata;
+  if (hasMetadata) {
+    // If we're decoding, we decode into fresh metadata.  If we're encoding,
+    // we encode *from* the stored metadata.
+    auto& binASTMetadata =
+        mode == XDR_DECODE ? freshMetadata : ss->binASTMetadata_;
+
+    uint32_t numBinASTKinds;
+    uint32_t numStrings;
+    if (mode == XDR_ENCODE) {
+      numBinASTKinds = binASTMetadata->numBinASTKinds();
+      numStrings = binASTMetadata->numStrings();
+    }
+    MOZ_TRY(xdr->codeUint32(&numBinASTKinds));
+    MOZ_TRY(xdr->codeUint32(&numStrings));
+
+    if (mode == XDR_DECODE) {
+      // Use calloc, since we're storing this immediately, and filling it
+      // might GC, to avoid marking bogus atoms.
+      void* mem = js_calloc(frontend::BinASTSourceMetadata::totalSize(
+          numBinASTKinds, numStrings));
+      if (!mem) {
+        return xdr->fail(JS::TranscodeResult_Throw);
+      }
+
+      auto metadata =
+          new (mem) frontend::BinASTSourceMetadata(numBinASTKinds, numStrings);
+      binASTMetadata.reset(metadata);
+    }
+
+    frontend::BinASTKind* binASTKindBase = binASTMetadata->binASTKindBase();
+    for (uint32_t i = 0; i < numBinASTKinds; i++) {
+      MOZ_TRY(xdr->codeEnum32(&binASTKindBase[i]));
+    }
+
+    RootedAtom atom(xdr->cx());
+    JSAtom** atomsBase = binASTMetadata->atomsBase();
+    auto slices = binASTMetadata->sliceBase();
+    const char* sourceBase =
+        mode == XDR_ENCODE ? bytes.get() : ss->data.as<BinAST>().string.chars();
+
+    for (uint32_t i = 0; i < numStrings; i++) {
+      uint8_t isNull;
+      if (mode == XDR_ENCODE) {
+        atom = binASTMetadata->getAtom(i);
+        isNull = !atom;
+      }
+      MOZ_TRY(xdr->codeUint8(&isNull));
+      if (isNull) {
+        atom = nullptr;
+      } else {
+        MOZ_TRY(XDRAtom(xdr, &atom));
+      }
+      if (mode == XDR_DECODE) {
+        atomsBase[i] = atom;
+      }
+
+      uint64_t sliceOffset;
+      uint32_t sliceLen;
+      if (mode == XDR_ENCODE) {
+        auto& slice = binASTMetadata->getSlice(i);
+        sliceOffset = slice.begin() - sourceBase;
+        sliceLen = slice.byteLen_;
+      }
+
+      MOZ_TRY(xdr->codeUint64(&sliceOffset));
+      MOZ_TRY(xdr->codeUint32(&sliceLen));
+
+      if (mode == XDR_DECODE) {
+        new (&slices[i]) frontend::BinASTSourceMetadata::CharSlice(
+            sourceBase + sliceOffset, sliceLen);
+      }
+    }
+  }
+
+  if (mode == XDR_DECODE) {
+    if (!ss->initializeBinAST(xdr->cx(), std::move(bytes), binASTLength,
+                              std::move(freshMetadata))) {
+      return xdr->fail(JS::TranscodeResult_Throw);
+    }
+  } else {
+    MOZ_ASSERT(freshMetadata == nullptr);
+  }
+
+  return Ok();
+#endif  // !defined(JS_BUILD_BINAST)
+}
+
 template <XDRMode mode>
 /* static */
 XDRResult ScriptSource::xdrData(XDRState<mode>* const xdr,
                                 ScriptSource* const ss) {
   // Retrievability is kept outside |ScriptSource::data| (and not solely as
   // distinct variant types within it) because retrievable compressed or
   // uncompressed data need not be XDR'd.
   uint8_t retrievable;
@@ -2698,241 +2926,28 @@ XDRResult ScriptSource::xdrData(XDRState
       // Fail in debug, but only soft-fail in release, if the type is invalid.
       MOZ_ASSERT_UNREACHABLE("bad tag");
       return xdr->fail(JS::TranscodeResult_Failure_BadDecode);
     }
 
     tag = static_cast<DataType>(type);
   }
 
-  auto CodeCompressedData = [xdr, ss, &retrievable](auto unit) -> XDRResult {
-    using Unit = decltype(unit);
-
-    static_assert(std::is_same<Unit, Utf8Unit>::value ||
-                      std::is_same<Unit, char16_t>::value,
-                  "should handle UTF-8 and UTF-16");
-
-    if (mode == XDR_ENCODE) {
-      MOZ_ASSERT(ss->data.is<Compressed<Unit>>());
-    }
-
-    if (retrievable) {
-      // It's unnecessary to code compressed data if it can just be retrieved
-      // using the source hook.
-      if (mode == XDR_DECODE) {
-        ss->data = SourceType(Retrievable<Unit>());
-      }
-      return Ok();
-    }
-
-    uint32_t uncompressedLength = 0;
-    if (mode == XDR_ENCODE) {
-      uncompressedLength = ss->data.as<Compressed<Unit>>().uncompressedLength;
-    }
-    MOZ_TRY(xdr->codeUint32(&uncompressedLength));
-
-    uint32_t compressedLength;
-    if (mode == XDR_ENCODE) {
-      compressedLength = ss->data.as<Compressed<Unit>>().raw.length();
-    }
-    MOZ_TRY(xdr->codeUint32(&compressedLength));
-
-    if (mode == XDR_DECODE) {
-      // Compressed data is always single-byte chars.
-      auto bytes = xdr->cx()->template make_pod_array<char>(compressedLength);
-      if (!bytes) {
-        return xdr->fail(JS::TranscodeResult_Throw);
-      }
-      MOZ_TRY(xdr->codeBytes(bytes.get(), compressedLength));
-
-      if (!ss->initializeWithCompressedSource<Unit>(xdr->cx(), std::move(bytes),
-                                                    compressedLength,
-                                                    uncompressedLength)) {
-        return xdr->fail(JS::TranscodeResult_Throw);
-      }
-    } else {
-      void* bytes =
-          const_cast<char*>(ss->data.as<Compressed<Unit>>().raw.chars());
-      MOZ_TRY(xdr->codeBytes(bytes, compressedLength));
-    }
-
-    return Ok();
-  };
-
-  auto CodeUncompressedData = [xdr, ss, &retrievable](auto unit) -> XDRResult {
-    using Unit = decltype(unit);
-
-    static_assert(std::is_same<Unit, Utf8Unit>::value ||
-                      std::is_same<Unit, char16_t>::value,
-                  "should handle UTF-8 and UTF-16");
-
-    if (mode == XDR_ENCODE) {
-      MOZ_ASSERT(ss->data.is<Uncompressed<Unit>>());
-    }
-
-    if (retrievable) {
-      // It's unnecessary to code uncompressed data if it can just be retrieved
-      // using the source hook.
-      if (mode == XDR_DECODE) {
-        ss->data = SourceType(Retrievable<Unit>());
-      }
-      return Ok();
-    }
-
-    uint32_t uncompressedLength = 0;
-    if (mode == XDR_ENCODE) {
-      uncompressedLength = ss->data.as<Uncompressed<Unit>>().length();
-    }
-    MOZ_TRY(xdr->codeUint32(&uncompressedLength));
-
-    return ss->xdrUncompressedSource(xdr, sizeof(Unit), uncompressedLength);
-  };
-
-  auto CodeBinASTData = [xdr
-#if defined(JS_BUILD_BINAST)
-                         ,
-                         ss
-#endif
-  ]() -> XDRResult {
-#if !defined(JS_BUILD_BINAST)
-    return xdr->fail(JS::TranscodeResult_Throw);
-#else
-    // XDR the length of the BinAST data.
-    uint32_t binASTLength;
-    if (mode == XDR_ENCODE) {
-      binASTLength = ss->data.as<BinAST>().string.length();
-    }
-    MOZ_TRY(xdr->codeUint32(&binASTLength));
-
-    // XDR the BinAST data.
-    UniquePtr<char[], JS::FreePolicy> bytes;
-    if (mode == XDR_DECODE) {
-      bytes = xdr->cx()->template make_pod_array<char>(
-          Max<size_t>(binASTLength, 1));
-      if (!bytes) {
-        return xdr->fail(JS::TranscodeResult_Throw);
-      }
-      MOZ_TRY(xdr->codeBytes(bytes.get(), binASTLength));
-    } else {
-      void* bytes = ss->binASTData();
-      MOZ_TRY(xdr->codeBytes(bytes, binASTLength));
-    }
-
-    // XDR any BinAST metadata.
-    uint8_t hasMetadata;
-    if (mode == XDR_ENCODE) {
-      hasMetadata = ss->binASTMetadata_ != nullptr;
-    }
-    MOZ_TRY(xdr->codeUint8(&hasMetadata));
-
-    UniquePtr<frontend::BinASTSourceMetadata> freshMetadata;
-    if (hasMetadata) {
-      // If we're decoding, we decode into fresh metadata.  If we're encoding,
-      // we encode *from* the stored metadata.
-      auto& binASTMetadata =
-          mode == XDR_DECODE ? freshMetadata : ss->binASTMetadata_;
-
-      uint32_t numBinASTKinds;
-      uint32_t numStrings;
-      if (mode == XDR_ENCODE) {
-        numBinASTKinds = binASTMetadata->numBinASTKinds();
-        numStrings = binASTMetadata->numStrings();
-      }
-      MOZ_TRY(xdr->codeUint32(&numBinASTKinds));
-      MOZ_TRY(xdr->codeUint32(&numStrings));
-
-      if (mode == XDR_DECODE) {
-        // Use calloc, since we're storing this immediately, and filling it
-        // might GC, to avoid marking bogus atoms.
-        void* mem = js_calloc(frontend::BinASTSourceMetadata::totalSize(
-            numBinASTKinds, numStrings));
-        if (!mem) {
-          return xdr->fail(JS::TranscodeResult_Throw);
-        }
-
-        auto metadata = new (mem)
-            frontend::BinASTSourceMetadata(numBinASTKinds, numStrings);
-        binASTMetadata.reset(metadata);
-      }
-
-      frontend::BinASTKind* binASTKindBase = binASTMetadata->binASTKindBase();
-      for (uint32_t i = 0; i < numBinASTKinds; i++) {
-        MOZ_TRY(xdr->codeEnum32(&binASTKindBase[i]));
-      }
-
-      RootedAtom atom(xdr->cx());
-      JSAtom** atomsBase = binASTMetadata->atomsBase();
-      auto slices = binASTMetadata->sliceBase();
-      const char* sourceBase = mode == XDR_ENCODE
-                                   ? bytes.get()
-                                   : ss->data.as<BinAST>().string.chars();
-
-      for (uint32_t i = 0; i < numStrings; i++) {
-        uint8_t isNull;
-        if (mode == XDR_ENCODE) {
-          atom = binASTMetadata->getAtom(i);
-          isNull = !atom;
-        }
-        MOZ_TRY(xdr->codeUint8(&isNull));
-        if (isNull) {
-          atom = nullptr;
-        } else {
-          MOZ_TRY(XDRAtom(xdr, &atom));
-        }
-        if (mode == XDR_DECODE) {
-          atomsBase[i] = atom;
-        }
-
-        uint64_t sliceOffset;
-        uint32_t sliceLen;
-        if (mode == XDR_ENCODE) {
-          auto& slice = binASTMetadata->getSlice(i);
-          sliceOffset = slice.begin() - sourceBase;
-          sliceLen = slice.byteLen_;
-        }
-
-        MOZ_TRY(xdr->codeUint64(&sliceOffset));
-        MOZ_TRY(xdr->codeUint32(&sliceLen));
-
-        if (mode == XDR_DECODE) {
-          new (&slices[i]) frontend::BinASTSourceMetadata::CharSlice(
-              sourceBase + sliceOffset, sliceLen);
-        }
-      }
-    }
-
-    if (mode == XDR_DECODE) {
-      if (!ss->initializeBinAST(xdr->cx(), std::move(bytes), binASTLength,
-                                std::move(freshMetadata))) {
-        return xdr->fail(JS::TranscodeResult_Throw);
-      }
-    } else {
-      MOZ_ASSERT(freshMetadata == nullptr);
-    }
-
-    return Ok();
-#endif  // !defined(JS_BUILD_BINAST)
-  };
-
   switch (tag) {
     case DataType::CompressedUtf8:
-      // The argument here is just for overloading -- its value doesn't matter.
-      return CodeCompressedData(Utf8Unit('0'));
+      return ScriptSource::codeCompressedData<Utf8Unit>(xdr, ss, retrievable);
 
     case DataType::UncompressedUtf8:
-      // The argument here is just for overloading -- its value doesn't matter.
-      return CodeUncompressedData(Utf8Unit('0'));
+      return ScriptSource::codeUncompressedData<Utf8Unit>(xdr, ss, retrievable);
 
     case DataType::CompressedUtf16:
-      // The argument here is just for overloading -- its value doesn't matter.
-      return CodeCompressedData(char16_t('0'));
+      return ScriptSource::codeCompressedData<char16_t>(xdr, ss, retrievable);
 
     case DataType::UncompressedUtf16:
-      // The argument here is just for overloading -- its value doesn't matter.
-      return CodeUncompressedData(char16_t('0'));
+      return ScriptSource::codeUncompressedData<char16_t>(xdr, ss, retrievable);
 
     case DataType::Missing: {
       MOZ_ASSERT(ss->data.is<Missing>(),
                  "ScriptSource::data is initialized as missing, so neither "
                  "encoding nor decoding has to change anything");
 
       // There's no data to XDR for missing source.
       break;
@@ -2950,17 +2965,17 @@ XDRResult ScriptSource::xdrData(XDRState
       if (mode == XDR_DECODE) {
         MOZ_ASSERT(ss->data.is<Missing>());
         ss->data = SourceType(Retrievable<char16_t>());
       }
       return Ok();
     }
 
     case DataType::BinAST:
-      return CodeBinASTData();
+      return codeBinASTData(xdr, ss);
   }
 
   // The range-check on |type| far above ought ensure the above |switch| is
   // exhaustive and all cases will return, but not all compilers understand
   // this.  Make the Missing case break to here so control obviously never flows
   // off the end.
   MOZ_ASSERT(tag == DataType::Missing);
   return Ok();
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -1141,16 +1141,30 @@ class ScriptSource {
   const mozilla::TimeStamp parseEnded() const { return parseEnded_; }
   // Inform `this` source that it has been fully parsed.
   void recordParseEnded() {
     MOZ_ASSERT(parseEnded_.IsNull());
     parseEnded_ = ReallyNow();
   }
 
  private:
+  template <typename Unit, XDRMode mode>
+  static MOZ_MUST_USE XDRResult codeUncompressedData(XDRState<mode>* const xdr,
+                                                     ScriptSource* const ss,
+                                                     bool retrievable);
+
+  template <typename Unit, XDRMode mode>
+  static MOZ_MUST_USE XDRResult codeCompressedData(XDRState<mode>* const xdr,
+                                                   ScriptSource* const ss,
+                                                   bool retrievable);
+
+  template <XDRMode mode>
+  static MOZ_MUST_USE XDRResult codeBinASTData(XDRState<mode>* const xdr,
+                                               ScriptSource* const ss);
+
   template <XDRMode mode>
   static MOZ_MUST_USE XDRResult xdrData(XDRState<mode>* const xdr,
                                         ScriptSource* const ss);
 
  public:
   template <XDRMode mode>
   static MOZ_MUST_USE XDRResult
   XDR(XDRState<mode>* xdr, const mozilla::Maybe<JS::CompileOptions>& options,
new file mode 100644
--- /dev/null
+++ b/third_party/rust/svg_fmt/.cargo-checksum.json
@@ -0,0 +1,1 @@
+{"files":{"Cargo.toml":"ceb0db86bf7dc037a5f1c60d721e011c997af4d0b3039969eebf4ec7963ef1d8","README.md":"efffe3ac4d3b72a3786c842724f78721f65047c2420a51362355b73e6c5f879d","src/layout.rs":"dc1378e2911912b59e826b31e30306ecc47cf2f12bada184c7c73a21c3a15acf","src/lib.rs":"983b5686a05b093bf405418fe42eb662f72deb9f05c30c6ea122c477683ad405","src/svg.rs":"6c04d9bcee8a2e5724c6e3ec77526b52433529270e627bd8e693224c197923e2"},"package":"c666f0fed8e1e20e057af770af9077d72f3d5a33157b8537c1475dd8ffd6d32b"}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/third_party/rust/svg_fmt/Cargo.toml
@@ -0,0 +1,24 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies
+#
+# If you believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+edition = "2018"
+name = "svg_fmt"
+version = "0.4.0"
+authors = ["Nicolas Silva <nical@fastmail.com>"]
+description = "Very simple debugging utilities to dump shapes in SVG format."
+documentation = "https://docs.rs/svg_fmt/"
+keywords = ["2d", "graphics", "svg"]
+license = "MIT/Apache-2.0"
+repository = "https://github.com/nical/rust_debug"
+
+[dependencies]
new file mode 100644
--- /dev/null
+++ b/third_party/rust/svg_fmt/README.md
@@ -0,0 +1,28 @@
+# svg_fmt
+
+A set of simple types using `Display` formatters `{}` to easily write in the SVG format.
+This can be useful to dump information in a visual way when debugging.
+
+The crate is very small (and has no dependency).
+
+## Example
+
+```rust
+use svg_fmt::*;
+
+println!("{}", BeginSvg { w: 800.0, h: 600.0 });
+println!("    {}",
+    rectangle(20.0, 50.0, 200.0, 100.0)
+        .fill(Fill::Color(red()))
+        .stroke(Stroke::Color(black(), 3.0))
+        .border_radius(5.0)
+);
+println!("    {}",
+    text(25.0, 100.0, "Hi!")
+        .size(42.0)
+        .color(white())
+);
+println!("{}", EndSvg);
+
+```
+
new file mode 100644
--- /dev/null
+++ b/third_party/rust/svg_fmt/src/layout.rs
@@ -0,0 +1,43 @@
+use crate::svg::{Rectangle, rectangle};
+
+#[derive(Copy, Clone, Debug)]
+pub struct VerticalLayout {
+    pub x: f32,
+    pub y: f32,
+    pub start_y: f32,
+    pub width: f32,
+}
+
+impl VerticalLayout {
+    pub fn new(x: f32, y: f32, width: f32) -> Self {
+        VerticalLayout {
+            x,
+            y,
+            start_y: y,
+            width,
+        }
+    }
+
+    pub fn advance(&mut self, by: f32) {
+        self.y += by;
+    }
+
+    pub fn push_rectangle(&mut self, height: f32) -> Rectangle {
+        let rect = rectangle(self.x, self.y, self.width, height);
+
+        self.y += height;
+
+        rect
+    }
+
+    pub fn total_rectangle(&self) -> Rectangle {
+        rectangle(
+            self.x, self.start_y,
+            self.width, self.y,
+        )
+    }
+
+    pub fn start_here(&mut self) {
+        self.start_y = self.y;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/svg_fmt/src/lib.rs
@@ -0,0 +1,5 @@
+mod svg;
+mod layout;
+
+pub use svg::*;
+pub use layout::*;
new file mode 100644
--- /dev/null
+++ b/third_party/rust/svg_fmt/src/svg.rs
@@ -0,0 +1,586 @@
+use std::fmt;
+
+/// `rgb({r},{g},{b})`
+#[derive(Copy, Clone, PartialEq)]
+pub struct Color {
+    pub r: u8,
+    pub g: u8,
+    pub b: u8,
+}
+
+impl fmt::Display for Color {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "rgb({},{},{})", self.r, self.g, self.b)
+    }
+}
+
+pub fn rgb(r: u8, g: u8, b: u8) -> Color { Color { r, g, b } }
+pub fn black() -> Color { rgb(0, 0, 0) }
+pub fn white() -> Color { rgb(255, 255, 255) }
+pub fn red() -> Color { rgb(255, 0, 0) }
+pub fn green() -> Color { rgb(0, 255, 0) }
+pub fn blue() -> Color { rgb(0, 0, 255) }
+
+/// `fill:{self}`
+#[derive(Copy, Clone, PartialEq)]
+pub enum Fill {
+    Color(Color),
+    None,
+}
+
+/// `stroke:{self}`
+#[derive(Copy, Clone, PartialEq)]
+pub enum Stroke {
+    Color(Color, f32),
+    None,
+}
+
+/// `fill:{fill};stroke:{stroke};fill-opacity:{opacity};`
+#[derive(Copy, Clone, PartialEq)]
+pub struct Style {
+    pub fill: Fill,
+    pub stroke: Stroke,
+    pub opacity: f32,
+}
+
+impl fmt::Display for Style {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{};{};fill-opacity:{};",
+            self.fill,
+            self.stroke,
+            self.opacity,
+        )
+    }
+}
+
+impl Style {
+    pub fn default() -> Self {
+        Style {
+            fill: Fill::Color(black()),
+            stroke: Stroke::None,
+            opacity: 1.0,
+        }
+    }
+}
+
+impl fmt::Display for Fill {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Fill::Color(color) => write!(f, "fill:{}", color),
+            Fill::None => write!(f, "fill:none"),
+        }
+    }
+}
+
+impl fmt::Display for Stroke {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Stroke::Color(color, radius) => write!(f, "stroke:{};stroke-width:{}", color, radius),
+            Stroke::None => write!(f, "stroke:none"),
+        }
+    }
+}
+
+impl Into<Fill> for Color {
+    fn into(self) -> Fill {
+        Fill::Color(self)
+    }
+}
+
+impl Into<Stroke> for Color {
+    fn into(self) -> Stroke {
+        Stroke::Color(self, 1.0)
+    }
+}
+
+/// `<rect x="{x}" y="{y}" width="{w}" height="{h}" ... />`,
+#[derive(Copy, Clone, PartialEq)]
+pub struct Rectangle {
+    pub x: f32,
+    pub y: f32,
+    pub w: f32,
+    pub h: f32,
+    pub style: Style,
+    pub border_radius: f32,
+}
+
+pub fn rectangle(x: f32, y: f32, w: f32, h: f32) -> Rectangle {
+    Rectangle {
+        x, y, w, h,
+        style: Style::default(),
+        border_radius: 0.0,
+    }
+}
+
+impl Rectangle {
+    pub fn fill<F>(mut self, fill: F) -> Self
+    where F: Into<Fill> {
+        self.style.fill = fill.into();
+        self
+    }
+
+    pub fn stroke<S>(mut self, stroke: S) -> Self
+    where S: Into<Stroke> {
+        self.style.stroke = stroke.into();
+        self
+    }
+
+    pub fn opacity(mut self, opacity: f32) -> Self {
+        self.style.opacity = opacity;
+        self
+    }
+
+    pub fn style(mut self, style: Style) -> Self {
+        self.style = style;
+        self
+    }
+
+    pub fn border_radius(mut self, r: f32) -> Self {
+        self.border_radius = r;
+        self
+    }
+
+    pub fn offset(mut self, dx: f32, dy: f32) -> Self {
+        self.x += dx;
+        self.y += dy;
+        self
+    }
+
+    pub fn inflate(mut self, dx: f32, dy: f32) -> Self {
+        self.x -= dx;
+        self.y -= dy;
+        self.w += 2.0 * dx;
+        self.h += 2.0 * dy;
+        self
+    }
+}
+
+impl fmt::Display for Rectangle {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f,
+            r#"<rect x="{}" y="{}" width="{}" height="{}" ry="{}" style="{}" />""#,
+            self.x, self.y, self.w, self.h,
+            self.border_radius,
+            self.style,
+        )
+    }
+}
+
+/// `<circle cx="{x}" cy="{y}" r="{radius}" .../>`
+#[derive(Copy, Clone, PartialEq)]
+pub struct Circle {
+    pub x: f32,
+    pub y: f32,
+    pub radius: f32,
+    pub style: Style,
+}
+
+impl Circle {
+    pub fn fill<F>(mut self, fill: F) -> Self
+    where F: Into<Fill> {
+        self.style.fill = fill.into();
+        self
+    }
+
+    pub fn stroke<S>(mut self, stroke: S) -> Self
+    where S: Into<Stroke> {
+        self.style.stroke = stroke.into();
+        self
+    }
+
+    pub fn style(mut self, style: Style) -> Self {
+        self.style = style;
+        self
+    }
+
+    pub fn opacity(mut self, opacity: f32) -> Self {
+        self.style.opacity = opacity;
+        self
+    }
+
+
+    pub fn offset(mut self, dx: f32, dy: f32) -> Self {
+        self.x += dx;
+        self.y += dy;
+        self
+    }
+
+    pub fn inflate(mut self, by: f32) -> Self {
+        self.radius += by;
+        self
+    }
+}
+
+impl fmt::Display for Circle {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f,
+            r#"<circle cx="{}" cy="{}" r="{}" style="{}" />""#,
+            self.x, self.y, self.radius,
+            self.style,
+        )
+    }
+}
+
+/// `<path d="..." style="..."/>`
+#[derive(Clone, PartialEq)]
+pub struct Polygon {
+    pub points: Vec<[f32; 2]>,
+    pub closed: bool,
+    pub style: Style,
+}
+
+impl fmt::Display for Polygon {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, r#"<path d="#)?;
+        if self.points.len() > 0 {
+            write!(f, "M {} {} ", self.points[0][0], self.points[0][1])?;
+            for &p in &self.points[1..] {
+                write!(f, "L {} {} ", p[0], p[1])?;
+            }
+            if self.closed {
+                write!(f, "Z")?;
+            }
+        }
+        write!(f, r#"" style="{}"/>"#, self.style)
+    }
+}
+
+pub fn polygon<T: Copy + Into<[f32; 2]>>(pts: &[T]) ->  Polygon {
+    let mut points = Vec::with_capacity(pts.len());
+    for p in pts {
+        points.push((*p).into());
+    }
+    Polygon {
+        points,
+        closed: true,
+        style: Style::default(),
+    }
+}
+
+pub fn triangle(x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) -> Polygon {
+    polygon(&[[x1, y1], [x2, y2], [x3, y3]])
+}
+
+impl Polygon {
+    pub fn open(mut self) -> Self {
+        self.closed = false;
+        self
+    }
+
+    pub fn fill<F>(mut self, fill: F) -> Self
+    where F: Into<Fill> {
+        self.style.fill = fill.into();
+        self
+    }
+
+    pub fn stroke<S>(mut self, stroke: S) -> Self
+    where S: Into<Stroke> {
+        self.style.stroke = stroke.into();
+        self
+    }
+
+    pub fn opacity(mut self, opacity: f32) -> Self {
+        self.style.opacity = opacity;
+        self
+    }
+
+    pub fn style(mut self, style: Style) -> Self {
+        self.style = style;
+        self
+    }
+}
+
+/// `<path d="M {x1} {y1} L {x2} {y2}" ... />`
+#[derive(Copy, Clone, PartialEq)]
+pub struct LineSegment {
+    pub x1: f32,
+    pub x2: f32,
+    pub y1: f32,
+    pub y2: f32,
+    pub color: Color,
+    pub width: f32,
+}
+
+impl fmt::Display for LineSegment {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f,
+            r#"<path d="M {} {} L {} {}" style="stroke:{};stroke-width:{}"/>"#,
+            self.x1, self.y1,
+            self.x2, self.y2,
+            self.color,
+            self.width,
+        )
+    }
+}
+
+pub fn line_segment(x1: f32, y1: f32, x2: f32, y2: f32) -> LineSegment {
+    LineSegment {
+        x1, y1, x2, y2,
+        color: black(),
+        width: 1.0,
+    }
+}
+
+impl LineSegment {
+    pub fn color(mut self, color: Color) -> Self {
+        self.color = color;
+        self
+    }
+
+    pub fn width(mut self, width: f32) -> Self {
+        self.width = width;
+        self
+    }
+
+    pub fn offset(mut self, dx: f32, dy: f32) -> Self {
+        self.x1 += dx;
+        self.y1 += dy;
+        self.x2 += dx;
+        self.y2 += dy;
+        self
+    }
+}
+
+/// `<path d="..." />`
+#[derive(Clone, PartialEq)]
+pub struct Path {
+    pub ops: Vec<PathOp>,
+    pub style: Style,
+}
+
+/// `M {} {} L {} {} ...`
+#[derive(Copy, Clone, PartialEq)]
+pub enum PathOp {
+    MoveTo { x: f32, y: f32 },
+    LineTo { x: f32, y: f32 },
+    QuadraticTo { ctrl_x: f32, ctrl_y: f32, x: f32, y: f32 },
+    CubicTo { ctrl1_x: f32, ctrl1_y: f32, ctrl2_x: f32, ctrl2_y: f32, x: f32, y: f32 },
+    Close,
+}
+impl fmt::Display for PathOp {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match *self {
+            PathOp::MoveTo { x, y } => write!(f, "M {} {} ", x, y),
+            PathOp::LineTo { x, y } => write!(f, "L {} {} ", x, y),
+            PathOp::QuadraticTo { ctrl_x, ctrl_y, x, y } => write!(f, "Q {} {} {} {} ", ctrl_x, ctrl_y, x, y),
+            PathOp::CubicTo { ctrl1_x, ctrl1_y, ctrl2_x, ctrl2_y, x, y } => write!(f, "C {} {} {} {} {} {} ", ctrl1_x, ctrl1_y, ctrl2_x, ctrl2_y, x, y),
+            PathOp::Close => write!(f, "Z "),
+        }
+    }
+}
+
+impl fmt::Display for Path {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, r#"<path d=""#)?;
+        for op in &self.ops {
+            op.fmt(f)?;
+        }
+        write!(f, r#"" style="{}" />"#, self.style)
+    }
+}
+
+impl Path {
+    pub fn move_to(mut self, x: f32, y: f32) -> Self {
+        self.ops.push(PathOp::MoveTo { x, y });
+        self
+    }
+
+    pub fn line_to(mut self, x: f32, y: f32) -> Self {
+        self.ops.push(PathOp::LineTo { x, y });
+        self
+    }
+
+    pub fn quadratic_bezier_to(
+        mut self,
+        ctrl_x: f32, ctrl_y: f32,
+        x: f32, y: f32,
+    ) -> Self {
+        self.ops.push(PathOp::QuadraticTo { ctrl_x, ctrl_y, x, y });
+        self
+    }
+
+    pub fn cubic_bezier_to(
+        mut self,
+        ctrl1_x: f32, ctrl1_y: f32,
+        ctrl2_x: f32, ctrl2_y: f32,
+        x: f32, y: f32,
+    ) -> Self {
+        self.ops.push(PathOp::CubicTo { ctrl1_x, ctrl1_y, ctrl2_x, ctrl2_y, x, y });
+        self
+    }
+
+    pub fn close(mut self) -> Self {
+        self.ops.push(PathOp::Close);
+        self
+    }
+
+    pub fn fill<F>(mut self, fill: F) -> Self
+    where F: Into<Fill> {
+        self.style.fill = fill.into();
+        self
+    }
+
+    pub fn stroke<S>(mut self, stroke: S) -> Self
+    where S: Into<Stroke> {
+        self.style.stroke = stroke.into();
+        self
+    }
+
+    pub fn opacity(mut self, opacity: f32) -> Self {
+        self.style.opacity = opacity;
+        self
+    }
+
+    pub fn style(mut self, style: Style) -> Self {
+        self.style = style;
+        self
+    }
+}
+
+pub fn path() -> Path {
+    Path {
+        ops: Vec::new(),
+        style: Style::default(),
+    }
+}
+
+/// `<text x="{x}" y="{y}" ... > {text} </text>`
+#[derive(Clone, PartialEq)]
+pub struct Text {
+    pub x: f32, pub y: f32,
+    pub text: String,
+    pub color: Color,
+    pub align: Align,
+    pub size: f32,
+}
+
+impl fmt::Display for Text {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f,
+            r#"<text x="{}" y="{}" style="font-size:{}px;fill:{};{}"> {} </text>"#,
+            self.x, self.y,
+            self.size,
+            self.color,
+            self.align,
+            self.text,
+        )
+    }
+}
+
+pub fn text<T: Into<String>>(x: f32, y: f32, txt: T) -> Text {
+    Text {
+        x, y,
+        text: txt.into(),
+        color: black(),
+        align: Align::Left,
+        size: 10.0,
+    }
+}
+
+impl Text {
+    pub fn color(mut self, color: Color) -> Self {
+        self.color = color;
+        self
+    }
+
+    pub fn size(mut self, size: f32) -> Self {
+        self.size = size;
+        self
+    }
+
+    pub fn align(mut self, align: Align) -> Self {
+        self.align = align;
+        self
+    }
+
+    pub fn offset(mut self, dx: f32, dy: f32) -> Self {
+        self.x += dx;
+        self.y += dy;
+        self
+    }
+}
+
+/// `text-align:{self}`
+#[derive(Copy, Clone, PartialEq)]
+pub enum Align {
+    Left, Right, Center
+}
+
+impl fmt::Display for Align {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match *self {
+            Align::Left => write!(f, "text-anchor:start;text-align:left;"),
+            Align::Right => write!(f, "text-anchor:end;text-align:right;"),
+            Align::Center => write!(f, "text-anchor:middle;text-align:center;"),
+        }
+    }
+}
+
+/// `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {y}">`
+#[derive(Copy, Clone, PartialEq)]
+pub struct BeginSvg {
+    pub w: f32,
+    pub h: f32,
+}
+
+impl fmt::Display for BeginSvg {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f,
+            r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {} {}">"#,
+            self.w,
+            self.h,
+        )
+    }
+}
+
+
+/// `</svg>`
+#[derive(Copy, Clone, PartialEq)]
+pub struct EndSvg;
+
+impl fmt::Display for EndSvg {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "</svg>")
+    }
+}
+
+/// `"    "`
+pub struct Indentation {
+    pub n: u32,
+}
+
+pub fn indent(n: u32) -> Indentation {
+    Indentation { n }
+}
+
+impl Indentation {
+    pub fn push(&mut self) {
+        self.n += 1;
+    }
+
+    pub fn pop(&mut self) {
+        self.n -= 1;
+    }
+}
+
+impl fmt::Display for Indentation {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        for _ in 0..self.n {
+            write!(f, "    ")?;
+        }
+        Ok(())
+    }
+}
+
+#[test]
+fn foo() {
+    println!("{}", BeginSvg { w: 800.0, h: 600.0 });
+    println!("    {}",
+        rectangle(20.0, 50.0, 200.0, 100.0)
+            .fill(red())
+            .stroke(Stroke::Color(black(), 3.0))
+            .border_radius(5.0)
+    );
+    println!("    {}", text(25.0, 100.0, "Foo!").size(42.0).color(white()));
+    println!("{}", EndSvg);
+}