Merge mozilla-central to autoland. CLOSED TREE
authorSebastian Hengst <archaeopteryx@coole-files.de>
Tue, 30 Apr 2019 13:35:58 +0200
changeset 530736 979af2efdbf9b83afb1ee3ea7bcae263c3ffb3a0
parent 530735 b603d0be91d0b0c748860f147e263d75cadcaf7e (current diff)
parent 530711 83950e03831430c0a00db8b83890d51aad0ff43c (diff)
child 530737 e04d7e4e58a12869d43db693b66e1467d758ef39
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)
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-central to autoland. CLOSED TREE
dom/events/EventStateManager.cpp
--- 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/layers/apz/test/mochitest/apz_test_utils.js
+++ b/gfx/layers/apz/test/mochitest/apz_test_utils.js
@@ -14,18 +14,16 @@
 function convertEntries(entries) {
   var result = {};
   for (var i = 0; i < entries.length; ++i) {
     result[entries[i].key] = entries[i].value;
   }
   return result;
 }
 
-// TODO: Clean up these rect-handling functions so that e.g. a rect returned
-//       by Element.getBoundingClientRect() Just Works with them.
 function parseRect(str) {
   var pieces = str.replace(/[()\s]+/g, "").split(",");
   SimpleTest.is(pieces.length, 4, "expected string of form (x,y,w,h)");
   return { x: parseInt(pieces[0]),
            y: parseInt(pieces[1]),
            w: parseInt(pieces[2]),
            h: parseInt(pieces[3]) };
 }
@@ -36,21 +34,16 @@ function rectContains(haystack, needle) 
   return haystack.x <= needle.x
       && haystack.y <= needle.y
       && (haystack.x + haystack.w) >= (needle.x + needle.w)
       && (haystack.y + haystack.h) >= (needle.y + needle.h);
 }
 function rectToString(rect) {
   return "(" + rect.x + "," + rect.y + "," + rect.w + "," + rect.h + ")";
 }
-function assertRectContainment(haystackRect, haystackDesc, needleRect, needleDesc) {
-  SimpleTest.ok(rectContains(haystackRect, needleRect),
-                haystackDesc + " " + rectToString(haystackRect) + " should contain " +
-                needleDesc + " " + rectToString(needleRect));
-}
 
 function getPropertyAsRect(scrollFrames, scrollId, prop) {
   SimpleTest.ok(scrollId in scrollFrames,
                 "expected scroll frame data for scroll id " + scrollId);
   var scrollFrameData = scrollFrames[scrollId];
   SimpleTest.ok("displayport" in scrollFrameData,
                 "expected a " + prop + " for scroll id " + scrollId);
   var value = scrollFrameData[prop];
--- a/gfx/layers/apz/test/mochitest/helper_fixed_pos_displayport.html
+++ b/gfx/layers/apz/test/mochitest/helper_fixed_pos_displayport.html
@@ -68,18 +68,19 @@
       // relative to the layout viewport (but not relative to the page), since
       // fixed-position elements are attached to the layout viewport.
       // This is accomplished by checking the fixed-pos display port contains
       // the visual viewport rect as expressed relative to the layout viewport.
       let vvRect = { x: vv.offsetLeft,  // offsets relative to layout viewport
                      y: vv.offsetTop,
                      w: vv.width,
                      h: vv.height };
-      assertRectContainment(fixedPosDisplayport, "fixed-pos displayport",
-                            vvRect, "visual viewport");
+      ok(rectContains(fixedPosDisplayport, vvRect),
+         "fixed-pos displayport " + rectToString(fixedPosDisplayport) +
+         " should contain visual viewport " + rectToString(vvRect));
     }
 
     function* test(testDriver) {
       // First, check size and position on page load.
       checkFixedPosDisplayport();
 
       // Scroll the visual viewport within the layout viewport, without
       // scrolling the layout viewport itself, and check the size and
--- a/gfx/layers/apz/test/mochitest/helper_scroll_into_view_bug1516056.html
+++ b/gfx/layers/apz/test/mochitest/helper_scroll_into_view_bug1516056.html
@@ -14,54 +14,31 @@
       margin-right: 50%;
       background: cyan;
     }
   </style>
 </head>
 <body>
   <div id="target"></div>
   <script>
-    let vv = window.visualViewport;
     function getVisualScrollRange() {
       let rootScroller = document.scrollingElement;
+      let vv = window.visualViewport;
       return {
         width: rootScroller.scrollWidth - vv.width,
         height: rootScroller.scrollHeight - vv.height,
       };
     }
-    function getVisualViewportRect() {
-      return {
-        x: vv.pageLeft,
-        y: vv.pageTop,
-        w: vv.width,
-        h: vv.height,
-      };
-    }
     function* test(testDriver) {
       SimpleTest.is(window.scrollMaxX, 0, "page should have a zero horizontal layout scroll range");
       SimpleTest.is(window.scrollMaxY, 0, "page should have a zero vertical layout scroll range");
       let visualScrollRange = getVisualScrollRange();
       SimpleTest.ok(visualScrollRange.width > 0, "page should have a nonzero horizontal visual scroll range");
       SimpleTest.ok(visualScrollRange.height > 0, "page should have a nonzero vertical visual scroll range");
       let target = document.getElementById("target");
-
-      // Scroll target element into view. Wait until any visual scrolling is done before doing checks.
-      vv.addEventListener("scroll", testDriver, { once: true });
       target.scrollIntoView();
-      yield; // wait for visual viewport "scroll" event
-      yield waitForApzFlushedRepaints(testDriver);
-
-      // Test that scrollIntoView() respected the layout scroll range.
       SimpleTest.is(window.scrollX, 0, "page should not layout-scroll with a zero layout scroll range");
       SimpleTest.is(window.scrollY, 0, "page should not layout-scroll with a zero layout scroll range");
-
-      // Test that scrollIntoView() did perform visual scrolling.
-      let vvRect = getVisualViewportRect();
-      let targetBounds = target.getBoundingClientRect();
-      // set property names expected by rectContains()
-      targetBounds.w = targetBounds.width;
-      targetBounds.h = targetBounds.height;
-      assertRectContainment(vvRect, "visual viewport", targetBounds, "target element bounding rect");
     }
     waitUntilApzStable().then(runContinuation(test)).then(subtestDone);
   </script>
 </body>
 </html>
--- 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,
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -3323,20 +3323,21 @@ static nscoord ComputeWhereToScroll(Wher
  * This needs to work even if aRect has a width or height of zero.
  *
  * Note that, since we are performing a layout scroll, it's possible that
  * this fnction will sometimes be unsuccessful; the content will move as
  * fast as it can on the screen using layout viewport scrolling, and then
  * stop there, even if it could get closer to the desired position by
  * moving the visual viewport within the layout viewport.
  */
-static void ScrollToShowRect(nsIPresShell* aPresShell,
-                             nsIScrollableFrame* aFrameAsScrollable,
-                             const nsRect& aRect, ScrollAxis aVertical,
-                             ScrollAxis aHorizontal, ScrollFlags aScrollFlags) {
+static void ScrollToShowRect(nsIScrollableFrame* aFrameAsScrollable,
+                             const nsRect& aRect,
+                             nsIPresShell::ScrollAxis aVertical,
+                             nsIPresShell::ScrollAxis aHorizontal,
+                             ScrollFlags aScrollFlags) {
   nsPoint scrollPt = aFrameAsScrollable->GetVisualViewportOffset();
   nsRect visibleRect(scrollPt, aFrameAsScrollable->GetVisualViewportSize());
 
   nsSize lineSize;
   // Don't call GetLineScrollAmount unless we actually need it. Not only
   // does this save time, but it's not safe to call GetLineScrollAmount
   // during reflow (because it depends on font size inflation and doesn't
   // use the in-reflow-safe font-size inflation path). If we did call it,
@@ -3394,25 +3395,16 @@ static void ScrollToShowRect(nsIPresShel
                          autoBehaviorIsSmooth);
     if (gfxPrefs::ScrollBehaviorEnabled() && smoothScroll) {
       scrollMode = ScrollMode::SmoothMsd;
     }
     aFrameAsScrollable->ScrollTo(scrollPt, scrollMode, &allowedRange,
                                  aScrollFlags & ScrollFlags::ScrollSnap
                                      ? nsIScrollbarMediator::ENABLE_SNAP
                                      : nsIScrollbarMediator::DISABLE_SNAP);
-    // If this is the RCD-RSF, also call ScrollToVisual() since we want to
-    // scroll the rect into view visually, and that may require scrolling
-    // the visual viewport in scenarios where there is not enough layout
-    // scroll range.
-    if (aFrameAsScrollable->IsRootScrollFrameOfDocument() &&
-        aPresShell->GetPresContext()->IsRootContentDocument()) {
-      aPresShell->ScrollToVisual(scrollPt, FrameMetrics::eMainThread,
-                                 scrollMode);
-    }
   }
 }
 
 nsresult PresShell::ScrollContentIntoView(nsIContent* aContent,
                                           ScrollAxis aVertical,
                                           ScrollAxis aHorizontal,
                                           ScrollFlags aScrollFlags) {
   NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER);
@@ -3568,18 +3560,17 @@ bool nsIPresShell::ScrollFrameRectIntoVi
 
       targetRect -= sf->GetScrolledFrame()->GetPosition();
       if (!(aScrollFlags & ScrollFlags::IgnoreMarginAndPadding)) {
         nsMargin scrollPadding = sf->GetScrollPadding();
         targetRect.Inflate(scrollPadding);
         targetRect = targetRect.Intersect(sf->GetScrolledRect());
       }
 
-      ScrollToShowRect(this, sf, targetRect, aVertical, aHorizontal,
-                       aScrollFlags);
+      ScrollToShowRect(sf, targetRect, aVertical, aHorizontal, aScrollFlags);
 
       nsPoint newPosition = sf->LastScrollDestination();
       // If the scroll position increased, that means our content moved up,
       // so our rect's offset should decrease
       rect += oldPosition - newPosition;
 
       if (oldPosition != newPosition) {
         didScroll = true;
--- a/security/manager/ssl/RemoteSecuritySettings.jsm
+++ b/security/manager/ssl/RemoteSecuritySettings.jsm
@@ -25,19 +25,17 @@ const INTERMEDIATES_PRELOADED_TELEMETRY 
 const INTERMEDIATES_UPDATE_MS_TELEMETRY  = "INTERMEDIATE_PRELOADING_UPDATE_TIME_MS";
 
 XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
 
 XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => new TextDecoder());
 
 XPCOMUtils.defineLazyGetter(this, "baseAttachmentsURL", async () => {
   const server = Services.prefs.getCharPref("services.settings.server");
-  const serverInfo = await (await fetch(`${server}/`, {
-    credentials: "omit",
-  })).json();
+  const serverInfo = await (await fetch(`${server}/`)).json();
   const {capabilities: {attachments: {base_url}}} = serverInfo;
   return base_url;
 });
 
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let { ConsoleAPI } = ChromeUtils.import("resource://gre/modules/Console.jsm");
   return new ConsoleAPI({
     prefix: "RemoteSecuritySettings.jsm",
@@ -178,20 +176,18 @@ this.RemoteSecuritySettings = class Remo
      * @return {Promise}          resolves to a Uint8Array on success
      */
     async _downloadAttachmentBytes(record) {
       const {attachment: {location}} = record;
       const remoteFilePath = (await baseAttachmentsURL) + location;
       const headers = new Headers();
       headers.set("Accept-Encoding", "gzip");
 
-      return fetch(remoteFilePath, {
-        headers,
-        credentials: "omit",
-      }).then(resp => {
+      return fetch(remoteFilePath, {headers})
+      .then(resp => {
         log.debug(`Download fetch completed: ${resp.ok} ${resp.status}`);
         if (!resp.ok) {
           Cu.reportError(`Failed to fetch ${remoteFilePath}: ${resp.status}`);
 
           Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
             .add("failedToFetch");
 
           return Promise.reject();
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);
+}