diff --git a/DEPS b/DEPS
index c160b90..88c054a5 100644
--- a/DEPS
+++ b/DEPS
@@ -40,11 +40,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '6a1a5f74039a7d3e80e33051bac8f14b092c8671',
+  'skia_revision': '2229b576949a76fddb5bc823accfd70e655dea14',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '6b326c71ca66c4ef4364a59a0e9c7c11bf2e129e',
+  'v8_revision': '36a60914e44cddc57bc81aee75cd6bb56fa8a51d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -64,7 +64,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': 'f9f26b588e0b30fb581097e2a430f5c80f33b692',
+  'pdfium_revision': '350d2d904a3e6bd1e96542c5e223d301d9bdbe6a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -96,7 +96,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'cf5379cbc7095de979bba11519acc126a99532d2',
+  'catapult_revision': 'b5b525957fba7bd8fce6908b02df49f0d7e0992c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
diff --git a/ash/root_window_controller.cc b/ash/root_window_controller.cc
index d10013e..63286ffb 100644
--- a/ash/root_window_controller.cc
+++ b/ash/root_window_controller.cc
@@ -707,12 +707,10 @@
     return;
 
   menu_runner_ = base::MakeUnique<views::MenuRunner>(
-      menu_model_adapter_->CreateMenu(),
-      views::MenuRunner::CONTEXT_MENU | views::MenuRunner::ASYNC);
-  ignore_result(
-      menu_runner_->RunMenuAt(wallpaper_widget_controller()->widget(), nullptr,
-                              gfx::Rect(location_in_screen, gfx::Size()),
-                              views::MENU_ANCHOR_TOPLEFT, source_type));
+      menu_model_adapter_->CreateMenu(), views::MenuRunner::CONTEXT_MENU);
+  menu_runner_->RunMenuAt(wallpaper_widget_controller()->widget(), nullptr,
+                          gfx::Rect(location_in_screen, gfx::Size()),
+                          views::MENU_ANCHOR_TOPLEFT, source_type);
 }
 
 void RootWindowController::UpdateAfterLoginStatusChange(LoginStatus status) {
diff --git a/ash/shelf/shelf_view.cc b/ash/shelf/shelf_view.cc
index 0bf4302..94259de 100644
--- a/ash/shelf/shelf_view.cc
+++ b/ash/shelf/shelf_view.cc
@@ -1649,7 +1649,7 @@
       base::Bind(&ShelfView::OnMenuClosed, base::Unretained(this), ink_drop)));
 
   closing_event_time_ = base::TimeTicks();
-  int run_types = views::MenuRunner::ASYNC;
+  int run_types = 0;
   if (context_menu)
     run_types |= views::MenuRunner::CONTEXT_MENU;
   launcher_menu_runner_.reset(
diff --git a/ash/shell/window_type_launcher.cc b/ash/shell/window_type_launcher.cc
index 564d0d11..ab73191 100644
--- a/ash/shell/window_type_launcher.cc
+++ b/ash/shell/window_type_launcher.cc
@@ -328,14 +328,10 @@
                        base::ASCIIToUTF16("Toggle FullScreen"),
                        MenuItemView::NORMAL);
   // MenuRunner takes ownership of root.
-  menu_runner_.reset(new MenuRunner(root, MenuRunner::HAS_MNEMONICS |
-                                              views::MenuRunner::CONTEXT_MENU |
-                                              views::MenuRunner::ASYNC));
-  if (menu_runner_->RunMenuAt(GetWidget(), NULL, gfx::Rect(point, gfx::Size()),
-                              views::MENU_ANCHOR_TOPLEFT,
-                              source_type) == MenuRunner::MENU_DELETED) {
-    return;
-  }
+  menu_runner_.reset(new MenuRunner(
+      root, MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU));
+  menu_runner_->RunMenuAt(GetWidget(), NULL, gfx::Rect(point, gfx::Size()),
+                          views::MENU_ANCHOR_TOPLEFT, source_type);
 }
 
 }  // namespace shell
diff --git a/ash/shell_unittest.cc b/ash/shell_unittest.cc
index 1a981617..f4a8488 100644
--- a/ash/shell_unittest.cc
+++ b/ash/shell_unittest.cc
@@ -357,14 +357,11 @@
                               ->GetRootWindowController()
                               ->wallpaper_widget_controller()
                               ->widget();
-  std::unique_ptr<views::MenuRunner> menu_runner(new views::MenuRunner(
-      menu_model.get(),
-      views::MenuRunner::CONTEXT_MENU | views::MenuRunner::ASYNC));
+  std::unique_ptr<views::MenuRunner> menu_runner(
+      new views::MenuRunner(menu_model.get(), views::MenuRunner::CONTEXT_MENU));
 
-  EXPECT_EQ(views::MenuRunner::NORMAL_EXIT,
-            menu_runner->RunMenuAt(widget, NULL, gfx::Rect(),
-                                   views::MENU_ANCHOR_TOPLEFT,
-                                   ui::MENU_SOURCE_MOUSE));
+  menu_runner->RunMenuAt(widget, NULL, gfx::Rect(), views::MENU_ANCHOR_TOPLEFT,
+                         ui::MENU_SOURCE_MOUSE);
   LockScreenAndVerifyMenuClosed();
 }
 
diff --git a/base/metrics/histogram.cc b/base/metrics/histogram.cc
index bf22ffc..e1d0df6 100644
--- a/base/metrics/histogram.cc
+++ b/base/metrics/histogram.cc
@@ -275,12 +275,14 @@
     Sample minimum,
     Sample maximum,
     const BucketRanges* ranges,
-    const DelayedPersistentAllocation& counts,
-    const DelayedPersistentAllocation& logged_counts,
+    HistogramBase::AtomicCount* counts,
+    HistogramBase::AtomicCount* logged_counts,
+    uint32_t counts_size,
     HistogramSamples::Metadata* meta,
     HistogramSamples::Metadata* logged_meta) {
   return WrapUnique(new Histogram(name, minimum, maximum, ranges, counts,
-                                  logged_counts, meta, logged_meta));
+                                  logged_counts, counts_size, meta,
+                                  logged_meta));
 }
 
 // Calculate what range of values are held in each bucket.
@@ -510,19 +512,20 @@
                      Sample minimum,
                      Sample maximum,
                      const BucketRanges* ranges,
-                     const DelayedPersistentAllocation& counts,
-                     const DelayedPersistentAllocation& logged_counts,
+                     HistogramBase::AtomicCount* counts,
+                     HistogramBase::AtomicCount* logged_counts,
+                     uint32_t counts_size,
                      HistogramSamples::Metadata* meta,
                      HistogramSamples::Metadata* logged_meta)
-    : HistogramBase(name),
-      bucket_ranges_(ranges),
-      declared_min_(minimum),
-      declared_max_(maximum) {
+  : HistogramBase(name),
+    bucket_ranges_(ranges),
+    declared_min_(minimum),
+    declared_max_(maximum) {
   if (ranges) {
-    samples_.reset(
-        new PersistentSampleVector(HashMetricName(name), ranges, meta, counts));
-    logged_samples_.reset(new PersistentSampleVector(
-        samples_->id(), ranges, logged_meta, logged_counts));
+    samples_.reset(new SampleVector(HashMetricName(name),
+                                    counts, counts_size, meta, ranges));
+    logged_samples_.reset(new SampleVector(samples_->id(), logged_counts,
+                                           counts_size, logged_meta, ranges));
   }
 }
 
@@ -653,7 +656,7 @@
   DCHECK_EQ(sample_count, past);
 }
 
-double Histogram::GetPeakBucketSize(const SampleVectorBase& samples) const {
+double Histogram::GetPeakBucketSize(const SampleVector& samples) const {
   double max = 0;
   for (uint32_t i = 0; i < bucket_count() ; ++i) {
     double current_size = GetBucketSize(samples.GetCountAtIndex(i), i);
@@ -663,7 +666,7 @@
   return max;
 }
 
-void Histogram::WriteAsciiHeader(const SampleVectorBase& samples,
+void Histogram::WriteAsciiHeader(const SampleVector& samples,
                                  Count sample_count,
                                  std::string* output) const {
   StringAppendF(output,
@@ -812,12 +815,14 @@
     Sample minimum,
     Sample maximum,
     const BucketRanges* ranges,
-    const DelayedPersistentAllocation& counts,
-    const DelayedPersistentAllocation& logged_counts,
+    HistogramBase::AtomicCount* counts,
+    HistogramBase::AtomicCount* logged_counts,
+    uint32_t counts_size,
     HistogramSamples::Metadata* meta,
     HistogramSamples::Metadata* logged_meta) {
-  return WrapUnique(new LinearHistogram(name, minimum, maximum, ranges, counts,
-                                        logged_counts, meta, logged_meta));
+  return WrapUnique(new LinearHistogram(name, minimum, maximum, ranges,
+                                              counts, logged_counts,
+                                              counts_size, meta, logged_meta));
 }
 
 HistogramBase* LinearHistogram::FactoryGetWithRangeDescription(
@@ -846,23 +851,17 @@
     : Histogram(name, minimum, maximum, ranges) {
 }
 
-LinearHistogram::LinearHistogram(
-    const std::string& name,
-    Sample minimum,
-    Sample maximum,
-    const BucketRanges* ranges,
-    const DelayedPersistentAllocation& counts,
-    const DelayedPersistentAllocation& logged_counts,
-    HistogramSamples::Metadata* meta,
-    HistogramSamples::Metadata* logged_meta)
-    : Histogram(name,
-                minimum,
-                maximum,
-                ranges,
-                counts,
-                logged_counts,
-                meta,
-                logged_meta) {}
+LinearHistogram::LinearHistogram(const std::string& name,
+                                 Sample minimum,
+                                 Sample maximum,
+                                 const BucketRanges* ranges,
+                                 HistogramBase::AtomicCount* counts,
+                                 HistogramBase::AtomicCount* logged_counts,
+                                 uint32_t counts_size,
+                                 HistogramSamples::Metadata* meta,
+                                 HistogramSamples::Metadata* logged_meta)
+    : Histogram(name, minimum, maximum, ranges, counts, logged_counts,
+                counts_size, meta, logged_meta) {}
 
 double LinearHistogram::GetBucketSize(Count current, uint32_t i) const {
   DCHECK_GT(ranges(i + 1), ranges(i));
@@ -962,12 +961,12 @@
 std::unique_ptr<HistogramBase> BooleanHistogram::PersistentCreate(
     const std::string& name,
     const BucketRanges* ranges,
-    const DelayedPersistentAllocation& counts,
-    const DelayedPersistentAllocation& logged_counts,
+    HistogramBase::AtomicCount* counts,
+    HistogramBase::AtomicCount* logged_counts,
     HistogramSamples::Metadata* meta,
     HistogramSamples::Metadata* logged_meta) {
-  return WrapUnique(new BooleanHistogram(name, ranges, counts, logged_counts,
-                                         meta, logged_meta));
+  return WrapUnique(new BooleanHistogram(
+      name, ranges, counts, logged_counts, meta, logged_meta));
 }
 
 HistogramType BooleanHistogram::GetHistogramType() const {
@@ -978,20 +977,13 @@
                                    const BucketRanges* ranges)
     : LinearHistogram(name, 1, 2, ranges) {}
 
-BooleanHistogram::BooleanHistogram(
-    const std::string& name,
-    const BucketRanges* ranges,
-    const DelayedPersistentAllocation& counts,
-    const DelayedPersistentAllocation& logged_counts,
-    HistogramSamples::Metadata* meta,
-    HistogramSamples::Metadata* logged_meta)
-    : LinearHistogram(name,
-                      1,
-                      2,
-                      ranges,
-                      counts,
-                      logged_counts,
-                      meta,
+BooleanHistogram::BooleanHistogram(const std::string& name,
+                                   const BucketRanges* ranges,
+                                   HistogramBase::AtomicCount* counts,
+                                   HistogramBase::AtomicCount* logged_counts,
+                                   HistogramSamples::Metadata* meta,
+                                   HistogramSamples::Metadata* logged_meta)
+    : LinearHistogram(name, 1, 2, ranges, counts, logged_counts, 2, meta,
                       logged_meta) {}
 
 HistogramBase* BooleanHistogram::DeserializeInfoImpl(PickleIterator* iter) {
@@ -1076,12 +1068,13 @@
 std::unique_ptr<HistogramBase> CustomHistogram::PersistentCreate(
     const std::string& name,
     const BucketRanges* ranges,
-    const DelayedPersistentAllocation& counts,
-    const DelayedPersistentAllocation& logged_counts,
+    HistogramBase::AtomicCount* counts,
+    HistogramBase::AtomicCount* logged_counts,
+    uint32_t counts_size,
     HistogramSamples::Metadata* meta,
     HistogramSamples::Metadata* logged_meta) {
-  return WrapUnique(new CustomHistogram(name, ranges, counts, logged_counts,
-                                        meta, logged_meta));
+  return WrapUnique(new CustomHistogram(
+      name, ranges, counts, logged_counts, counts_size, meta, logged_meta));
 }
 
 HistogramType CustomHistogram::GetHistogramType() const {
@@ -1110,19 +1103,20 @@
                 ranges->range(ranges->bucket_count() - 1),
                 ranges) {}
 
-CustomHistogram::CustomHistogram(
-    const std::string& name,
-    const BucketRanges* ranges,
-    const DelayedPersistentAllocation& counts,
-    const DelayedPersistentAllocation& logged_counts,
-    HistogramSamples::Metadata* meta,
-    HistogramSamples::Metadata* logged_meta)
+CustomHistogram::CustomHistogram(const std::string& name,
+                                 const BucketRanges* ranges,
+                                 HistogramBase::AtomicCount* counts,
+                                 HistogramBase::AtomicCount* logged_counts,
+                                 uint32_t counts_size,
+                                 HistogramSamples::Metadata* meta,
+                                 HistogramSamples::Metadata* logged_meta)
     : Histogram(name,
                 ranges->range(1),
                 ranges->range(ranges->bucket_count() - 1),
                 ranges,
                 counts,
                 logged_counts,
+                counts_size,
                 meta,
                 logged_meta) {}
 
diff --git a/base/metrics/histogram.h b/base/metrics/histogram.h
index 98c07aa..a76dd632 100644
--- a/base/metrics/histogram.h
+++ b/base/metrics/histogram.h
@@ -86,13 +86,11 @@
 
 class BooleanHistogram;
 class CustomHistogram;
-class DelayedPersistentAllocation;
 class Histogram;
 class LinearHistogram;
 class Pickle;
 class PickleIterator;
 class SampleVector;
-class SampleVectorBase;
 
 class BASE_EXPORT Histogram : public HistogramBase {
  public:
@@ -144,8 +142,9 @@
       Sample minimum,
       Sample maximum,
       const BucketRanges* ranges,
-      const DelayedPersistentAllocation& counts,
-      const DelayedPersistentAllocation& logged_counts,
+      HistogramBase::AtomicCount* counts,
+      HistogramBase::AtomicCount* logged_counts,
+      uint32_t counts_size,
       HistogramSamples::Metadata* meta,
       HistogramSamples::Metadata* logged_meta);
 
@@ -231,8 +230,9 @@
             Sample minimum,
             Sample maximum,
             const BucketRanges* ranges,
-            const DelayedPersistentAllocation& counts,
-            const DelayedPersistentAllocation& logged_counts,
+            HistogramBase::AtomicCount* counts,
+            HistogramBase::AtomicCount* logged_counts,
+            uint32_t counts_size,
             HistogramSamples::Metadata* meta,
             HistogramSamples::Metadata* logged_meta);
 
@@ -274,10 +274,10 @@
                       std::string* output) const;
 
   // Find out how large (graphically) the largest bucket will appear to be.
-  double GetPeakBucketSize(const SampleVectorBase& samples) const;
+  double GetPeakBucketSize(const SampleVector& samples) const;
 
   // Write a common header message describing this histogram.
-  void WriteAsciiHeader(const SampleVectorBase& samples,
+  void WriteAsciiHeader(const SampleVector& samples,
                         Count sample_count,
                         std::string* output) const;
 
@@ -304,7 +304,7 @@
 
   // Finally, provide the state that changes with the addition of each new
   // sample.
-  std::unique_ptr<SampleVectorBase> samples_;
+  std::unique_ptr<SampleVector> samples_;
 
   // Also keep a previous uploaded state for calculating deltas.
   std::unique_ptr<HistogramSamples> logged_samples_;
@@ -357,8 +357,9 @@
       Sample minimum,
       Sample maximum,
       const BucketRanges* ranges,
-      const DelayedPersistentAllocation& counts,
-      const DelayedPersistentAllocation& logged_counts,
+      HistogramBase::AtomicCount* counts,
+      HistogramBase::AtomicCount* logged_counts,
+      uint32_t counts_size,
       HistogramSamples::Metadata* meta,
       HistogramSamples::Metadata* logged_meta);
 
@@ -399,8 +400,9 @@
                   Sample minimum,
                   Sample maximum,
                   const BucketRanges* ranges,
-                  const DelayedPersistentAllocation& counts,
-                  const DelayedPersistentAllocation& logged_counts,
+                  HistogramBase::AtomicCount* counts,
+                  HistogramBase::AtomicCount* logged_counts,
+                  uint32_t counts_size,
                   HistogramSamples::Metadata* meta,
                   HistogramSamples::Metadata* logged_meta);
 
@@ -444,8 +446,8 @@
   static std::unique_ptr<HistogramBase> PersistentCreate(
       const std::string& name,
       const BucketRanges* ranges,
-      const DelayedPersistentAllocation& counts,
-      const DelayedPersistentAllocation& logged_counts,
+      HistogramBase::AtomicCount* counts,
+      HistogramBase::AtomicCount* logged_counts,
       HistogramSamples::Metadata* meta,
       HistogramSamples::Metadata* logged_meta);
 
@@ -458,8 +460,8 @@
   BooleanHistogram(const std::string& name, const BucketRanges* ranges);
   BooleanHistogram(const std::string& name,
                    const BucketRanges* ranges,
-                   const DelayedPersistentAllocation& counts,
-                   const DelayedPersistentAllocation& logged_counts,
+                   HistogramBase::AtomicCount* counts,
+                   HistogramBase::AtomicCount* logged_counts,
                    HistogramSamples::Metadata* meta,
                    HistogramSamples::Metadata* logged_meta);
 
@@ -494,8 +496,9 @@
   static std::unique_ptr<HistogramBase> PersistentCreate(
       const std::string& name,
       const BucketRanges* ranges,
-      const DelayedPersistentAllocation& counts,
-      const DelayedPersistentAllocation& logged_counts,
+      HistogramBase::AtomicCount* counts,
+      HistogramBase::AtomicCount* logged_counts,
+      uint32_t counts_size,
       HistogramSamples::Metadata* meta,
       HistogramSamples::Metadata* logged_meta);
 
@@ -518,8 +521,9 @@
 
   CustomHistogram(const std::string& name,
                   const BucketRanges* ranges,
-                  const DelayedPersistentAllocation& counts,
-                  const DelayedPersistentAllocation& logged_counts,
+                  HistogramBase::AtomicCount* counts,
+                  HistogramBase::AtomicCount* logged_counts,
+                  uint32_t counts_size,
                   HistogramSamples::Metadata* meta,
                   HistogramSamples::Metadata* logged_meta);
 
diff --git a/base/metrics/histogram_samples.cc b/base/metrics/histogram_samples.cc
index b07ff9a..3475cd59 100644
--- a/base/metrics/histogram_samples.cc
+++ b/base/metrics/histogram_samples.cc
@@ -4,26 +4,13 @@
 
 #include "base/metrics/histogram_samples.h"
 
-#include <limits>
-
 #include "base/compiler_specific.h"
-#include "base/numerics/safe_math.h"
 #include "base/pickle.h"
 
 namespace base {
 
 namespace {
 
-// A shorthand constant for the max value of size_t.
-constexpr size_t kSizeMax = std::numeric_limits<size_t>::max();
-
-// A constant stored in an AtomicSingleSample (as_atomic) to indicate that the
-// sample is "disabled" and no further accumulation should be done with it. The
-// value is chosen such that it will be MAX_UINT16 for both |bucket| & |count|,
-// and thus less likely to conflict with real use. Conflicts are explicitly
-// handled in the code but it's worth making them as unlikely as possible.
-constexpr int32_t kDisabledSingleSample = -1;
-
 class SampleCountPickleIterator : public SampleCountIterator {
  public:
   explicit SampleCountPickleIterator(PickleIterator* iter);
@@ -72,97 +59,6 @@
 
 }  // namespace
 
-static_assert(sizeof(HistogramSamples::AtomicSingleSample) ==
-                  sizeof(subtle::Atomic32),
-              "AtomicSingleSample isn't 32 bits");
-
-HistogramSamples::SingleSample HistogramSamples::AtomicSingleSample::Load()
-    const {
-  AtomicSingleSample single_sample = subtle::Acquire_Load(&as_atomic);
-
-  // If the sample was extracted/disabled, it's still zero to the outside.
-  if (single_sample.as_atomic == kDisabledSingleSample)
-    single_sample.as_atomic = 0;
-
-  return single_sample.as_parts;
-}
-
-HistogramSamples::SingleSample HistogramSamples::AtomicSingleSample::Extract(
-    bool disable) {
-  AtomicSingleSample single_sample = subtle::NoBarrier_AtomicExchange(
-      &as_atomic, disable ? kDisabledSingleSample : 0);
-  if (single_sample.as_atomic == kDisabledSingleSample)
-    single_sample.as_atomic = 0;
-  return single_sample.as_parts;
-}
-
-bool HistogramSamples::AtomicSingleSample::Accumulate(
-    size_t bucket,
-    HistogramBase::Count count) {
-  if (count == 0)
-    return true;
-
-  // Convert the parameters to 16-bit variables because it's all 16-bit below.
-  if (count < std::numeric_limits<uint16_t>::min() ||
-      count > std::numeric_limits<uint16_t>::max() ||
-      bucket > std::numeric_limits<uint16_t>::max()) {
-    return false;
-  }
-  uint16_t bucket16 = static_cast<uint16_t>(bucket);
-  uint16_t count16 = static_cast<uint16_t>(count);
-
-  // A local, unshared copy of the single-sample is necessary so the parts
-  // can be manipulated without worrying about atomicity.
-  AtomicSingleSample single_sample;
-
-  bool sample_updated;
-  do {
-    subtle::Atomic32 original = subtle::Acquire_Load(&as_atomic);
-    if (original == kDisabledSingleSample)
-      return false;
-    single_sample.as_atomic = original;
-    if (single_sample.as_atomic != 0) {
-      // Only the same bucket (parameter and stored) can be counted multiple
-      // times.
-      if (single_sample.as_parts.bucket != bucket16)
-        return false;
-    } else {
-      // The |single_ sample| was zero so becomes the |bucket| parameter, the
-      // contents of which were checked above to fit in 16 bits.
-      single_sample.as_parts.bucket = bucket16;
-    }
-
-    // Update count, making sure that it doesn't overflow.
-    CheckedNumeric<uint16_t> new_count(single_sample.as_parts.count);
-    new_count += count16;
-    if (!new_count.AssignIfValid(&single_sample.as_parts.count))
-      return false;
-
-    // Don't let this become equivalent to the "disabled" value.
-    if (single_sample.as_atomic == kDisabledSingleSample)
-      return false;
-
-    // Store the updated single-sample back into memory. |existing| is what
-    // was in that memory location at the time of the call; if it doesn't
-    // match |original| then the swap didn't happen so loop again.
-    subtle::Atomic32 existing = subtle::Release_CompareAndSwap(
-        &as_atomic, original, single_sample.as_atomic);
-    sample_updated = (existing == original);
-  } while (!sample_updated);
-
-  return true;
-}
-
-bool HistogramSamples::AtomicSingleSample::IsDisabled() const {
-  return subtle::Acquire_Load(&as_atomic) == kDisabledSingleSample;
-}
-
-HistogramSamples::LocalMetadata::LocalMetadata() {
-  // This is the same way it's done for persistent metadata since no ctor
-  // is called for the data members in that case.
-  memset(this, 0, sizeof(*this));
-}
-
 // Don't try to delegate behavior to the constructor below that accepts a
 // Matadata pointer by passing &local_meta_. Such cannot be reliably passed
 // because it has not yet been constructed -- no member variables have; the
@@ -187,7 +83,9 @@
 HistogramSamples::~HistogramSamples() {}
 
 void HistogramSamples::Add(const HistogramSamples& other) {
-  IncreaseSumAndCount(other.sum(), other.redundant_count());
+  IncreaseSum(other.sum());
+  subtle::NoBarrier_AtomicIncrement(&meta_->redundant_count,
+                                    other.redundant_count());
   bool success = AddSubtractImpl(other.Iterator().get(), ADD);
   DCHECK(success);
 }
@@ -199,14 +97,18 @@
   if (!iter->ReadInt64(&sum) || !iter->ReadInt(&redundant_count))
     return false;
 
-  IncreaseSumAndCount(sum, redundant_count);
+  IncreaseSum(sum);
+  subtle::NoBarrier_AtomicIncrement(&meta_->redundant_count,
+                                    redundant_count);
 
   SampleCountPickleIterator pickle_iter(iter);
   return AddSubtractImpl(&pickle_iter, ADD);
 }
 
 void HistogramSamples::Subtract(const HistogramSamples& other) {
-  IncreaseSumAndCount(-other.sum(), -other.redundant_count());
+  IncreaseSum(-other.sum());
+  subtle::NoBarrier_AtomicIncrement(&meta_->redundant_count,
+                                    -other.redundant_count());
   bool success = AddSubtractImpl(other.Iterator().get(), SUBTRACT);
   DCHECK(success);
 }
@@ -231,25 +133,16 @@
   return true;
 }
 
-bool HistogramSamples::AccumulateSingleSample(HistogramBase::Sample value,
-                                              HistogramBase::Count count,
-                                              size_t bucket) {
-  if (single_sample().Accumulate(bucket, count)) {
-    // Success. Update the (separate) sum and redundant-count.
-    IncreaseSumAndCount(static_cast<int64_t>(value) * count, count);
-    return true;
-  }
-  return false;
+void HistogramSamples::IncreaseSum(int64_t diff) {
+#ifdef ARCH_CPU_64_BITS
+  subtle::NoBarrier_AtomicIncrement(&meta_->sum, diff);
+#else
+  meta_->sum += diff;
+#endif
 }
 
-void HistogramSamples::IncreaseSumAndCount(int64_t sum,
-                                           HistogramBase::Count count) {
-#ifdef ARCH_CPU_64_BITS
-  subtle::NoBarrier_AtomicIncrement(&meta_->sum, sum);
-#else
-  meta_->sum += sum;
-#endif
-  subtle::NoBarrier_AtomicIncrement(&meta_->redundant_count, count);
+void HistogramSamples::IncreaseRedundantCount(HistogramBase::Count diff) {
+  subtle::NoBarrier_AtomicIncrement(&meta_->redundant_count, diff);
 }
 
 SampleCountIterator::~SampleCountIterator() {}
@@ -259,46 +152,4 @@
   return false;
 }
 
-SingleSampleIterator::SingleSampleIterator(HistogramBase::Sample min,
-                                           HistogramBase::Sample max,
-                                           HistogramBase::Count count)
-    : SingleSampleIterator(min, max, count, kSizeMax) {}
-
-SingleSampleIterator::SingleSampleIterator(HistogramBase::Sample min,
-                                           HistogramBase::Sample max,
-                                           HistogramBase::Count count,
-                                           size_t bucket_index)
-    : min_(min), max_(max), bucket_index_(bucket_index), count_(count) {}
-
-SingleSampleIterator::~SingleSampleIterator() {}
-
-bool SingleSampleIterator::Done() const {
-  return count_ == 0;
-}
-
-void SingleSampleIterator::Next() {
-  DCHECK(!Done());
-  count_ = 0;
-}
-
-void SingleSampleIterator::Get(HistogramBase::Sample* min,
-                               HistogramBase::Sample* max,
-                               HistogramBase::Count* count) const {
-  DCHECK(!Done());
-  if (min != nullptr)
-    *min = min_;
-  if (max != nullptr)
-    *max = max_;
-  if (count != nullptr)
-    *count = count_;
-}
-
-bool SingleSampleIterator::GetBucketIndex(size_t* index) const {
-  DCHECK(!Done());
-  if (bucket_index_ == kSizeMax)
-    return false;
-  *index = bucket_index_;
-  return true;
-}
-
 }  // namespace base
diff --git a/base/metrics/histogram_samples.h b/base/metrics/histogram_samples.h
index 0e24c4f..93f6d21 100644
--- a/base/metrics/histogram_samples.h
+++ b/base/metrics/histogram_samples.h
@@ -24,64 +24,8 @@
 // elements must be of a fixed width to ensure 32/64-bit interoperability.
 // If this structure changes, bump the version number for kTypeIdHistogram
 // in persistent_histogram_allocator.cc.
-//
-// Note that though these samples are individually consistent (through the use
-// of atomic operations on the counts), there is only "eventual consistency"
-// overall when multiple threads are accessing this data. That means that the
-// sum, redundant-count, etc. could be momentarily out-of-sync with the stored
-// counts but will settle to a consistent "steady state" once all threads have
-// exited this code.
 class BASE_EXPORT HistogramSamples {
  public:
-  // A single bucket and count. To fit within a single atomic on 32-bit build
-  // architectures, both |bucket| and |count| are limited in size to 16 bits.
-  // This limits the functionality somewhat but if an entry can't fit then
-  // the full array of samples can be allocated and used.
-  struct SingleSample {
-    uint16_t bucket;
-    uint16_t count;
-  };
-
-  // A structure for managing an atomic single sample. Because this is generally
-  // used in association with other atomic values, the defined methods use
-  // acquire/release operations to guarantee ordering with outside values.
-  union AtomicSingleSample {
-    AtomicSingleSample() : as_atomic(0) {}
-    AtomicSingleSample(subtle::Atomic32 rhs) : as_atomic(rhs) {}
-
-    // Returns the single sample in an atomic manner. This in an "acquire"
-    // load. The returned sample isn't shared and thus its fields can be safely
-    // accessed.
-    SingleSample Load() const;
-
-    // Extracts the single sample in an atomic manner. If |disable| is true
-    // then this object will be set so it will never accumulate another value.
-    // This is "no barrier" so doesn't enforce ordering with other atomic ops.
-    SingleSample Extract(bool disable);
-
-    // Adds a given count to the held bucket. If not possible, it returns false
-    // and leaves the parts unchanged. Once extracted/disabled, this always
-    // returns false. This in an "acquire/release" operation.
-    bool Accumulate(size_t bucket, HistogramBase::Count count);
-
-    // Returns if the sample has been "disabled" (via Extract) and thus not
-    // allowed to accept further accumulation.
-    bool IsDisabled() const;
-
-   private:
-    // union field: The actual sample bucket and count.
-    SingleSample as_parts;
-
-    // union field: The sample as an atomic value. Atomic64 would provide
-    // more flexibility but isn't available on all builds. This can hold a
-    // special, internal "disabled" value indicating that it must not accept
-    // further accumulation.
-    subtle::Atomic32 as_atomic;
-  };
-
-  // A structure of information about the data, common to all sample containers.
-  // Because of how this is used in persistent memory, it must be a POD object
-  // that makes sense when initialized to all zeros.
   struct Metadata {
     // Expected size for 32/64-bit check.
     static constexpr size_t kExpectedInstanceSize = 24;
@@ -114,17 +58,21 @@
     // might mismatch even when no memory corruption has happened.
     HistogramBase::AtomicCount redundant_count;
 
-    // A single histogram value and associated count. This allows histograms
-    // that typically report only a single value to not require full storage
-    // to be allocated.
-    AtomicSingleSample single_sample;  // 32 bits
+    // 4 bytes of padding to explicitly extend this structure to a multiple of
+    // 64-bits. This is required to ensure the structure is the same size on
+    // both 32-bit and 64-bit builds.
+    char padding[4];
   };
 
-  // Because structures held in persistent memory must be POD, there can be no
+  // Because sturctures held in persistent memory must be POD, there can be no
   // default constructor to clear the fields. This derived class exists just
   // to clear them when being allocated on the heap.
-  struct BASE_EXPORT LocalMetadata : Metadata {
-    LocalMetadata();
+  struct LocalMetadata : Metadata {
+    LocalMetadata() {
+      id = 0;
+      sum = 0;
+      redundant_count = 0;
+    }
   };
 
   explicit HistogramSamples(uint64_t id);
@@ -164,20 +112,8 @@
   enum Operator { ADD, SUBTRACT };
   virtual bool AddSubtractImpl(SampleCountIterator* iter, Operator op) = 0;
 
-  // Accumulates to the embedded single-sample field if possible. Returns true
-  // on success, false otherwise. Sum and redundant-count are also updated in
-  // the success case.
-  bool AccumulateSingleSample(HistogramBase::Sample value,
-                              HistogramBase::Count count,
-                              size_t bucket);
-
-  // Atomically adjust the sum and redundant-count.
-  void IncreaseSumAndCount(int64_t sum, HistogramBase::Count count);
-
-  AtomicSingleSample& single_sample() { return meta_->single_sample; }
-  const AtomicSingleSample& single_sample() const {
-    return meta_->single_sample;
-  }
+  void IncreaseSum(int64_t diff);
+  void IncreaseRedundantCount(HistogramBase::Count diff);
 
  private:
   // In order to support histograms shared through an external memory segment,
@@ -209,35 +145,6 @@
   virtual bool GetBucketIndex(size_t* index) const;
 };
 
-class BASE_EXPORT SingleSampleIterator : public SampleCountIterator {
- public:
-  SingleSampleIterator(HistogramBase::Sample min,
-                       HistogramBase::Sample max,
-                       HistogramBase::Count count);
-  SingleSampleIterator(HistogramBase::Sample min,
-                       HistogramBase::Sample max,
-                       HistogramBase::Count count,
-                       size_t bucket_index);
-  ~SingleSampleIterator() override;
-
-  // SampleCountIterator:
-  bool Done() const override;
-  void Next() override;
-  void Get(HistogramBase::Sample* min,
-           HistogramBase::Sample* max,
-           HistogramBase::Count* count) const override;
-
-  // SampleVector uses predefined buckets so iterator can return bucket index.
-  bool GetBucketIndex(size_t* index) const override;
-
- private:
-  // Information about the single value to return.
-  const HistogramBase::Sample min_;
-  const HistogramBase::Sample max_;
-  const size_t bucket_index_;
-  HistogramBase::Count count_;
-};
-
 }  // namespace base
 
 #endif  // BASE_METRICS_HISTOGRAM_SAMPLES_H_
diff --git a/base/metrics/histogram_unittest.cc b/base/metrics/histogram_unittest.cc
index d89e7e93..02ed93b 100644
--- a/base/metrics/histogram_unittest.cc
+++ b/base/metrics/histogram_unittest.cc
@@ -490,15 +490,15 @@
   EXPECT_EQ(2, snapshot->redundant_count());
   EXPECT_EQ(2, snapshot->TotalCount());
 
-  snapshot->counts()[3] += 100;  // Sample count won't match redundant count.
+  snapshot->counts_[3] += 100;  // Sample count won't match redundant count.
   EXPECT_EQ(HistogramBase::COUNT_LOW_ERROR,
             histogram->FindCorruption(*snapshot));
-  snapshot->counts()[2] -= 200;
+  snapshot->counts_[2] -= 200;
   EXPECT_EQ(HistogramBase::COUNT_HIGH_ERROR,
             histogram->FindCorruption(*snapshot));
 
   // But we can't spot a corruption if it is compensated for.
-  snapshot->counts()[1] += 100;
+  snapshot->counts_[1] += 100;
   EXPECT_EQ(HistogramBase::NO_INCONSISTENCIES,
             histogram->FindCorruption(*snapshot));
 }
diff --git a/base/metrics/persistent_histogram_allocator.cc b/base/metrics/persistent_histogram_allocator.cc
index c73d844..b2dae99 100644
--- a/base/metrics/persistent_histogram_allocator.cc
+++ b/base/metrics/persistent_histogram_allocator.cc
@@ -240,7 +240,7 @@
   uint32_t bucket_count;
   PersistentMemoryAllocator::Reference ranges_ref;
   uint32_t ranges_checksum;
-  subtle::Atomic32 counts_ref;  // PersistentMemoryAllocator::Reference
+  PersistentMemoryAllocator::Reference counts_ref;
   HistogramSamples::Metadata samples_metadata;
   HistogramSamples::Metadata logged_metadata;
 
@@ -375,12 +375,14 @@
       DCHECK_EQ(kTypeIdRangesArray, memory_allocator_->GetType(ranges_ref));
     }
 
+    PersistentMemoryAllocator::Reference counts_ref =
+        memory_allocator_->Allocate(counts_bytes, kTypeIdCountsArray);
 
     // Only continue here if all allocations were successful. If they weren't,
     // there is no way to free the space but that's not really a problem since
     // the allocations only fail because the space is full or corrupt and so
     // any future attempts will also fail.
-    if (ranges_ref && histogram_data) {
+    if (counts_ref && ranges_ref && histogram_data) {
       histogram_data->minimum = minimum;
       histogram_data->maximum = maximum;
       // |bucket_count| must fit within 32-bits or the allocation of the counts
@@ -389,6 +391,7 @@
       histogram_data->bucket_count = static_cast<uint32_t>(bucket_count);
       histogram_data->ranges_ref = ranges_ref;
       histogram_data->ranges_checksum = bucket_ranges->checksum();
+      histogram_data->counts_ref = counts_ref;
     } else {
       histogram_data = nullptr;  // Clear this for proper handling below.
     }
@@ -603,52 +606,42 @@
       StatisticsRecorder::RegisterOrDeleteDuplicateRanges(
           created_ranges.release());
 
+  HistogramBase::AtomicCount* counts_data =
+      memory_allocator_->GetAsArray<HistogramBase::AtomicCount>(
+          histogram_data.counts_ref, kTypeIdCountsArray,
+          PersistentMemoryAllocator::kSizeAny);
   size_t counts_bytes =
       CalculateRequiredCountsBytes(histogram_data.bucket_count);
-  PersistentMemoryAllocator::Reference counts_ref =
-      subtle::NoBarrier_Load(&histogram_data.counts_ref);
-  if (counts_bytes == 0 ||
-      (counts_ref != 0 &&
-       memory_allocator_->GetAllocSize(counts_ref) < counts_bytes)) {
+  if (!counts_data || counts_bytes == 0 ||
+      memory_allocator_->GetAllocSize(histogram_data.counts_ref) <
+          counts_bytes) {
     RecordCreateHistogramResult(CREATE_HISTOGRAM_INVALID_COUNTS_ARRAY);
     NOTREACHED();
     return nullptr;
   }
 
-  // The "counts" data (including both samples and logged samples) is a delayed
-  // persistent allocation meaning that though its size and storage for a
-  // reference is defined, no space is reserved until actually needed. When
-  // it is needed, memory will be allocated from the persistent segment and
-  // a reference to it stored at the passed address. Other threads can then
-  // notice the valid reference and access the same data.
-  DelayedPersistentAllocation counts_data(memory_allocator_.get(),
-                                          &histogram_data_ptr->counts_ref,
-                                          kTypeIdCountsArray, counts_bytes, 0);
+  // After the main "counts" array is a second array using for storing what
+  // was previously logged. This is used to calculate the "delta" during
+  // snapshot operations.
+  HistogramBase::AtomicCount* logged_data =
+      counts_data + histogram_data.bucket_count;
 
-  // A second delayed allocations is defined using the same reference storage
-  // location as the first so the allocation of one will automatically be found
-  // by the other. Within the block, the first half of the space is for "counts"
-  // and the second half is for "logged counts".
-  DelayedPersistentAllocation logged_data(
-      memory_allocator_.get(), &histogram_data_ptr->counts_ref,
-      kTypeIdCountsArray, counts_bytes, counts_bytes / 2,
-      /*make_iterable=*/false);
-
-  // Create the right type of histogram.
   std::string name(histogram_data_ptr->name);
   std::unique_ptr<HistogramBase> histogram;
   switch (histogram_data.histogram_type) {
     case HISTOGRAM:
       histogram = Histogram::PersistentCreate(
           name, histogram_data.minimum, histogram_data.maximum, ranges,
-          counts_data, logged_data, &histogram_data_ptr->samples_metadata,
+          counts_data, logged_data, histogram_data.bucket_count,
+          &histogram_data_ptr->samples_metadata,
           &histogram_data_ptr->logged_metadata);
       DCHECK(histogram);
       break;
     case LINEAR_HISTOGRAM:
       histogram = LinearHistogram::PersistentCreate(
           name, histogram_data.minimum, histogram_data.maximum, ranges,
-          counts_data, logged_data, &histogram_data_ptr->samples_metadata,
+          counts_data, logged_data, histogram_data.bucket_count,
+          &histogram_data_ptr->samples_metadata,
           &histogram_data_ptr->logged_metadata);
       DCHECK(histogram);
       break;
@@ -661,7 +654,7 @@
       break;
     case CUSTOM_HISTOGRAM:
       histogram = CustomHistogram::PersistentCreate(
-          name, ranges, counts_data, logged_data,
+          name, ranges, counts_data, logged_data, histogram_data.bucket_count,
           &histogram_data_ptr->samples_metadata,
           &histogram_data_ptr->logged_metadata);
       DCHECK(histogram);
diff --git a/base/metrics/persistent_sample_map.cc b/base/metrics/persistent_sample_map.cc
index 9fb0ff8..51cc0c70 100644
--- a/base/metrics/persistent_sample_map.cc
+++ b/base/metrics/persistent_sample_map.cc
@@ -115,7 +115,8 @@
 
 void PersistentSampleMap::Accumulate(Sample value, Count count) {
   *GetOrCreateSampleCountStorage(value) += count;
-  IncreaseSumAndCount(static_cast<int64_t>(count) * value, count);
+  IncreaseSum(static_cast<int64_t>(count) * value);
+  IncreaseRedundantCount(count);
 }
 
 Count PersistentSampleMap::GetCount(Sample value) const {
diff --git a/base/metrics/sample_map.cc b/base/metrics/sample_map.cc
index 5ef0fb8..8abd01ed 100644
--- a/base/metrics/sample_map.cc
+++ b/base/metrics/sample_map.cc
@@ -84,7 +84,8 @@
 
 void SampleMap::Accumulate(Sample value, Count count) {
   sample_counts_[value] += count;
-  IncreaseSumAndCount(static_cast<int64_t>(count) * value, count);
+  IncreaseSum(static_cast<int64_t>(count) * value);
+  IncreaseRedundantCount(count);
 }
 
 Count SampleMap::GetCount(Sample value) const {
diff --git a/base/metrics/sample_vector.cc b/base/metrics/sample_vector.cc
index b55449d..477b8af 100644
--- a/base/metrics/sample_vector.cc
+++ b/base/metrics/sample_vector.cc
@@ -4,216 +4,103 @@
 
 #include "base/metrics/sample_vector.h"
 
-#include "base/lazy_instance.h"
 #include "base/logging.h"
-#include "base/memory/ptr_util.h"
-#include "base/metrics/persistent_memory_allocator.h"
-#include "base/synchronization/lock.h"
-#include "base/threading/platform_thread.h"
-
-// This SampleVector makes use of the single-sample embedded in the base
-// HistogramSamples class. If the count is non-zero then there is guaranteed
-// (within the bounds of "eventual consistency") to be no allocated external
-// storage. Once the full counts storage is allocated, the single-sample must
-// be extracted and disabled.
+#include "base/metrics/bucket_ranges.h"
 
 namespace base {
 
 typedef HistogramBase::Count Count;
 typedef HistogramBase::Sample Sample;
 
-SampleVectorBase::SampleVectorBase(uint64_t id,
-                                   const BucketRanges* bucket_ranges)
-    : HistogramSamples(id), bucket_ranges_(bucket_ranges) {
+SampleVector::SampleVector(const BucketRanges* bucket_ranges)
+    : SampleVector(0, bucket_ranges) {}
+
+SampleVector::SampleVector(uint64_t id, const BucketRanges* bucket_ranges)
+    : HistogramSamples(id),
+      local_counts_(bucket_ranges->bucket_count()),
+      counts_(&local_counts_[0]),
+      counts_size_(local_counts_.size()),
+      bucket_ranges_(bucket_ranges) {
   CHECK_GE(bucket_ranges_->bucket_count(), 1u);
 }
 
-SampleVectorBase::SampleVectorBase(uint64_t id,
-                                   Metadata* meta,
-                                   const BucketRanges* bucket_ranges)
-    : HistogramSamples(id, meta), bucket_ranges_(bucket_ranges) {
+SampleVector::SampleVector(uint64_t id,
+                           HistogramBase::AtomicCount* counts,
+                           size_t counts_size,
+                           Metadata* meta,
+                           const BucketRanges* bucket_ranges)
+    : HistogramSamples(id, meta),
+      counts_(counts),
+      counts_size_(bucket_ranges->bucket_count()),
+      bucket_ranges_(bucket_ranges) {
+  CHECK_LE(bucket_ranges_->bucket_count(), counts_size_);
   CHECK_GE(bucket_ranges_->bucket_count(), 1u);
 }
 
-SampleVectorBase::~SampleVectorBase() {}
+SampleVector::~SampleVector() {}
 
-void SampleVectorBase::Accumulate(Sample value, Count count) {
-  const size_t bucket_index = GetBucketIndex(value);
+void SampleVector::Accumulate(Sample value, Count count) {
+  size_t bucket_index = GetBucketIndex(value);
+  subtle::NoBarrier_AtomicIncrement(&counts_[bucket_index], count);
+  IncreaseSum(static_cast<int64_t>(count) * value);
+  IncreaseRedundantCount(count);
+}
 
-  // Handle the single-sample case.
-  if (!counts()) {
-    // Try to accumulate the parameters into the single-count entry.
-    if (AccumulateSingleSample(value, count, bucket_index)) {
-      // A race condition could lead to a new single-sample being accumulated
-      // above just after another thread executed the MountCountsStorage below.
-      // Since it is mounted, it could be mounted elsewhere and have values
-      // written to it. It's not allowed to have both a single-sample and
-      // entries in the counts array so move the single-sample.
-      if (counts())
-        MoveSingleSampleToCounts();
-      return;
-    }
+Count SampleVector::GetCount(Sample value) const {
+  size_t bucket_index = GetBucketIndex(value);
+  return subtle::NoBarrier_Load(&counts_[bucket_index]);
+}
 
-    // Need real storage to store both what was in the single-sample plus the
-    // parameter information.
-    MountCountsStorageAndMoveSingleSample();
+Count SampleVector::TotalCount() const {
+  Count count = 0;
+  for (size_t i = 0; i < counts_size_; i++) {
+    count += subtle::NoBarrier_Load(&counts_[i]);
   }
-
-  // Handle the multi-sample case.
-  subtle::NoBarrier_AtomicIncrement(&counts()[bucket_index], count);
-  IncreaseSumAndCount(static_cast<int64_t>(count) * value, count);
+  return count;
 }
 
-Count SampleVectorBase::GetCount(Sample value) const {
-  return GetCountAtIndex(GetBucketIndex(value));
+Count SampleVector::GetCountAtIndex(size_t bucket_index) const {
+  DCHECK(bucket_index < counts_size_);
+  return subtle::NoBarrier_Load(&counts_[bucket_index]);
 }
 
-Count SampleVectorBase::TotalCount() const {
-  // Handle the single-sample case.
-  SingleSample sample = single_sample().Load();
-  if (sample.count != 0)
-    return sample.count;
-
-  // Handle the multi-sample case.
-  if (counts() || MountExistingCountsStorage()) {
-    Count count = 0;
-    size_t size = counts_size();
-    const HistogramBase::AtomicCount* counts_array = counts();
-    for (size_t i = 0; i < size; ++i) {
-      count += subtle::NoBarrier_Load(&counts_array[i]);
-    }
-    return count;
-  }
-
-  // And the no-value case.
-  return 0;
+std::unique_ptr<SampleCountIterator> SampleVector::Iterator() const {
+  return std::unique_ptr<SampleCountIterator>(
+      new SampleVectorIterator(counts_, counts_size_, bucket_ranges_));
 }
 
-Count SampleVectorBase::GetCountAtIndex(size_t bucket_index) const {
-  DCHECK(bucket_index < counts_size());
-
-  // Handle the single-sample case.
-  SingleSample sample = single_sample().Load();
-  if (sample.count != 0)
-    return sample.bucket == bucket_index ? sample.count : 0;
-
-  // Handle the multi-sample case.
-  if (counts() || MountExistingCountsStorage())
-    return subtle::NoBarrier_Load(&counts()[bucket_index]);
-
-  // And the no-value case.
-  return 0;
-}
-
-std::unique_ptr<SampleCountIterator> SampleVectorBase::Iterator() const {
-  // Handle the single-sample case.
-  SingleSample sample = single_sample().Load();
-  if (sample.count != 0) {
-    return MakeUnique<SingleSampleIterator>(
-        bucket_ranges_->range(sample.bucket),
-        bucket_ranges_->range(sample.bucket + 1), sample.count, sample.bucket);
-  }
-
-  // Handle the multi-sample case.
-  if (counts() || MountExistingCountsStorage()) {
-    return MakeUnique<SampleVectorIterator>(counts(), counts_size(),
-                                            bucket_ranges_);
-  }
-
-  // And the no-value case.
-  return MakeUnique<SampleVectorIterator>(nullptr, 0, bucket_ranges_);
-}
-
-bool SampleVectorBase::AddSubtractImpl(SampleCountIterator* iter,
-                                       HistogramSamples::Operator op) {
-  // Stop now if there's nothing to do.
-  if (iter->Done())
-    return true;
-
-  // Get the first value and its index.
+bool SampleVector::AddSubtractImpl(SampleCountIterator* iter,
+                                   HistogramSamples::Operator op) {
   HistogramBase::Sample min;
   HistogramBase::Sample max;
   HistogramBase::Count count;
-  iter->Get(&min, &max, &count);
-  size_t dest_index = GetBucketIndex(min);
-
-  // The destination must be a superset of the source meaning that though the
-  // incoming ranges will find an exact match, the incoming bucket-index, if
-  // it exists, may be offset from the destination bucket-index. Calculate
-  // that offset of the passed iterator; there are are no overflow checks
-  // because 2's compliment math will work it out in the end.
-  //
-  // Because GetBucketIndex() always returns the same true or false result for
-  // a given iterator object, |index_offset| is either set here and used below,
-  // or never set and never used. The compiler doesn't know this, though, which
-  // is why it's necessary to initialize it to something.
-  size_t index_offset = 0;
-  size_t iter_index;
-  if (iter->GetBucketIndex(&iter_index))
-    index_offset = dest_index - iter_index;
-  if (dest_index >= counts_size())
-    return false;
-
-  // Post-increment. Information about the current sample is not available
-  // after this point.
-  iter->Next();
-
-  // Single-value storage is possible if there is no counts storage and the
-  // retrieved entry is the only one in the iterator.
-  if (!counts()) {
-    if (iter->Done()) {
-      // Don't call AccumulateSingleSample because that updates sum and count
-      // which was already done by the caller of this method.
-      if (single_sample().Accumulate(
-              dest_index, op == HistogramSamples::ADD ? count : -count)) {
-        // Handle race-condition that mounted counts storage between above and
-        // here.
-        if (counts())
-          MoveSingleSampleToCounts();
-        return true;
-      }
-    }
-
-    // The counts storage will be needed to hold the multiple incoming values.
-    MountCountsStorageAndMoveSingleSample();
-  }
 
   // Go through the iterator and add the counts into correct bucket.
-  while (true) {
-    // Ensure that the sample's min/max match the ranges min/max.
-    if (min != bucket_ranges_->range(dest_index) ||
-        max != bucket_ranges_->range(dest_index + 1)) {
-      NOTREACHED() << "sample=" << min << "," << max
-                   << "; range=" << bucket_ranges_->range(dest_index) << ","
-                   << bucket_ranges_->range(dest_index + 1);
-      return false;
-    }
-
-    // Sample's bucket matches exactly. Adjust count.
-    subtle::NoBarrier_AtomicIncrement(
-        &counts()[dest_index], op == HistogramSamples::ADD ? count : -count);
-
-    // Advance to the next iterable sample. See comments above for how
-    // everything works.
-    if (iter->Done())
-      return true;
+  size_t index = 0;
+  while (index < counts_size_ && !iter->Done()) {
     iter->Get(&min, &max, &count);
-    if (iter->GetBucketIndex(&iter_index)) {
-      // Destination bucket is a known offset from the source bucket.
-      dest_index = iter_index + index_offset;
+    if (min == bucket_ranges_->range(index) &&
+        max == bucket_ranges_->range(index + 1)) {
+      // Sample matches this bucket!
+      subtle::NoBarrier_AtomicIncrement(
+          &counts_[index], op == HistogramSamples::ADD ? count : -count);
+      iter->Next();
+    } else if (min > bucket_ranges_->range(index)) {
+      // Sample is larger than current bucket range. Try next.
+      index++;
     } else {
-      // Destination bucket has to be determined anew each time.
-      dest_index = GetBucketIndex(min);
-    }
-    if (dest_index >= counts_size())
+      // Sample is smaller than current bucket range. We scan buckets from
+      // smallest to largest, so the sample value must be invalid.
       return false;
-    iter->Next();
+    }
   }
+
+  return iter->Done();
 }
 
 // Use simple binary search.  This is very general, but there are better
 // approaches if we knew that the buckets were linearly distributed.
-size_t SampleVectorBase::GetBucketIndex(Sample value) const {
+size_t SampleVector::GetBucketIndex(Sample value) const {
   size_t bucket_count = bucket_ranges_->bucket_count();
   CHECK_GE(bucket_count, 1u);
   CHECK_GE(value, bucket_ranges_->range(0));
@@ -238,124 +125,6 @@
   return mid;
 }
 
-void SampleVectorBase::MoveSingleSampleToCounts() {
-  DCHECK(counts());
-
-  // Disable the single-sample since there is now counts storage for the data.
-  SingleSample sample = single_sample().Extract(/*disable=*/true);
-
-  // Stop here if there is no "count" as trying to find the bucket index of
-  // an invalid (including zero) "value" will crash.
-  if (sample.count == 0)
-    return;
-
-  // Move the value into storage. Sum and redundant-count already account
-  // for this entry so no need to call IncreaseSumAndCount().
-  subtle::NoBarrier_AtomicIncrement(&counts()[sample.bucket], sample.count);
-}
-
-void SampleVectorBase::MountCountsStorageAndMoveSingleSample() {
-  // There are many SampleVector objects and the lock is needed very
-  // infrequently (just when advancing from single-sample to multi-sample) so
-  // define a single, global lock that all can use. This lock only prevents
-  // concurrent entry into the code below; access and updates to |counts_|
-  // still requires atomic operations.
-  static LazyInstance<Lock>::Leaky counts_lock = LAZY_INSTANCE_INITIALIZER;
-  if (subtle::NoBarrier_Load(&counts_) == 0) {
-    AutoLock lock(counts_lock.Get());
-    if (subtle::NoBarrier_Load(&counts_) == 0) {
-      // Create the actual counts storage while the above lock is acquired.
-      HistogramBase::Count* counts = CreateCountsStorageWhileLocked();
-      DCHECK(counts);
-
-      // Point |counts_| to the newly created storage. This is done while
-      // locked to prevent possible concurrent calls to CreateCountsStorage
-      // but, between that call and here, other threads could notice the
-      // existance of the storage and race with this to set_counts(). That's
-      // okay because (a) it's atomic and (b) it always writes the same value.
-      set_counts(counts);
-    }
-  }
-
-  // Move any single-sample into the newly mounted storage.
-  MoveSingleSampleToCounts();
-}
-
-SampleVector::SampleVector(const BucketRanges* bucket_ranges)
-    : SampleVector(0, bucket_ranges) {}
-
-SampleVector::SampleVector(uint64_t id, const BucketRanges* bucket_ranges)
-    : SampleVectorBase(id, bucket_ranges) {}
-
-SampleVector::~SampleVector() {}
-
-bool SampleVector::MountExistingCountsStorage() const {
-  // There is never any existing storage other than what is already in use.
-  return counts() != nullptr;
-}
-
-HistogramBase::AtomicCount* SampleVector::CreateCountsStorageWhileLocked() {
-  local_counts_.resize(counts_size());
-  return &local_counts_[0];
-}
-
-PersistentSampleVector::PersistentSampleVector(
-    uint64_t id,
-    const BucketRanges* bucket_ranges,
-    Metadata* meta,
-    const DelayedPersistentAllocation& counts)
-    : SampleVectorBase(id, meta, bucket_ranges), persistent_counts_(counts) {
-  // Only mount the full storage if the single-sample has been disabled.
-  // Otherwise, it is possible for this object instance to start using (empty)
-  // storage that was created incidentally while another instance continues to
-  // update to the single sample. This "incidental creation" can happen because
-  // the memory is a DelayedPersistentAllocation which allows multiple memory
-  // blocks within it and applies an all-or-nothing approach to the allocation.
-  // Thus, a request elsewhere for one of the _other_ blocks would make _this_
-  // block available even though nothing has explicitly requested it.
-  //
-  // Note that it's not possible for the ctor to mount existing storage and
-  // move any single-sample to it because sometimes the persistent memory is
-  // read-only. Only non-const methods (which assume that memory is read/write)
-  // can do that.
-  if (single_sample().IsDisabled()) {
-    bool success = MountExistingCountsStorage();
-    DCHECK(success);
-  }
-}
-
-PersistentSampleVector::~PersistentSampleVector() {}
-
-bool PersistentSampleVector::MountExistingCountsStorage() const {
-  // There is no early exit if counts is not yet mounted because, given that
-  // this is a virtual function, it's more efficient to do that at the call-
-  // site. There is no danger, however, should this get called anyway (perhaps
-  // because of a race condition) because at worst the |counts_| value would
-  // be over-written (in an atomic manner) with the exact same address.
-
-  if (!persistent_counts_.reference())
-    return false;  // Nothing to mount.
-
-  // Mount the counts array in position.
-  set_counts(
-      static_cast<HistogramBase::AtomicCount*>(persistent_counts_.Get()));
-  return true;
-}
-
-HistogramBase::AtomicCount*
-PersistentSampleVector::CreateCountsStorageWhileLocked() {
-  void* mem = persistent_counts_.Get();
-  if (!mem) {
-    // The above shouldn't fail but can if Bad Things(tm) are occurring in the
-    // persistent allocator. Crashing isn't a good option so instead just
-    // allocate something from the heap and return that. There will be no
-    // sharing or persistence but worse things are already happening.
-    return new HistogramBase::AtomicCount[counts_size()];
-  }
-
-  return static_cast<HistogramBase::AtomicCount*>(mem);
-}
-
 SampleVectorIterator::SampleVectorIterator(
     const std::vector<HistogramBase::AtomicCount>* counts,
     const BucketRanges* bucket_ranges)
diff --git a/base/metrics/sample_vector.h b/base/metrics/sample_vector.h
index a264b7e7..ee26c52 100644
--- a/base/metrics/sample_vector.h
+++ b/base/metrics/sample_vector.h
@@ -14,28 +14,28 @@
 #include <memory>
 #include <vector>
 
-#include "base/atomicops.h"
 #include "base/compiler_specific.h"
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
-#include "base/metrics/bucket_ranges.h"
 #include "base/metrics/histogram_base.h"
 #include "base/metrics/histogram_samples.h"
-#include "base/metrics/persistent_memory_allocator.h"
 
 namespace base {
 
 class BucketRanges;
 
-class BASE_EXPORT SampleVectorBase : public HistogramSamples {
+class BASE_EXPORT SampleVector : public HistogramSamples {
  public:
-  SampleVectorBase(uint64_t id, const BucketRanges* bucket_ranges);
-  SampleVectorBase(uint64_t id,
-                   Metadata* meta,
-                   const BucketRanges* bucket_ranges);
-  ~SampleVectorBase() override;
+  explicit SampleVector(const BucketRanges* bucket_ranges);
+  SampleVector(uint64_t id, const BucketRanges* bucket_ranges);
+  SampleVector(uint64_t id,
+               HistogramBase::AtomicCount* counts,
+               size_t counts_size,
+               Metadata* meta,
+               const BucketRanges* bucket_ranges);
+  ~SampleVector() override;
 
-  // HistogramSamples:
+  // HistogramSamples implementation:
   void Accumulate(HistogramBase::Sample value,
                   HistogramBase::Count count) override;
   HistogramBase::Count GetCount(HistogramBase::Sample value) const override;
@@ -52,103 +52,25 @@
 
   virtual size_t GetBucketIndex(HistogramBase::Sample value) const;
 
-  // Moves the single-sample value to a mounted "counts" array.
-  void MoveSingleSampleToCounts();
-
-  // Mounts (creating if necessary) an array of "counts" for multi-value
-  // storage.
-  void MountCountsStorageAndMoveSingleSample();
-
-  // Mounts "counts" storage that already exists. This does not attempt to move
-  // any single-sample information to that storage as that would violate the
-  // "const" restriction that is often used to indicate read-only memory.
-  virtual bool MountExistingCountsStorage() const = 0;
-
-  // Creates "counts" storage and returns a pointer to it. Ownership of the
-  // array remains with the called method but will never change. This must be
-  // called while some sort of lock is held to prevent reentry.
-  virtual HistogramBase::Count* CreateCountsStorageWhileLocked() = 0;
-
-  HistogramBase::AtomicCount* counts() {
-    return reinterpret_cast<HistogramBase::AtomicCount*>(
-        subtle::Acquire_Load(&counts_));
-  }
-
-  const HistogramBase::AtomicCount* counts() const {
-    return reinterpret_cast<HistogramBase::AtomicCount*>(
-        subtle::Acquire_Load(&counts_));
-  }
-
-  void set_counts(const HistogramBase::AtomicCount* counts) const {
-    subtle::Release_Store(&counts_, reinterpret_cast<uintptr_t>(counts));
-  }
-
-  size_t counts_size() const { return bucket_ranges_->bucket_count(); }
-
  private:
-  friend class SampleVectorTest;
   FRIEND_TEST_ALL_PREFIXES(HistogramTest, CorruptSampleCounts);
   FRIEND_TEST_ALL_PREFIXES(SharedHistogramTest, CorruptSampleCounts);
 
-  // |counts_| is actually a pointer to a HistogramBase::AtomicCount array but
-  // is held as an AtomicWord for concurrency reasons. When combined with the
-  // single_sample held in the metadata, there are four possible states:
-  //   1) single_sample == zero, counts_ == null
-  //   2) single_sample != zero, counts_ == null
-  //   3) single_sample != zero, counts_ != null BUT IS EMPTY
-  //   4) single_sample == zero, counts_ != null and may have data
-  // Once |counts_| is set, it can never revert and any existing single-sample
-  // must be moved to this storage. It is mutable because changing it doesn't
-  // change the (const) data but must adapt if a non-const object causes the
-  // storage to be allocated and updated.
-  mutable subtle::AtomicWord counts_ = 0;
+  // In the case where this class manages the memory, here it is.
+  std::vector<HistogramBase::AtomicCount> local_counts_;
+
+  // These are raw pointers rather than objects for flexibility. The actual
+  // memory is either managed by local_counts_ above or by an external object
+  // and passed in directly.
+  HistogramBase::AtomicCount* counts_;
+  size_t counts_size_;
 
   // Shares the same BucketRanges with Histogram object.
   const BucketRanges* const bucket_ranges_;
 
-  DISALLOW_COPY_AND_ASSIGN(SampleVectorBase);
-};
-
-// A sample vector that uses local memory for the counts array.
-class BASE_EXPORT SampleVector : public SampleVectorBase {
- public:
-  explicit SampleVector(const BucketRanges* bucket_ranges);
-  SampleVector(uint64_t id, const BucketRanges* bucket_ranges);
-  ~SampleVector() override;
-
- private:
-  // SampleVectorBase:
-  bool MountExistingCountsStorage() const override;
-  HistogramBase::Count* CreateCountsStorageWhileLocked() override;
-
-  // Simple local storage for counts.
-  mutable std::vector<HistogramBase::AtomicCount> local_counts_;
-
   DISALLOW_COPY_AND_ASSIGN(SampleVector);
 };
 
-// A sample vector that uses persistent memory for the counts array.
-class BASE_EXPORT PersistentSampleVector : public SampleVectorBase {
- public:
-  PersistentSampleVector(uint64_t id,
-                         const BucketRanges* bucket_ranges,
-                         Metadata* meta,
-                         const DelayedPersistentAllocation& counts);
-  ~PersistentSampleVector() override;
-
- private:
-  // SampleVectorBase:
-  bool MountExistingCountsStorage() const override;
-  HistogramBase::Count* CreateCountsStorageWhileLocked() override;
-
-  // Persistent storage for counts.
-  DelayedPersistentAllocation persistent_counts_;
-
-  DISALLOW_COPY_AND_ASSIGN(PersistentSampleVector);
-};
-
-// An iterator for sample vectors. This could be defined privately in the .cc
-// file but is here for easy testing.
 class BASE_EXPORT SampleVectorIterator : public SampleCountIterator {
  public:
   SampleVectorIterator(const std::vector<HistogramBase::AtomicCount>* counts,
diff --git a/base/metrics/sample_vector_unittest.cc b/base/metrics/sample_vector_unittest.cc
index 9616c00..2d77d23 100644
--- a/base/metrics/sample_vector_unittest.cc
+++ b/base/metrics/sample_vector_unittest.cc
@@ -7,29 +7,18 @@
 #include <limits.h>
 #include <stddef.h>
 
-#include <atomic>
 #include <memory>
 #include <vector>
 
 #include "base/metrics/bucket_ranges.h"
 #include "base/metrics/histogram.h"
-#include "base/metrics/persistent_memory_allocator.h"
 #include "base/test/gtest_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace base {
+namespace {
 
-// This framework class has "friend" access to the SampleVector for accessing
-// non-public methods and fields.
-class SampleVectorTest : public testing::Test {
- public:
-  const HistogramBase::AtomicCount* GetSamplesCounts(
-      const SampleVectorBase& samples) {
-    return samples.counts();
-  }
-};
-
-TEST_F(SampleVectorTest, Accumulate) {
+TEST(SampleVectorTest, AccumulateTest) {
   // Custom buckets: [1, 5) [5, 10)
   BucketRanges ranges(3);
   ranges.set_range(0, 1);
@@ -56,7 +45,7 @@
   EXPECT_EQ(samples.TotalCount(), samples.redundant_count());
 }
 
-TEST_F(SampleVectorTest, Accumulate_LargeValuesDontOverflow) {
+TEST(SampleVectorTest, Accumulate_LargeValuesDontOverflow) {
   // Custom buckets: [1, 250000000) [250000000, 500000000)
   BucketRanges ranges(3);
   ranges.set_range(0, 1);
@@ -83,7 +72,7 @@
   EXPECT_EQ(samples.TotalCount(), samples.redundant_count());
 }
 
-TEST_F(SampleVectorTest, AddSubtract) {
+TEST(SampleVectorTest, AddSubtractTest) {
   // Custom buckets: [0, 1) [1, 2) [2, 3) [3, INT_MAX)
   BucketRanges ranges(5);
   ranges.set_range(0, 0);
@@ -127,7 +116,7 @@
   EXPECT_EQ(samples1.redundant_count(), samples1.TotalCount());
 }
 
-TEST_F(SampleVectorTest, BucketIndexDeath) {
+TEST(SampleVectorDeathTest, BucketIndexTest) {
   // 8 buckets with exponential layout:
   // [0, 1) [1, 2) [2, 4) [4, 8) [8, 16) [16, 32) [32, 64) [64, INT_MAX)
   BucketRanges ranges(9);
@@ -169,7 +158,7 @@
   EXPECT_DCHECK_DEATH(samples2.Accumulate(10, 100));
 }
 
-TEST_F(SampleVectorTest, AddSubtractBucketNotMatchDeath) {
+TEST(SampleVectorDeathTest, AddSubtractBucketNotMatchTest) {
   // Custom buckets 1: [1, 3) [3, 5)
   BucketRanges ranges1(3);
   ranges1.set_range(0, 1);
@@ -208,7 +197,7 @@
   EXPECT_DCHECK_DEATH(samples1.Subtract(samples2));
 }
 
-TEST_F(SampleVectorTest, Iterate) {
+TEST(SampleVectorIteratorTest, IterateTest) {
   BucketRanges ranges(5);
   ranges.set_range(0, 0);
   ranges.set_range(1, 1);
@@ -268,7 +257,7 @@
   EXPECT_EQ(4, i);
 }
 
-TEST_F(SampleVectorTest, IterateDoneDeath) {
+TEST(SampleVectorIteratorDeathTest, IterateDoneTest) {
   BucketRanges ranges(5);
   ranges.set_range(0, 0);
   ranges.set_range(1, 1);
@@ -293,251 +282,5 @@
   EXPECT_FALSE(it->Done());
 }
 
-TEST_F(SampleVectorTest, SingleSample) {
-  // Custom buckets: [1, 5) [5, 10)
-  BucketRanges ranges(3);
-  ranges.set_range(0, 1);
-  ranges.set_range(1, 5);
-  ranges.set_range(2, 10);
-  SampleVector samples(&ranges);
-
-  // Ensure that a single value accumulates correctly.
-  EXPECT_FALSE(GetSamplesCounts(samples));
-  samples.Accumulate(3, 200);
-  EXPECT_EQ(200, samples.GetCount(3));
-  EXPECT_FALSE(GetSamplesCounts(samples));
-  samples.Accumulate(3, 400);
-  EXPECT_EQ(600, samples.GetCount(3));
-  EXPECT_FALSE(GetSamplesCounts(samples));
-  EXPECT_EQ(3 * 600, samples.sum());
-  EXPECT_EQ(600, samples.TotalCount());
-  EXPECT_EQ(600, samples.redundant_count());
-
-  // Ensure that the iterator returns only one value.
-  HistogramBase::Sample min;
-  HistogramBase::Sample max;
-  HistogramBase::Count count;
-  std::unique_ptr<SampleCountIterator> it = samples.Iterator();
-  ASSERT_FALSE(it->Done());
-  it->Get(&min, &max, &count);
-  EXPECT_EQ(1, min);
-  EXPECT_EQ(5, max);
-  EXPECT_EQ(600, count);
-  it->Next();
-  EXPECT_TRUE(it->Done());
-
-  // Ensure that it can be merged to another single-sample vector.
-  SampleVector samples_copy(&ranges);
-  samples_copy.Add(samples);
-  EXPECT_FALSE(GetSamplesCounts(samples_copy));
-  EXPECT_EQ(3 * 600, samples_copy.sum());
-  EXPECT_EQ(600, samples_copy.TotalCount());
-  EXPECT_EQ(600, samples_copy.redundant_count());
-
-  // A different value should cause creation of the counts array.
-  samples.Accumulate(8, 100);
-  EXPECT_TRUE(GetSamplesCounts(samples));
-  EXPECT_EQ(600, samples.GetCount(3));
-  EXPECT_EQ(100, samples.GetCount(8));
-  EXPECT_EQ(3 * 600 + 8 * 100, samples.sum());
-  EXPECT_EQ(600 + 100, samples.TotalCount());
-  EXPECT_EQ(600 + 100, samples.redundant_count());
-
-  // The iterator should now return both values.
-  it = samples.Iterator();
-  ASSERT_FALSE(it->Done());
-  it->Get(&min, &max, &count);
-  EXPECT_EQ(1, min);
-  EXPECT_EQ(5, max);
-  EXPECT_EQ(600, count);
-  it->Next();
-  ASSERT_FALSE(it->Done());
-  it->Get(&min, &max, &count);
-  EXPECT_EQ(5, min);
-  EXPECT_EQ(10, max);
-  EXPECT_EQ(100, count);
-  it->Next();
-  EXPECT_TRUE(it->Done());
-
-  // Ensure that it can merged to a single-sample vector.
-  samples_copy.Add(samples);
-  EXPECT_TRUE(GetSamplesCounts(samples_copy));
-  EXPECT_EQ(3 * 1200 + 8 * 100, samples_copy.sum());
-  EXPECT_EQ(1200 + 100, samples_copy.TotalCount());
-  EXPECT_EQ(1200 + 100, samples_copy.redundant_count());
-}
-
-TEST_F(SampleVectorTest, PersistentSampleVector) {
-  LocalPersistentMemoryAllocator allocator(64 << 10, 0, "");
-  std::atomic<PersistentMemoryAllocator::Reference> samples_ref;
-  samples_ref.store(0, std::memory_order_relaxed);
-  HistogramSamples::Metadata samples_meta;
-  memset(&samples_meta, 0, sizeof(samples_meta));
-
-  // Custom buckets: [1, 5) [5, 10)
-  BucketRanges ranges(3);
-  ranges.set_range(0, 1);
-  ranges.set_range(1, 5);
-  ranges.set_range(2, 10);
-
-  // Persistent allocation.
-  const size_t counts_bytes =
-      sizeof(HistogramBase::AtomicCount) * ranges.bucket_count();
-  const DelayedPersistentAllocation allocation(&allocator, &samples_ref, 1,
-                                               counts_bytes, false);
-
-  PersistentSampleVector samples1(0, &ranges, &samples_meta, allocation);
-  EXPECT_FALSE(GetSamplesCounts(samples1));
-  samples1.Accumulate(3, 200);
-  EXPECT_EQ(200, samples1.GetCount(3));
-  EXPECT_FALSE(GetSamplesCounts(samples1));
-  EXPECT_EQ(0, samples1.GetCount(8));
-  EXPECT_FALSE(GetSamplesCounts(samples1));
-
-  PersistentSampleVector samples2(0, &ranges, &samples_meta, allocation);
-  EXPECT_EQ(200, samples2.GetCount(3));
-  EXPECT_FALSE(GetSamplesCounts(samples2));
-
-  HistogramBase::Sample min;
-  HistogramBase::Sample max;
-  HistogramBase::Count count;
-  std::unique_ptr<SampleCountIterator> it = samples2.Iterator();
-  ASSERT_FALSE(it->Done());
-  it->Get(&min, &max, &count);
-  EXPECT_EQ(1, min);
-  EXPECT_EQ(5, max);
-  EXPECT_EQ(200, count);
-  it->Next();
-  EXPECT_TRUE(it->Done());
-
-  samples1.Accumulate(8, 100);
-  EXPECT_TRUE(GetSamplesCounts(samples1));
-
-  EXPECT_FALSE(GetSamplesCounts(samples2));
-  EXPECT_EQ(200, samples2.GetCount(3));
-  EXPECT_EQ(100, samples2.GetCount(8));
-  EXPECT_TRUE(GetSamplesCounts(samples2));
-  EXPECT_EQ(3 * 200 + 8 * 100, samples2.sum());
-  EXPECT_EQ(300, samples2.TotalCount());
-  EXPECT_EQ(300, samples2.redundant_count());
-
-  it = samples2.Iterator();
-  ASSERT_FALSE(it->Done());
-  it->Get(&min, &max, &count);
-  EXPECT_EQ(1, min);
-  EXPECT_EQ(5, max);
-  EXPECT_EQ(200, count);
-  it->Next();
-  ASSERT_FALSE(it->Done());
-  it->Get(&min, &max, &count);
-  EXPECT_EQ(5, min);
-  EXPECT_EQ(10, max);
-  EXPECT_EQ(100, count);
-  it->Next();
-  EXPECT_TRUE(it->Done());
-
-  PersistentSampleVector samples3(0, &ranges, &samples_meta, allocation);
-  EXPECT_TRUE(GetSamplesCounts(samples2));
-  EXPECT_EQ(200, samples3.GetCount(3));
-  EXPECT_EQ(100, samples3.GetCount(8));
-  EXPECT_EQ(3 * 200 + 8 * 100, samples3.sum());
-  EXPECT_EQ(300, samples3.TotalCount());
-  EXPECT_EQ(300, samples3.redundant_count());
-
-  it = samples3.Iterator();
-  ASSERT_FALSE(it->Done());
-  it->Get(&min, &max, &count);
-  EXPECT_EQ(1, min);
-  EXPECT_EQ(5, max);
-  EXPECT_EQ(200, count);
-  it->Next();
-  ASSERT_FALSE(it->Done());
-  it->Get(&min, &max, &count);
-  EXPECT_EQ(5, min);
-  EXPECT_EQ(10, max);
-  EXPECT_EQ(100, count);
-  it->Next();
-  EXPECT_TRUE(it->Done());
-}
-
-TEST_F(SampleVectorTest, PersistentSampleVectorTestWithOutsideAlloc) {
-  LocalPersistentMemoryAllocator allocator(64 << 10, 0, "");
-  std::atomic<PersistentMemoryAllocator::Reference> samples_ref;
-  samples_ref.store(0, std::memory_order_relaxed);
-  HistogramSamples::Metadata samples_meta;
-  memset(&samples_meta, 0, sizeof(samples_meta));
-
-  // Custom buckets: [1, 5) [5, 10)
-  BucketRanges ranges(3);
-  ranges.set_range(0, 1);
-  ranges.set_range(1, 5);
-  ranges.set_range(2, 10);
-
-  // Persistent allocation.
-  const size_t counts_bytes =
-      sizeof(HistogramBase::AtomicCount) * ranges.bucket_count();
-  const DelayedPersistentAllocation allocation(&allocator, &samples_ref, 1,
-                                               counts_bytes, false);
-
-  PersistentSampleVector samples1(0, &ranges, &samples_meta, allocation);
-  EXPECT_FALSE(GetSamplesCounts(samples1));
-  samples1.Accumulate(3, 200);
-  EXPECT_EQ(200, samples1.GetCount(3));
-  EXPECT_FALSE(GetSamplesCounts(samples1));
-
-  // Because the delayed allocation can be shared with other objects (the
-  // |offset| parameter allows concatinating multiple data blocks into the
-  // same allocation), it's possible that the allocation gets realized from
-  // the outside even though the data block being accessed is all zero.
-  allocation.Get();
-  EXPECT_EQ(200, samples1.GetCount(3));
-  EXPECT_FALSE(GetSamplesCounts(samples1));
-
-  HistogramBase::Sample min;
-  HistogramBase::Sample max;
-  HistogramBase::Count count;
-  std::unique_ptr<SampleCountIterator> it = samples1.Iterator();
-  ASSERT_FALSE(it->Done());
-  it->Get(&min, &max, &count);
-  EXPECT_EQ(1, min);
-  EXPECT_EQ(5, max);
-  EXPECT_EQ(200, count);
-  it->Next();
-  EXPECT_TRUE(it->Done());
-
-  // A duplicate samples object should still see the single-sample entry even
-  // when storage is available.
-  PersistentSampleVector samples2(0, &ranges, &samples_meta, allocation);
-  EXPECT_EQ(200, samples2.GetCount(3));
-
-  // New accumulations, in both directions, of the existing value should work.
-  samples1.Accumulate(3, 50);
-  EXPECT_EQ(250, samples1.GetCount(3));
-  EXPECT_EQ(250, samples2.GetCount(3));
-  samples2.Accumulate(3, 50);
-  EXPECT_EQ(300, samples1.GetCount(3));
-  EXPECT_EQ(300, samples2.GetCount(3));
-
-  it = samples1.Iterator();
-  ASSERT_FALSE(it->Done());
-  it->Get(&min, &max, &count);
-  EXPECT_EQ(1, min);
-  EXPECT_EQ(5, max);
-  EXPECT_EQ(300, count);
-  it->Next();
-  EXPECT_TRUE(it->Done());
-
-  samples1.Accumulate(8, 100);
-  EXPECT_TRUE(GetSamplesCounts(samples1));
-  EXPECT_EQ(300, samples1.GetCount(3));
-  EXPECT_EQ(300, samples2.GetCount(3));
-  EXPECT_EQ(100, samples1.GetCount(8));
-  EXPECT_EQ(100, samples2.GetCount(8));
-  samples2.Accumulate(8, 100);
-  EXPECT_EQ(300, samples1.GetCount(3));
-  EXPECT_EQ(300, samples2.GetCount(3));
-  EXPECT_EQ(200, samples1.GetCount(8));
-  EXPECT_EQ(200, samples2.GetCount(8));
-}
-
+}  // namespace
 }  // namespace base
diff --git a/build/android/pylib/local/device/local_device_instrumentation_test_run.py b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
index 958163f..ec695f5 100644
--- a/build/android/pylib/local/device/local_device_instrumentation_test_run.py
+++ b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
@@ -76,11 +76,21 @@
     @trace_event.traced
     def individual_device_set_up(dev, host_device_tuples):
       steps = []
+
       def install_helper(apk, permissions):
-        return lambda: dev.Install(apk, permissions=permissions)
+        @trace_event.traced("apk_path")
+        def install_helper_internal(apk_path=apk.path):
+          # pylint: disable=unused-argument
+          dev.Install(apk, permissions=permissions)
+        return install_helper_internal
+
       def incremental_install_helper(dev, apk, script):
-        return lambda: local_device_test_run.IncrementalInstall(
-                           dev, apk, script)
+        @trace_event.traced("apk_path")
+        def incremental_install_helper_internal(apk_path=apk.path):
+          # pylint: disable=unused-argument
+          local_device_test_run.IncrementalInstall(
+              dev, apk, script)
+        return incremental_install_helper_internal
 
       if self._test_instance.apk_under_test:
         if self._test_instance.apk_under_test_incremental_install_script:
@@ -108,6 +118,7 @@
       steps.extend(install_helper(apk, None)
                    for apk in self._test_instance.additional_apks)
 
+      @trace_event.traced
       def set_debug_app():
         # Set debug app in order to enable reading command line flags on user
         # builds
@@ -120,7 +131,7 @@
             dev.RunShellCommand(['am', 'set-debug-app', '--persistent',
                                   self._test_instance.package_info.package],
                                 check_return=True)
-
+      @trace_event.traced
       def edit_shared_prefs():
         for pref in self._test_instance.edit_shared_prefs:
           prefs = shared_prefs.SharedPrefs(dev, pref['package'],
@@ -145,6 +156,7 @@
                   str(type(value)), key))
           prefs.Commit()
 
+      @trace_event.traced
       def push_test_data():
         device_root = posixpath.join(dev.GetExternalStoragePath(),
                                      'chromium_tests_root')
@@ -160,6 +172,7 @@
           dev.RunShellCommand(['rm', '-rf', device_root], check_return=True)
           dev.RunShellCommand(['mkdir', '-p', device_root], check_return=True)
 
+      @trace_event.traced
       def create_flag_changer():
         if self._test_instance.flags:
           if not self._test_instance.package_info:
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index f7918544..f925333 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -566,6 +566,8 @@
     "test/fake_scoped_ui_resource.h",
     "test/fake_scrollbar.cc",
     "test/fake_scrollbar.h",
+    "test/fake_surface_resource_holder_client.cc",
+    "test/fake_surface_resource_holder_client.h",
     "test/fake_tile_manager.cc",
     "test/fake_tile_manager.h",
     "test/fake_tile_manager_client.cc",
@@ -620,6 +622,7 @@
     "test/stub_layer_tree_host_client.h",
     "test/stub_layer_tree_host_single_thread_client.cc",
     "test/stub_layer_tree_host_single_thread_client.h",
+    "test/stub_surface_factory_client.h",
     "test/surface_aggregator_test_helpers.cc",
     "test/surface_aggregator_test_helpers.h",
     "test/surface_hittest_test_helpers.cc",
diff --git a/cc/surfaces/BUILD.gn b/cc/surfaces/BUILD.gn
index 3ba54806..118e68e 100644
--- a/cc/surfaces/BUILD.gn
+++ b/cc/surfaces/BUILD.gn
@@ -47,6 +47,7 @@
     "display_scheduler.h",
     "framesink_manager.cc",
     "framesink_manager.h",
+    "framesink_manager_client.h",
     "local_surface_id_allocator.cc",
     "local_surface_id_allocator.h",
     "pending_frame_observer.h",
@@ -70,6 +71,7 @@
     "surface_manager.h",
     "surface_resource_holder.cc",
     "surface_resource_holder.h",
+    "surface_resource_holder_client.h",
     "surfaces_export.h",
   ]
 
diff --git a/cc/surfaces/compositor_frame_sink_support.cc b/cc/surfaces/compositor_frame_sink_support.cc
index 1db8d8f..7fc9e65 100644
--- a/cc/surfaces/compositor_frame_sink_support.cc
+++ b/cc/surfaces/compositor_frame_sink_support.cc
@@ -47,7 +47,7 @@
   // call back into here and access |client_| so we should destroy
   // |surface_factory_|'s resources early on.
   surface_factory_->EvictSurface();
-  surface_manager_->UnregisterSurfaceFactoryClient(frame_sink_id_);
+  surface_manager_->UnregisterFrameSinkManagerClient(frame_sink_id_);
   if (handles_frame_sink_id_invalidation_)
     surface_manager_->InvalidateFrameSinkId(frame_sink_id_);
 }
@@ -93,13 +93,6 @@
   UpdateNeedsBeginFramesInternal();
 }
 
-void CompositorFrameSinkSupport::WillDrawSurface(
-    const LocalSurfaceId& local_surface_id,
-    const gfx::Rect& damage_rect) {
-  if (client_)
-    client_->WillDrawSurface(local_surface_id, damage_rect);
-}
-
 void CompositorFrameSinkSupport::EvictFrame() {
   DCHECK(surface_factory_);
   surface_factory_->EvictSurface();
@@ -213,6 +206,13 @@
   surface_returned_resources_.clear();
 }
 
+void CompositorFrameSinkSupport::WillDrawSurface(
+    const LocalSurfaceId& local_surface_id,
+    const gfx::Rect& damage_rect) {
+  if (client_)
+    client_->WillDrawSurface(local_surface_id, damage_rect);
+}
+
 void CompositorFrameSinkSupport::ForceReclaimResources() {
   DCHECK(surface_factory_);
   surface_factory_->ClearSurface();
@@ -238,11 +238,11 @@
 void CompositorFrameSinkSupport::Init(SurfaceManager* surface_manager,
                                       bool needs_sync_points) {
   surface_manager_ = surface_manager;
-  surface_factory_ =
-      base::MakeUnique<SurfaceFactory>(frame_sink_id_, surface_manager_, this);
+  surface_factory_ = base::MakeUnique<SurfaceFactory>(
+      frame_sink_id_, surface_manager_, this, this);
   if (handles_frame_sink_id_invalidation_)
     surface_manager_->RegisterFrameSinkId(frame_sink_id_);
-  surface_manager_->RegisterSurfaceFactoryClient(frame_sink_id_, this);
+  surface_manager_->RegisterFrameSinkManagerClient(frame_sink_id_, this);
   surface_factory_->set_needs_sync_points(needs_sync_points);
 }
 
diff --git a/cc/surfaces/compositor_frame_sink_support.h b/cc/surfaces/compositor_frame_sink_support.h
index 977acdf..75f5605d 100644
--- a/cc/surfaces/compositor_frame_sink_support.h
+++ b/cc/surfaces/compositor_frame_sink_support.h
@@ -13,10 +13,12 @@
 #include "base/memory/weak_ptr.h"
 #include "cc/output/compositor_frame.h"
 #include "cc/scheduler/begin_frame_source.h"
+#include "cc/surfaces/framesink_manager_client.h"
 #include "cc/surfaces/referenced_surface_tracker.h"
 #include "cc/surfaces/surface_factory.h"
 #include "cc/surfaces/surface_factory_client.h"
 #include "cc/surfaces/surface_id.h"
+#include "cc/surfaces/surface_resource_holder_client.h"
 #include "cc/surfaces/surfaces_export.h"
 
 namespace cc {
@@ -26,7 +28,9 @@
 
 class CC_SURFACES_EXPORT CompositorFrameSinkSupport
     : public SurfaceFactoryClient,
-      public BeginFrameObserver {
+      public BeginFrameObserver,
+      public SurfaceResourceHolderClient,
+      public FrameSinkManagerClient {
  public:
   static std::unique_ptr<CompositorFrameSinkSupport> Create(
       CompositorFrameSinkSupportClient* client,
@@ -52,10 +56,12 @@
   void ReferencedSurfacesChanged(
       const LocalSurfaceId& local_surface_id,
       const std::vector<SurfaceId>* active_referenced_surfaces) override;
+
+  // SurfaceResourceHolderClient implementation.
   void ReturnResources(const ReturnedResourceArray& resources) override;
+
+  // FrameSinkManagerClient implementation.
   void SetBeginFrameSource(BeginFrameSource* begin_frame_source) override;
-  void WillDrawSurface(const LocalSurfaceId& local_surface_id,
-                       const gfx::Rect& damage_rect) override;
 
   void EvictFrame();
   void SetNeedsBeginFrame(bool needs_begin_frame);
@@ -86,6 +92,8 @@
   void RemoveTopLevelRootReference(const SurfaceId& surface_id);
 
   void DidReceiveCompositorFrameAck();
+  void WillDrawSurface(const LocalSurfaceId& local_surface_id,
+                       const gfx::Rect& damage_rect);
 
   // BeginFrameObserver implementation.
   void OnBeginFrame(const BeginFrameArgs& args) override;
diff --git a/cc/surfaces/framesink_manager.cc b/cc/surfaces/framesink_manager.cc
index d8fad7e..8095f62 100644
--- a/cc/surfaces/framesink_manager.cc
+++ b/cc/surfaces/framesink_manager.cc
@@ -8,6 +8,7 @@
 #include <stdint.h>
 
 #include "base/logging.h"
+#include "cc/surfaces/framesink_manager_client.h"
 #include "cc/surfaces/surface_factory_client.h"
 
 #if DCHECK_IS_ON()
@@ -43,9 +44,9 @@
   valid_frame_sink_ids_.erase(frame_sink_id);
 }
 
-void FrameSinkManager::RegisterSurfaceFactoryClient(
+void FrameSinkManager::RegisterFrameSinkManagerClient(
     const FrameSinkId& frame_sink_id,
-    SurfaceFactoryClient* client) {
+    FrameSinkManagerClient* client) {
   DCHECK(client);
   DCHECK_EQ(valid_frame_sink_ids_.count(frame_sink_id), 1u);
 
@@ -58,7 +59,7 @@
   }
 }
 
-void FrameSinkManager::UnregisterSurfaceFactoryClient(
+void FrameSinkManager::UnregisterFrameSinkManagerClient(
     const FrameSinkId& frame_sink_id) {
   DCHECK_EQ(valid_frame_sink_ids_.count(frame_sink_id), 1u);
   auto client_iter = clients_.find(frame_sink_id);
diff --git a/cc/surfaces/framesink_manager.h b/cc/surfaces/framesink_manager.h
index 087126c..bcecb8a 100644
--- a/cc/surfaces/framesink_manager.h
+++ b/cc/surfaces/framesink_manager.h
@@ -18,7 +18,7 @@
 
 namespace cc {
 class BeginFrameSource;
-class SurfaceFactoryClient;
+class FrameSinkManagerClient;
 
 namespace test {
 class CompositorFrameSinkSupportTest;
@@ -43,12 +43,12 @@
   // However, DelegatedFrameHost can register itself as a client before its
   // relationship with the ui::Compositor is known.
 
-  // Associates a SurfaceFactoryClient with the  frame_sink_id it  uses.
-  // SurfaceFactoryClient and framesink allocators have a 1:1 mapping.
+  // Associates a FrameSinkManagerClient with the frame_sink_id it uses.
+  // FrameSinkManagerClient and framesink allocators have a 1:1 mapping.
   // Caller guarantees the client is alive between register/unregister.
-  void RegisterSurfaceFactoryClient(const FrameSinkId& frame_sink_id,
-                                    SurfaceFactoryClient* client);
-  void UnregisterSurfaceFactoryClient(const FrameSinkId& frame_sink_id);
+  void RegisterFrameSinkManagerClient(const FrameSinkId& frame_sink_id,
+                                      FrameSinkManagerClient* client);
+  void UnregisterFrameSinkManagerClient(const FrameSinkId& frame_sink_id);
 
   // Associates a |source| with a particular framesink.  That framesink and
   // any children of that framesink with valid clients can potentially use
@@ -103,7 +103,7 @@
     std::vector<FrameSinkId> children;
   };
 
-  std::unordered_map<FrameSinkId, SurfaceFactoryClient*, FrameSinkIdHash>
+  std::unordered_map<FrameSinkId, FrameSinkManagerClient*, FrameSinkIdHash>
       clients_;
 
   std::unordered_map<FrameSinkId, FrameSinkSourceMapping, FrameSinkIdHash>
diff --git a/cc/surfaces/framesink_manager_client.h b/cc/surfaces/framesink_manager_client.h
new file mode 100644
index 0000000..18edf9bb
--- /dev/null
+++ b/cc/surfaces/framesink_manager_client.h
@@ -0,0 +1,22 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef CC_SURFACES_FRAMESINK_MANAGER_CLIENT_H_
+#define CC_SURFACES_FRAMESINK_MANAGER_CLIENT_H_
+
+#include "cc/scheduler/begin_frame_source.h"
+#include "cc/surfaces/surfaces_export.h"
+
+namespace cc {
+
+class CC_SURFACES_EXPORT FrameSinkManagerClient {
+ public:
+  virtual ~FrameSinkManagerClient() = default;
+
+  // This allows the FrameSinkManager to pass a BeginFrameSource to use.
+  virtual void SetBeginFrameSource(BeginFrameSource* begin_frame_source) = 0;
+};
+
+}  // namespace cc
+
+#endif  // CC_SURFACES_FRAMESINK_MANAGER_CLIENT_H_
diff --git a/cc/surfaces/surface_aggregator_perftest.cc b/cc/surfaces/surface_aggregator_perftest.cc
index 9743b20..9f5e093 100644
--- a/cc/surfaces/surface_aggregator_perftest.cc
+++ b/cc/surfaces/surface_aggregator_perftest.cc
@@ -7,12 +7,14 @@
 #include "cc/output/compositor_frame.h"
 #include "cc/quads/surface_draw_quad.h"
 #include "cc/quads/texture_draw_quad.h"
+#include "cc/surfaces/framesink_manager_client.h"
 #include "cc/surfaces/surface_aggregator.h"
 #include "cc/surfaces/surface_factory.h"
-#include "cc/surfaces/surface_factory_client.h"
 #include "cc/surfaces/surface_manager.h"
+#include "cc/surfaces/surface_resource_holder_client.h"
 #include "cc/test/fake_output_surface_client.h"
 #include "cc/test/fake_resource_provider.h"
+#include "cc/test/stub_surface_factory_client.h"
 #include "cc/test/test_context_provider.h"
 #include "cc/test/test_shared_bitmap_manager.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -24,10 +26,12 @@
 static const base::UnguessableToken kArbitraryToken =
     base::UnguessableToken::Create();
 
-class EmptySurfaceFactoryClient : public SurfaceFactoryClient {
+class StubSurfaceResourceHolderClient : public SurfaceResourceHolderClient {
  public:
+  StubSurfaceResourceHolderClient() = default;
+  ~StubSurfaceResourceHolderClient() override = default;
+
   void ReturnResources(const ReturnedResourceArray& resources) override {}
-  void SetBeginFrameSource(BeginFrameSource* begin_frame_source) override {}
 };
 
 class SurfaceAggregatorPerfTest : public testing::Test {
@@ -49,8 +53,9 @@
                const std::string& name) {
     std::vector<std::unique_ptr<SurfaceFactory>> child_factories(num_surfaces);
     for (int i = 0; i < num_surfaces; i++)
-      child_factories[i].reset(
-          new SurfaceFactory(FrameSinkId(1, i + 1), &manager_, &empty_client_));
+      child_factories[i].reset(new SurfaceFactory(
+          FrameSinkId(1, i + 1), &manager_, &stub_surface_factory_client_,
+          &stub_surface_resource_holder_client_));
     aggregator_.reset(new SurfaceAggregator(&manager_, resource_provider_.get(),
                                             optimize_damage));
     for (int i = 0; i < num_surfaces; i++) {
@@ -103,7 +108,8 @@
     }
 
     SurfaceFactory root_factory(FrameSinkId(1, num_surfaces + 1), &manager_,
-                                &empty_client_);
+                                &stub_surface_factory_client_,
+                                &stub_surface_resource_holder_client_);
     timer_.Reset();
     do {
       std::unique_ptr<RenderPass> pass(RenderPass::Create());
@@ -144,7 +150,8 @@
 
  protected:
   SurfaceManager manager_;
-  EmptySurfaceFactoryClient empty_client_;
+  StubSurfaceResourceHolderClient stub_surface_resource_holder_client_;
+  StubSurfaceFactoryClient stub_surface_factory_client_;
   scoped_refptr<TestContextProvider> context_provider_;
   std::unique_ptr<SharedBitmapManager> shared_bitmap_manager_;
   std::unique_ptr<ResourceProvider> resource_provider_;
diff --git a/cc/surfaces/surface_factory.cc b/cc/surfaces/surface_factory.cc
index 1a38e51..76167c9f 100644
--- a/cc/surfaces/surface_factory.cc
+++ b/cc/surfaces/surface_factory.cc
@@ -17,13 +17,15 @@
 #include "ui/gfx/geometry/size.h"
 
 namespace cc {
-SurfaceFactory::SurfaceFactory(const FrameSinkId& frame_sink_id,
-                               SurfaceManager* manager,
-                               SurfaceFactoryClient* client)
+SurfaceFactory::SurfaceFactory(
+    const FrameSinkId& frame_sink_id,
+    SurfaceManager* manager,
+    SurfaceFactoryClient* client,
+    SurfaceResourceHolderClient* resource_holder_client)
     : frame_sink_id_(frame_sink_id),
       manager_(manager),
       client_(client),
-      holder_(client),
+      holder_(resource_holder_client),
       needs_sync_points_(true),
       weak_factory_(this) {}
 
diff --git a/cc/surfaces/surface_factory.h b/cc/surfaces/surface_factory.h
index 7bec951..0a206e6 100644
--- a/cc/surfaces/surface_factory.h
+++ b/cc/surfaces/surface_factory.h
@@ -41,7 +41,8 @@
 
   SurfaceFactory(const FrameSinkId& frame_sink_id,
                  SurfaceManager* manager,
-                 SurfaceFactoryClient* client);
+                 SurfaceFactoryClient* client,
+                 SurfaceResourceHolderClient* resource_holder_client);
   ~SurfaceFactory() override;
 
   const FrameSinkId& frame_sink_id() const { return frame_sink_id_; }
diff --git a/cc/surfaces/surface_factory_client.h b/cc/surfaces/surface_factory_client.h
index 57ecfed1..bbcf545b 100644
--- a/cc/surfaces/surface_factory_client.h
+++ b/cc/surfaces/surface_factory_client.h
@@ -12,24 +12,15 @@
 
 namespace cc {
 
-class BeginFrameSource;
 class SurfaceId;
 
 class CC_SURFACES_EXPORT SurfaceFactoryClient {
  public:
-  virtual ~SurfaceFactoryClient() {}
+  virtual ~SurfaceFactoryClient() = default;
 
   virtual void ReferencedSurfacesChanged(
       const LocalSurfaceId& local_surface_id,
-      const std::vector<SurfaceId>* active_referenced_surfaces) {}
-
-  virtual void ReturnResources(const ReturnedResourceArray& resources) = 0;
-
-  virtual void WillDrawSurface(const LocalSurfaceId& local_surface_id,
-                               const gfx::Rect& damage_rect) {}
-
-  // This allows the SurfaceFactory to pass a BeginFrameSource to use.
-  virtual void SetBeginFrameSource(BeginFrameSource* begin_frame_source) = 0;
+      const std::vector<SurfaceId>* active_referenced_surfaces) = 0;
 };
 
 }  // namespace cc
diff --git a/cc/surfaces/surface_factory_unittest.cc b/cc/surfaces/surface_factory_unittest.cc
index 9052801..d942f7c 100644
--- a/cc/surfaces/surface_factory_unittest.cc
+++ b/cc/surfaces/surface_factory_unittest.cc
@@ -16,11 +16,15 @@
 #include "cc/output/copy_output_request.h"
 #include "cc/output/copy_output_result.h"
 #include "cc/resources/resource_provider.h"
+#include "cc/surfaces/framesink_manager_client.h"
 #include "cc/surfaces/surface.h"
 #include "cc/surfaces/surface_factory_client.h"
 #include "cc/surfaces/surface_info.h"
 #include "cc/surfaces/surface_manager.h"
+#include "cc/surfaces/surface_resource_holder_client.h"
+#include "cc/test/fake_surface_resource_holder_client.h"
 #include "cc/test/scheduler_test_common.h"
+#include "cc/test/stub_surface_factory_client.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gfx/geometry/size.h"
 
@@ -36,35 +40,6 @@
 static auto kArbitrarySourceId2 =
     base::UnguessableToken::Deserialize(0xdead, 0xbee0);
 
-class TestSurfaceFactoryClient : public SurfaceFactoryClient {
- public:
-  TestSurfaceFactoryClient() : begin_frame_source_(nullptr) {}
-  ~TestSurfaceFactoryClient() override {}
-
-  void ReturnResources(const ReturnedResourceArray& resources) override {
-    returned_resources_.insert(
-        returned_resources_.end(), resources.begin(), resources.end());
-  }
-
-  void SetBeginFrameSource(BeginFrameSource* begin_frame_source) override {
-    begin_frame_source_ = begin_frame_source;
-  }
-
-  const ReturnedResourceArray& returned_resources() const {
-    return returned_resources_;
-  }
-
-  void clear_returned_resources() { returned_resources_.clear(); }
-
-  BeginFrameSource* begin_frame_source() const { return begin_frame_source_; }
-
- private:
-  ReturnedResourceArray returned_resources_;
-  BeginFrameSource* begin_frame_source_;
-
-  DISALLOW_COPY_AND_ASSIGN(TestSurfaceFactoryClient);
-};
-
 gpu::SyncToken GenTestSyncToken(int id) {
   gpu::SyncToken token;
   token.Set(gpu::CommandBufferNamespace::GPU_IO, 0,
@@ -75,8 +50,10 @@
 class SurfaceFactoryTest : public testing::Test, public SurfaceObserver {
  public:
   SurfaceFactoryTest()
-      : factory_(
-            new SurfaceFactory(kArbitraryFrameSinkId, &manager_, &client_)),
+      : factory_(new SurfaceFactory(kArbitraryFrameSinkId,
+                                    &manager_,
+                                    &stub_surface_factory_client_,
+                                    &fake_surface_resource_holder_client_)),
         local_surface_id_(3, kArbitraryToken),
         frame_sync_token_(GenTestSyncToken(4)),
         consumer_sync_token_(GenTestSyncToken(5)) {
@@ -138,7 +115,7 @@
                                            size_t expected_resources,
                                            gpu::SyncToken expected_sync_token) {
     const ReturnedResourceArray& actual_resources =
-        client_.returned_resources();
+        fake_surface_resource_holder_client_.returned_resources();
     ASSERT_EQ(expected_resources, actual_resources.size());
     for (size_t i = 0; i < expected_resources; ++i) {
       ReturnedResource resource = actual_resources[i];
@@ -146,7 +123,7 @@
       EXPECT_EQ(expected_returned_ids[i], resource.id);
       EXPECT_EQ(expected_returned_counts[i], resource.count);
     }
-    client_.clear_returned_resources();
+    fake_surface_resource_holder_client_.clear_returned_resources();
   }
 
   void RefCurrentFrameResources() {
@@ -157,7 +134,8 @@
 
  protected:
   SurfaceManager manager_;
-  TestSurfaceFactoryClient client_;
+  StubSurfaceFactoryClient stub_surface_factory_client_;
+  FakeSurfaceResourceHolderClient fake_surface_resource_holder_client_;
   std::unique_ptr<SurfaceFactory> factory_;
   LocalSurfaceId local_surface_id_;
   SurfaceId last_created_surface_id_;
@@ -182,8 +160,9 @@
   // All of the resources submitted in the first frame are still in use at this
   // time by virtue of being in the pending frame, so none can be returned to
   // the client yet.
-  EXPECT_EQ(0u, client_.returned_resources().size());
-  client_.clear_returned_resources();
+  EXPECT_EQ(0u,
+            fake_surface_resource_holder_client_.returned_resources().size());
+  fake_surface_resource_holder_client_.clear_returned_resources();
 
   // The second frame references no resources of first frame and thus should
   // make all resources of first frame available to be returned.
@@ -203,8 +182,9 @@
   // All of the resources submitted in the third frame are still in use at this
   // time by virtue of being in the pending frame, so none can be returned to
   // the client yet.
-  EXPECT_EQ(0u, client_.returned_resources().size());
-  client_.clear_returned_resources();
+  EXPECT_EQ(0u,
+            fake_surface_resource_holder_client_.returned_resources().size());
+  fake_surface_resource_holder_client_.clear_returned_resources();
 
   // The forth frame references no resources of third frame and thus should
   // make all resources of third frame available to be returned.
@@ -230,8 +210,9 @@
   // All of the resources submitted in the first frame are still in use at this
   // time by virtue of being in the pending frame, so none can be returned to
   // the client yet.
-  EXPECT_EQ(0u, client_.returned_resources().size());
-  client_.clear_returned_resources();
+  EXPECT_EQ(0u,
+            fake_surface_resource_holder_client_.returned_resources().size());
+  fake_surface_resource_holder_client_.clear_returned_resources();
 
   // Hold on to everything.
   RefCurrentFrameResources();
@@ -240,8 +221,9 @@
   // available to be returned as soon as the resource provider releases them.
   SubmitCompositorFrameWithResources(NULL, 0);
 
-  EXPECT_EQ(0u, client_.returned_resources().size());
-  client_.clear_returned_resources();
+  EXPECT_EQ(0u,
+            fake_surface_resource_holder_client_.returned_resources().size());
+  fake_surface_resource_holder_client_.clear_returned_resources();
 
   int release_counts[] = {1, 1, 1};
   UnrefResources(first_frame_ids, release_counts, arraysize(first_frame_ids));
@@ -273,7 +255,8 @@
   // Now it should be returned.
   // We don't care how many entries are in the returned array for 7, so long as
   // the total returned count matches the submitted count.
-  const ReturnedResourceArray& returned = client_.returned_resources();
+  const ReturnedResourceArray& returned =
+      fake_surface_resource_holder_client_.returned_resources();
   size_t return_count = 0;
   for (size_t i = 0; i < returned.size(); ++i) {
     EXPECT_EQ(7u, returned[i].id);
@@ -306,8 +289,9 @@
   // submitted resources.
   SubmitCompositorFrameWithResources(NULL, 0);
 
-  EXPECT_EQ(0u, client_.returned_resources().size());
-  client_.clear_returned_resources();
+  EXPECT_EQ(0u,
+            fake_surface_resource_holder_client_.returned_resources().size());
+  fake_surface_resource_holder_client_.clear_returned_resources();
 
   // Expected current refs:
   //  3 -> 2
@@ -319,8 +303,9 @@
     int counts[] = {1, 1, 1};
     UnrefResources(ids_to_unref, counts, arraysize(ids_to_unref));
 
-    EXPECT_EQ(0u, client_.returned_resources().size());
-    client_.clear_returned_resources();
+    EXPECT_EQ(0u,
+              fake_surface_resource_holder_client_.returned_resources().size());
+    fake_surface_resource_holder_client_.clear_returned_resources();
 
     UnrefResources(ids_to_unref, counts, arraysize(ids_to_unref));
 
@@ -371,8 +356,9 @@
   // All of the resources submitted in the first frame are still in use at this
   // time by virtue of being in the pending frame, so none can be returned to
   // the client yet.
-  EXPECT_EQ(0u, client_.returned_resources().size());
-  client_.clear_returned_resources();
+  EXPECT_EQ(0u,
+            fake_surface_resource_holder_client_.returned_resources().size());
+  fake_surface_resource_holder_client_.clear_returned_resources();
 
   // The second frame references some of the same resources, but some different
   // ones. We expect to receive back resource 1 with a count of 1 since it was
@@ -414,13 +400,15 @@
   SubmitCompositorFrameWithResources(fourth_frame_ids,
                                      arraysize(fourth_frame_ids));
 
-  EXPECT_EQ(0u, client_.returned_resources().size());
+  EXPECT_EQ(0u,
+            fake_surface_resource_holder_client_.returned_resources().size());
 
   RefCurrentFrameResources();
 
   // All resources are still being used by the external reference, so none can
   // be returned to the client.
-  EXPECT_EQ(0u, client_.returned_resources().size());
+  EXPECT_EQ(0u,
+            fake_surface_resource_holder_client_.returned_resources().size());
 
   // Release resources associated with the first RefCurrentFrameResources() call
   // first.
@@ -447,7 +435,8 @@
 
   // Resources 12 and 13 are still in use by the current frame, so they
   // shouldn't be available to be returned.
-  EXPECT_EQ(0u, client_.returned_resources().size());
+  EXPECT_EQ(0u,
+            fake_surface_resource_holder_client_.returned_resources().size());
 
   // If we submit an empty frame, however, they should become available.
   SubmitCompositorFrameWithResources(NULL, 0u);
@@ -520,10 +509,12 @@
   local_surface_id_ = LocalSurfaceId();
 
   EXPECT_TRUE(manager_.GetSurfaceForId(id));
-  EXPECT_TRUE(client_.returned_resources().empty());
+  EXPECT_TRUE(
+      fake_surface_resource_holder_client_.returned_resources().empty());
   factory_->EvictSurface();
   EXPECT_FALSE(manager_.GetSurfaceForId(id));
-  EXPECT_FALSE(client_.returned_resources().empty());
+  EXPECT_FALSE(
+      fake_surface_resource_holder_client_.returned_resources().empty());
   EXPECT_EQ(1u, execute_count);
 }
 
@@ -549,10 +540,12 @@
       SurfaceSequence(kAnotherArbitraryFrameSinkId, 4));
 
   EXPECT_TRUE(manager_.GetSurfaceForId(surface_id));
-  EXPECT_TRUE(client_.returned_resources().empty());
+  EXPECT_TRUE(
+      fake_surface_resource_holder_client_.returned_resources().empty());
   factory_->EvictSurface();
   EXPECT_FALSE(manager_.GetSurfaceForId(surface_id));
-  EXPECT_FALSE(client_.returned_resources().empty());
+  EXPECT_FALSE(
+      fake_surface_resource_holder_client_.returned_resources().empty());
   EXPECT_EQ(1u, execute_count);
 }
 
@@ -580,21 +573,25 @@
       SurfaceSequence(kAnotherArbitraryFrameSinkId, 4));
 
   EXPECT_TRUE(manager_.GetSurfaceForId(surface_id));
-  EXPECT_TRUE(client_.returned_resources().empty());
+  EXPECT_TRUE(
+      fake_surface_resource_holder_client_.returned_resources().empty());
   factory_->EvictSurface();
   EXPECT_TRUE(manager_.GetSurfaceForId(surface_id));
-  EXPECT_TRUE(client_.returned_resources().empty());
+  EXPECT_TRUE(
+      fake_surface_resource_holder_client_.returned_resources().empty());
   EXPECT_EQ(0u, execute_count);
 
   manager_.SatisfySequence(SurfaceSequence(kAnotherArbitraryFrameSinkId, 4));
   EXPECT_FALSE(manager_.GetSurfaceForId(surface_id));
-  EXPECT_FALSE(client_.returned_resources().empty());
+  EXPECT_FALSE(
+      fake_surface_resource_holder_client_.returned_resources().empty());
 }
 
 TEST_F(SurfaceFactoryTest, DestroySequence) {
   LocalSurfaceId local_surface_id2(5, kArbitraryToken);
-  std::unique_ptr<SurfaceFactory> factory2(
-      new SurfaceFactory(kArbitraryFrameSinkId, &manager_, &client_));
+  std::unique_ptr<SurfaceFactory> factory2(new SurfaceFactory(
+      kArbitraryFrameSinkId, &manager_, &stub_surface_factory_client_,
+      &fake_surface_resource_holder_client_));
   SurfaceId id2(kArbitraryFrameSinkId, local_surface_id2);
   factory2->SubmitCompositorFrame(local_surface_id2, CompositorFrame(),
                                   SurfaceFactory::DrawCallback(),
@@ -653,8 +650,9 @@
 TEST_F(SurfaceFactoryTest, DestroyCycle) {
   LocalSurfaceId local_surface_id2(5, kArbitraryToken);
   SurfaceId id2(kArbitraryFrameSinkId, local_surface_id2);
-  std::unique_ptr<SurfaceFactory> factory2(
-      new SurfaceFactory(kArbitraryFrameSinkId, &manager_, &client_));
+  std::unique_ptr<SurfaceFactory> factory2(new SurfaceFactory(
+      kArbitraryFrameSinkId, &manager_, &stub_surface_factory_client_,
+      &fake_surface_resource_holder_client_));
   manager_.RegisterFrameSinkId(kAnotherArbitraryFrameSinkId);
   // Give id2 a frame that references local_surface_id_.
   {
diff --git a/cc/surfaces/surface_manager.cc b/cc/surfaces/surface_manager.cc
index d4b0d01..417b7691 100644
--- a/cc/surfaces/surface_manager.cc
+++ b/cc/surfaces/surface_manager.cc
@@ -407,15 +407,15 @@
     temporary_reference_ranges_.erase(frame_sink_id);
 }
 
-void SurfaceManager::RegisterSurfaceFactoryClient(
+void SurfaceManager::RegisterFrameSinkManagerClient(
     const FrameSinkId& frame_sink_id,
-    SurfaceFactoryClient* client) {
-  framesink_manager_.RegisterSurfaceFactoryClient(frame_sink_id, client);
+    FrameSinkManagerClient* client) {
+  framesink_manager_.RegisterFrameSinkManagerClient(frame_sink_id, client);
 }
 
-void SurfaceManager::UnregisterSurfaceFactoryClient(
+void SurfaceManager::UnregisterFrameSinkManagerClient(
     const FrameSinkId& frame_sink_id) {
-  framesink_manager_.UnregisterSurfaceFactoryClient(frame_sink_id);
+  framesink_manager_.UnregisterFrameSinkManagerClient(frame_sink_id);
 }
 
 void SurfaceManager::RegisterBeginFrameSource(
diff --git a/cc/surfaces/surface_manager.h b/cc/surfaces/surface_manager.h
index 53998ec5..53708504 100644
--- a/cc/surfaces/surface_manager.h
+++ b/cc/surfaces/surface_manager.h
@@ -36,6 +36,7 @@
 namespace cc {
 class BeginFrameSource;
 class CompositorFrame;
+class FrameSinkManagerClient;
 class Surface;
 class SurfaceFactory;
 class SurfaceFactoryClient;
@@ -111,15 +112,15 @@
   // However, DelegatedFrameHost can register itself as a client before its
   // relationship with the ui::Compositor is known.
 
-  // Associates a SurfaceFactoryClient with the surface id frame_sink_id it
+  // Associates a FrameSinkManagerClient with the surface id frame_sink_id it
   // uses.
-  // SurfaceFactoryClient and surface namespaces/allocators have a 1:1 mapping.
-  // Caller guarantees the client is alive between register/unregister.
+  // FrameSinkManagerClient and surface namespaces/allocators have a 1:1
+  // mapping. Caller guarantees the client is alive between register/unregister.
   // Reregistering the same namespace when a previous client is active is not
   // valid.
-  void RegisterSurfaceFactoryClient(const FrameSinkId& frame_sink_id,
-                                    SurfaceFactoryClient* client);
-  void UnregisterSurfaceFactoryClient(const FrameSinkId& frame_sink_id);
+  void RegisterFrameSinkManagerClient(const FrameSinkId& frame_sink_id,
+                                      FrameSinkManagerClient* client);
+  void UnregisterFrameSinkManagerClient(const FrameSinkId& frame_sink_id);
 
   // Associates a |source| with a particular namespace.  That namespace and
   // any children of that namespace with valid clients can potentially use
diff --git a/cc/surfaces/surface_manager_unittest.cc b/cc/surfaces/surface_manager_unittest.cc
index 6142560a..6b59a05c 100644
--- a/cc/surfaces/surface_manager_unittest.cc
+++ b/cc/surfaces/surface_manager_unittest.cc
@@ -5,25 +5,27 @@
 #include <stddef.h>
 
 #include "cc/scheduler/begin_frame_source.h"
+#include "cc/surfaces/framesink_manager_client.h"
 #include "cc/surfaces/surface_factory_client.h"
 #include "cc/surfaces/surface_manager.h"
+#include "cc/surfaces/surface_resource_holder_client.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cc {
 
-class FakeSurfaceFactoryClient : public SurfaceFactoryClient {
+class FakeFrameSinkManagerClient : public FrameSinkManagerClient {
  public:
-  explicit FakeSurfaceFactoryClient(const FrameSinkId& frame_sink_id)
+  explicit FakeFrameSinkManagerClient(const FrameSinkId& frame_sink_id)
       : source_(nullptr), manager_(nullptr), frame_sink_id_(frame_sink_id) {}
 
-  FakeSurfaceFactoryClient(const FrameSinkId& frame_sink_id,
-                           SurfaceManager* manager)
+  FakeFrameSinkManagerClient(const FrameSinkId& frame_sink_id,
+                             SurfaceManager* manager)
       : source_(nullptr), manager_(nullptr), frame_sink_id_(frame_sink_id) {
     DCHECK(manager);
     Register(manager);
   }
 
-  ~FakeSurfaceFactoryClient() override {
+  ~FakeFrameSinkManagerClient() override {
     if (manager_) {
       Unregister();
     }
@@ -36,21 +38,20 @@
   void Register(SurfaceManager* manager) {
     EXPECT_EQ(nullptr, manager_);
     manager_ = manager;
-    manager_->RegisterSurfaceFactoryClient(frame_sink_id_, this);
+    manager_->RegisterFrameSinkManagerClient(frame_sink_id_, this);
   }
 
   void Unregister() {
     EXPECT_NE(manager_, nullptr);
-    manager_->UnregisterSurfaceFactoryClient(frame_sink_id_);
+    manager_->UnregisterFrameSinkManagerClient(frame_sink_id_);
     manager_ = nullptr;
   }
 
-  // SurfaceFactoryClient implementation.
-  void ReturnResources(const ReturnedResourceArray& resources) override {}
+  // FrameSinkManagerClient implementation.
   void SetBeginFrameSource(BeginFrameSource* begin_frame_source) override {
     DCHECK(!source_ || !begin_frame_source);
     source_ = begin_frame_source;
-  };
+  }
 
  private:
   BeginFrameSource* source_;
@@ -80,8 +81,8 @@
 };
 
 TEST_F(SurfaceManagerTest, SingleClients) {
-  FakeSurfaceFactoryClient client(FrameSinkId(1, 1));
-  FakeSurfaceFactoryClient other_client(FrameSinkId(2, 2));
+  FakeFrameSinkManagerClient client(FrameSinkId(1, 1));
+  FakeFrameSinkManagerClient other_client(FrameSinkId(2, 2));
   StubBeginFrameSource source;
 
   EXPECT_EQ(nullptr, client.source());
@@ -120,11 +121,11 @@
 
   // root1 -> A -> B
   // root2 -> C
-  FakeSurfaceFactoryClient root1(FrameSinkId(1, 1), &manager_);
-  FakeSurfaceFactoryClient root2(FrameSinkId(2, 2), &manager_);
-  FakeSurfaceFactoryClient client_a(FrameSinkId(3, 3), &manager_);
-  FakeSurfaceFactoryClient client_b(FrameSinkId(4, 4), &manager_);
-  FakeSurfaceFactoryClient client_c(FrameSinkId(5, 5), &manager_);
+  FakeFrameSinkManagerClient root1(FrameSinkId(1, 1), &manager_);
+  FakeFrameSinkManagerClient root2(FrameSinkId(2, 2), &manager_);
+  FakeFrameSinkManagerClient client_a(FrameSinkId(3, 3), &manager_);
+  FakeFrameSinkManagerClient client_b(FrameSinkId(4, 4), &manager_);
+  FakeFrameSinkManagerClient client_c(FrameSinkId(5, 5), &manager_);
 
   manager_.RegisterBeginFrameSource(&root1_source, root1.frame_sink_id());
   manager_.RegisterBeginFrameSource(&root2_source, root2.frame_sink_id());
@@ -192,9 +193,9 @@
   constexpr FrameSinkId kFrameSinkIdB(3, 3);
   constexpr FrameSinkId kFrameSinkIdC(4, 4);
 
-  FakeSurfaceFactoryClient root(kFrameSinkIdRoot, &manager_);
-  FakeSurfaceFactoryClient client_b(kFrameSinkIdB, &manager_);
-  FakeSurfaceFactoryClient client_c(kFrameSinkIdC, &manager_);
+  FakeFrameSinkManagerClient root(kFrameSinkIdRoot, &manager_);
+  FakeFrameSinkManagerClient client_b(kFrameSinkIdB, &manager_);
+  FakeFrameSinkManagerClient client_c(kFrameSinkIdC, &manager_);
 
   manager_.RegisterBeginFrameSource(&root_source, root.frame_sink_id());
   EXPECT_EQ(&root_source, root.source());
@@ -231,9 +232,9 @@
   constexpr FrameSinkId kFrameSinkIdB(3, 3);
   constexpr FrameSinkId kFrameSinkIdC(4, 4);
 
-  FakeSurfaceFactoryClient root(kFrameSinkIdRoot, &manager_);
-  FakeSurfaceFactoryClient client_b(kFrameSinkIdB, &manager_);
-  FakeSurfaceFactoryClient client_c(kFrameSinkIdC, &manager_);
+  FakeFrameSinkManagerClient root(kFrameSinkIdRoot, &manager_);
+  FakeFrameSinkManagerClient client_b(kFrameSinkIdB, &manager_);
+  FakeFrameSinkManagerClient client_c(kFrameSinkIdC, &manager_);
 
   // Set up initial hierarchy: root -> A -> B.
   // Note that A does not have a SurfaceFactoryClient.
@@ -364,9 +365,9 @@
 
   StubBeginFrameSource source_;
   // A -> B -> C hierarchy, with A always having the BFS.
-  FakeSurfaceFactoryClient client_a_;
-  FakeSurfaceFactoryClient client_b_;
-  FakeSurfaceFactoryClient client_c_;
+  FakeFrameSinkManagerClient client_a_;
+  FakeFrameSinkManagerClient client_b_;
+  FakeFrameSinkManagerClient client_c_;
 
   bool hierarchy_registered_;
   bool clients_registered_;
diff --git a/cc/surfaces/surface_resource_holder.cc b/cc/surfaces/surface_resource_holder.cc
index 6d1e60d..dff955c 100644
--- a/cc/surfaces/surface_resource_holder.cc
+++ b/cc/surfaces/surface_resource_holder.cc
@@ -4,13 +4,12 @@
 
 #include "cc/surfaces/surface_resource_holder.h"
 
-#include "cc/surfaces/surface_factory_client.h"
-
+#include "cc/surfaces/surface_resource_holder_client.h"
 namespace cc {
 
-SurfaceResourceHolder::SurfaceResourceHolder(SurfaceFactoryClient* client)
-    : client_(client) {
-}
+SurfaceResourceHolder::SurfaceResourceHolder(
+    SurfaceResourceHolderClient* client)
+    : client_(client) {}
 
 SurfaceResourceHolder::~SurfaceResourceHolder() {
 }
diff --git a/cc/surfaces/surface_resource_holder.h b/cc/surfaces/surface_resource_holder.h
index 5d22286..e40f4945 100644
--- a/cc/surfaces/surface_resource_holder.h
+++ b/cc/surfaces/surface_resource_holder.h
@@ -14,7 +14,7 @@
 #include "cc/surfaces/surfaces_export.h"
 
 namespace cc {
-class SurfaceFactoryClient;
+class SurfaceResourceHolderClient;
 
 // A SurfaceResourceHolder manages the lifetime of resources submitted by a
 // particular SurfaceFactory. Each resource is held by the service until
@@ -22,7 +22,7 @@
 // resource providers.
 class CC_SURFACES_EXPORT SurfaceResourceHolder {
  public:
-  explicit SurfaceResourceHolder(SurfaceFactoryClient* client);
+  explicit SurfaceResourceHolder(SurfaceResourceHolderClient* client);
   ~SurfaceResourceHolder();
 
   void Reset();
@@ -31,7 +31,7 @@
   void UnrefResources(const ReturnedResourceArray& resources);
 
  private:
-  SurfaceFactoryClient* client_;
+  SurfaceResourceHolderClient* client_;
 
   struct ResourceRefs {
     ResourceRefs();
diff --git a/cc/surfaces/surface_resource_holder_client.h b/cc/surfaces/surface_resource_holder_client.h
new file mode 100644
index 0000000..2a9c78d
--- /dev/null
+++ b/cc/surfaces/surface_resource_holder_client.h
@@ -0,0 +1,23 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef CC_SURFACES_SURFACE_RESOURCE_HOLDER_CLIENT_H_
+#define CC_SURFACES_SURFACE_RESOURCE_HOLDER_CLIENT_H_
+
+#include "cc/resources/returned_resource.h"
+#include "cc/surfaces/surfaces_export.h"
+
+namespace cc {
+
+class CC_SURFACES_EXPORT SurfaceResourceHolderClient {
+ public:
+  virtual ~SurfaceResourceHolderClient() = default;
+
+  // ReturnResources gets called when the display compositor is done using the
+  // resources so that the client can use them.
+  virtual void ReturnResources(const ReturnedResourceArray& resources) = 0;
+};
+
+}  // namespace cc
+
+#endif  // CC_SURFACES_SURFACE_RESOURCE_HOLDER_CLIENT_H_
diff --git a/cc/test/fake_surface_resource_holder_client.cc b/cc/test/fake_surface_resource_holder_client.cc
new file mode 100644
index 0000000..a5cd8f1
--- /dev/null
+++ b/cc/test/fake_surface_resource_holder_client.cc
@@ -0,0 +1,19 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cc/test/fake_surface_resource_holder_client.h"
+
+namespace cc {
+
+FakeSurfaceResourceHolderClient::FakeSurfaceResourceHolderClient() = default;
+
+FakeSurfaceResourceHolderClient::~FakeSurfaceResourceHolderClient() = default;
+
+void FakeSurfaceResourceHolderClient::ReturnResources(
+    const ReturnedResourceArray& resources) {
+  returned_resources_.insert(returned_resources_.end(), resources.begin(),
+                             resources.end());
+}
+
+}  // namespace cc
diff --git a/cc/test/fake_surface_resource_holder_client.h b/cc/test/fake_surface_resource_holder_client.h
new file mode 100644
index 0000000..aa0e6906
--- /dev/null
+++ b/cc/test/fake_surface_resource_holder_client.h
@@ -0,0 +1,31 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CC_TEST_FAKE_SURFACE_RESOURCE_HOLDER_CLIENT_H_
+#define CC_TEST_FAKE_SURFACE_RESOURCE_HOLDER_CLIENT_H_
+
+#include "cc/surfaces/surface_resource_holder_client.h"
+
+namespace cc {
+
+class FakeSurfaceResourceHolderClient : public SurfaceResourceHolderClient {
+ public:
+  FakeSurfaceResourceHolderClient();
+  ~FakeSurfaceResourceHolderClient() override;
+
+  // SurfaceResourceHolderClient implementation.
+  void ReturnResources(const ReturnedResourceArray& resources) override;
+
+  void clear_returned_resources() { returned_resources_.clear(); }
+  const ReturnedResourceArray& returned_resources() {
+    return returned_resources_;
+  }
+
+ private:
+  ReturnedResourceArray returned_resources_;
+};
+
+}  // namespace cc
+
+#endif  // CC_TEST_FAKE_SURFACE_RESOURCE_HOLDER_CLIENT_H_
diff --git a/cc/test/stub_surface_factory_client.h b/cc/test/stub_surface_factory_client.h
new file mode 100644
index 0000000..4b1df0f
--- /dev/null
+++ b/cc/test/stub_surface_factory_client.h
@@ -0,0 +1,24 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CC_TEST_STUB_SURFACE_FACTORY_CLIENT_H_
+#define CC_TEST_STUB_SURFACE_FACTORY_CLIENT_H_
+
+#include "cc/surfaces/surface_factory_client.h"
+
+namespace cc {
+
+class StubSurfaceFactoryClient : public SurfaceFactoryClient {
+ public:
+  StubSurfaceFactoryClient() = default;
+  ~StubSurfaceFactoryClient() override = default;
+
+  void ReferencedSurfacesChanged(
+      const LocalSurfaceId& local_surface_id,
+      const std::vector<SurfaceId>* active_referenced_surfaces) override {}
+};
+
+}  // namespace cc
+
+#endif  //  CC_TEST_STUB_SURFACE_FACTORY_CLIENT_H_
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
index 97b0e44..8f4e442 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
@@ -812,7 +812,7 @@
      * Creates the sheet's animation to a target state.
      * @param targetState The target state.
      */
-    private void createSettleAnimation(@SheetState int targetState) {
+    private void createSettleAnimation(@SheetState final int targetState) {
         mTargetState = targetState;
         mSettleAnimator = ValueAnimator.ofFloat(
                 getSheetOffsetFromBottom(), getSheetHeightForState(targetState));
@@ -824,7 +824,7 @@
             @Override
             public void onAnimationEnd(Animator animator) {
                 mSettleAnimator = null;
-                setInternalCurrentState(mTargetState);
+                setInternalCurrentState(targetState);
                 mTargetState = SHEET_STATE_NONE;
             }
         });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/PopupTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/PopupTest.java
index 06795a0d..8e53cfd 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/PopupTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/PopupTest.java
@@ -112,7 +112,7 @@
             @Override
             public boolean isSatisfied() {
                 if (getNumInfobarsShowing() != 0) return false;
-                return TextUtils.equals("Popup #3", selector.getCurrentTab().getTitle());
+                return TextUtils.equals("Three", selector.getCurrentTab().getTitle());
             }
         }, 7500, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
 
@@ -126,7 +126,7 @@
             public boolean isSatisfied() {
                 if (getNumInfobarsShowing() != 0) return false;
                 if (selector.getTotalTabCount() != 7) return false;
-                return TextUtils.equals("Popup #3", selector.getCurrentTab().getTitle());
+                return TextUtils.equals("Three", selector.getCurrentTab().getTitle());
             }
         }, 7500, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
         assertNotSame(currentTabId, selector.getCurrentTab().getId());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/SearchGeolocationDisclosureInfoBarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/SearchGeolocationDisclosureInfoBarTest.java
index bb579c2..88b87d3 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/SearchGeolocationDisclosureInfoBarTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/SearchGeolocationDisclosureInfoBarTest.java
@@ -21,7 +21,7 @@
 /** Tests for the SearchGeolocationDisclosureInfobar. */
 public class SearchGeolocationDisclosureInfoBarTest
         extends ChromeActivityTestCaseBase<ChromeActivity> {
-    private static final String SEARCH_PAGE = "/chrome/test/data/empty.html";
+    private static final String SEARCH_PAGE = "/chrome/test/data/android/google.html";
     private static final String ENABLE_NEW_DISCLOSURE_FEATURE =
             "enable-features=ConsistentOmniboxGeolocation";
     private static final String DISABLE_NEW_DISCLOSURE_FEATURE =
diff --git a/chrome/app/BUILD.gn b/chrome/app/BUILD.gn
index 73352ee..c5909b2b 100644
--- a/chrome/app/BUILD.gn
+++ b/chrome/app/BUILD.gn
@@ -492,16 +492,14 @@
     ":chrome_content_utility_manifest",
   ]
 
-  if (enable_package_mash_services) {
-    catalog("catalog_for_mus") {
-      embedded_services =
-          chrome_embedded_services +
-          [ ":chrome_content_packaged_services_manifest_for_mash" ]
-    }
-  }
-
   catalog("catalog") {
     embedded_services = chrome_embedded_services +
                         [ ":chrome_content_packaged_services_manifest" ]
   }
+
+  catalog("catalog_for_mash") {
+    embedded_services =
+        chrome_embedded_services +
+        [ ":chrome_content_packaged_services_manifest_for_mash" ]
+  }
 }
diff --git a/chrome/app/mash/BUILD.gn b/chrome/app/mash/BUILD.gn
index ba8bd1ff..5bb9c71a 100644
--- a/chrome/app/mash/BUILD.gn
+++ b/chrome/app/mash/BUILD.gn
@@ -84,7 +84,7 @@
 
 catalog("catalog") {
   embedded_services = [ ":mash_manifest" ]
-  catalog_deps = [ "//chrome/app:catalog_for_mus" ]
+  catalog_deps = [ "//chrome/app:catalog_for_mash" ]
   standalone_services = [
     "//mash/example/views_examples:manifest",
     "//mash/simple_wm:manifest",
@@ -121,7 +121,7 @@
 
   catalog("catalog_mus") {
     embedded_services = [ ":mus_manifest" ]
-    catalog_deps = [ "//chrome/app:catalog_for_mus" ]
+    catalog_deps = [ "//chrome/app:catalog" ]
   }
 
   catalog_cpp_source("chrome_mus_catalog") {
diff --git a/chrome/browser/android/data_usage/data_use_ui_tab_model_factory.cc b/chrome/browser/android/data_usage/data_use_ui_tab_model_factory.cc
index b3b252d..cc06266d 100644
--- a/chrome/browser/android/data_usage/data_use_ui_tab_model_factory.cc
+++ b/chrome/browser/android/data_usage/data_use_ui_tab_model_factory.cc
@@ -37,16 +37,14 @@
   return io_thread->globals()->external_data_use_observer->GetDataUseTabModel();
 }
 
-void SetRegisterGoogleVariationIDOnIOThread(IOThread* io_thread,
-                                            bool register_google_variation_id) {
+void SetProfileSigninStatusOnIOThread(IOThread* io_thread, bool signin_status) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   // Avoid null pointer referencing during browser shutdown.
-  if (io_thread && !io_thread->globals() &&
+  if (io_thread && io_thread->globals() &&
       io_thread->globals()->external_data_use_observer) {
-    io_thread->globals()
-        ->external_data_use_observer->SetRegisterGoogleVariationID(
-            register_google_variation_id);
+    io_thread->globals()->external_data_use_observer->SetProfileSigninStatus(
+        signin_status);
   }
 }
 
@@ -95,7 +93,7 @@
 
   content::BrowserThread::PostTask(
       content::BrowserThread::IO, FROM_HERE,
-      base::Bind(&SetRegisterGoogleVariationIDOnIOThread,
+      base::Bind(&SetProfileSigninStatusOnIOThread,
                  g_browser_process->io_thread(),
                  signin_manager && signin_manager->IsAuthenticated()));
 
diff --git a/chrome/browser/android/data_usage/external_data_use_observer.cc b/chrome/browser/android/data_usage/external_data_use_observer.cc
index 44ce0df..1711dfdc 100644
--- a/chrome/browser/android/data_usage/external_data_use_observer.cc
+++ b/chrome/browser/android/data_usage/external_data_use_observer.cc
@@ -79,6 +79,7 @@
       fetch_matching_rules_duration_(
           base::TimeDelta::FromSeconds(GetFetchMatchingRulesDurationSeconds())),
       registered_as_data_use_observer_(false),
+      profile_signin_status_(false),
       weak_factory_(this) {
   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
   DCHECK(data_use_aggregator_);
@@ -191,6 +192,15 @@
     data_use_aggregator_->RemoveObserver(this);
 
   registered_as_data_use_observer_ = should_register;
+
+  // It is okay to use base::Unretained here since
+  // |external_data_use_observer_bridge_| is owned by |this|, and is destroyed
+  // on UI thread when |this| is destroyed.
+  ui_task_runner_->PostTask(
+      FROM_HERE,
+      base::Bind(&ExternalDataUseObserverBridge::RegisterGoogleVariationID,
+                 base::Unretained(external_data_use_observer_bridge_),
+                 registered_as_data_use_observer_ && profile_signin_status_));
 }
 
 void ExternalDataUseObserver::FetchMatchingRules() {
@@ -217,17 +227,18 @@
   return data_use_tab_model_;
 }
 
-void ExternalDataUseObserver::SetRegisterGoogleVariationID(
-    bool register_google_variation_id) {
+void ExternalDataUseObserver::SetProfileSigninStatus(bool signin_status) {
   DCHECK(thread_checker_.CalledOnValidThread());
+  profile_signin_status_ = signin_status;
+
   // It is okay to use base::Unretained here since
   // |external_data_use_observer_bridge_| is owned by |this|, and is destroyed
   // on UI thread when |this| is destroyed.
   ui_task_runner_->PostTask(
       FROM_HERE,
-      base::Bind(&ExternalDataUseObserverBridge::SetRegisterGoogleVariationID,
+      base::Bind(&ExternalDataUseObserverBridge::RegisterGoogleVariationID,
                  base::Unretained(external_data_use_observer_bridge_),
-                 register_google_variation_id));
+                 registered_as_data_use_observer_ && profile_signin_status_));
 }
 
 }  // namespace android
diff --git a/chrome/browser/android/data_usage/external_data_use_observer.h b/chrome/browser/android/data_usage/external_data_use_observer.h
index 3c4b7c2..c1e4d57 100644
--- a/chrome/browser/android/data_usage/external_data_use_observer.h
+++ b/chrome/browser/android/data_usage/external_data_use_observer.h
@@ -78,7 +78,7 @@
     return external_data_use_reporter_;
   }
 
-  void SetRegisterGoogleVariationID(bool register_google_variation_id);
+  void SetProfileSigninStatus(bool signin_status);
 
  private:
   friend class DataUseTabModelTest;
@@ -135,6 +135,9 @@
   // True if |this| is currently registered as a data use observer.
   bool registered_as_data_use_observer_;
 
+  // True if profile is signed in and authenticated.
+  bool profile_signin_status_;
+
   base::ThreadChecker thread_checker_;
 
   base::WeakPtrFactory<ExternalDataUseObserver> weak_factory_;
diff --git a/chrome/browser/android/data_usage/external_data_use_observer_bridge.cc b/chrome/browser/android/data_usage/external_data_use_observer_bridge.cc
index 70ed4ec..3ec90ed6 100644
--- a/chrome/browser/android/data_usage/external_data_use_observer_bridge.cc
+++ b/chrome/browser/android/data_usage/external_data_use_observer_bridge.cc
@@ -60,8 +60,7 @@
 
 ExternalDataUseObserverBridge::ExternalDataUseObserverBridge()
     : construct_time_(base::TimeTicks::Now()),
-      is_first_matching_rule_fetch_(true),
-      register_google_variation_id_(false) {
+      is_first_matching_rule_fetch_(true) {
   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
 
   // Detach from IO thread since rest of ExternalDataUseObserverBridge lives on
@@ -208,20 +207,22 @@
       FROM_HERE,
       base::Bind(&ExternalDataUseObserver::ShouldRegisterAsDataUseObserver,
                  external_data_use_observer_, should_register));
+}
 
-  if (!register_google_variation_id_)
-    return;
-
+void ExternalDataUseObserverBridge::RegisterGoogleVariationID(
+    bool should_register) {
+  DCHECK(thread_checker_.CalledOnValidThread());
   variations::VariationID variation_id = GetGoogleVariationID();
 
   if (variation_id != variations::EMPTY_ID) {
     // Set variation id for the enabled group if |should_register| is true.
     // Otherwise clear the variation id for the enabled group by setting to
     // EMPTY_ID.
-    variations::AssociateGoogleVariationID(
-        variations::GOOGLE_WEB_PROPERTIES, kSyntheticFieldTrial,
-        kSyntheticFieldTrialEnabledGroup,
-        should_register ? variation_id : variations::EMPTY_ID);
+    if (should_register) {
+      variations::AssociateGoogleVariationID(
+          variations::GOOGLE_WEB_PROPERTIES, kSyntheticFieldTrial,
+          kSyntheticFieldTrialEnabledGroup, variation_id);
+    }
     ChromeMetricsServiceAccessor::RegisterSyntheticFieldTrial(
         kSyntheticFieldTrial, should_register
                                   ? kSyntheticFieldTrialEnabledGroup
@@ -229,12 +230,6 @@
   }
 }
 
-void ExternalDataUseObserverBridge::SetRegisterGoogleVariationID(
-    bool register_google_variation_id) {
-  DCHECK(thread_checker_.CalledOnValidThread());
-  register_google_variation_id_ = register_google_variation_id;
-}
-
 bool RegisterExternalDataUseObserver(JNIEnv* env) {
   return RegisterNativesImpl(env);
 }
diff --git a/chrome/browser/android/data_usage/external_data_use_observer_bridge.h b/chrome/browser/android/data_usage/external_data_use_observer_bridge.h
index b8ae223..3d4a0c7 100644
--- a/chrome/browser/android/data_usage/external_data_use_observer_bridge.h
+++ b/chrome/browser/android/data_usage/external_data_use_observer_bridge.h
@@ -98,7 +98,7 @@
   // should register as a data use observer.
   virtual void ShouldRegisterAsDataUseObserver(bool should_register) const;
 
-  void SetRegisterGoogleVariationID(bool register_google_variation_id);
+  void RegisterGoogleVariationID(bool should_register);
 
  private:
   // Java listener that provides regular expressions to |this|. Data use
@@ -119,9 +119,6 @@
   // True if matching rules are fetched for the first time.
   bool is_first_matching_rule_fetch_;
 
-  // True if Google variation ID should be registered.
-  bool register_google_variation_id_;
-
   // |io_task_runner_| accesses ExternalDataUseObserver members on IO thread.
   scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
 
diff --git a/chrome/browser/android/vr_shell/textures/insecure_content_permanent_texture.cc b/chrome/browser/android/vr_shell/textures/insecure_content_permanent_texture.cc
index ef4c0c4..c645c75 100644
--- a/chrome/browser/android/vr_shell/textures/insecure_content_permanent_texture.cc
+++ b/chrome/browser/android/vr_shell/textures/insecure_content_permanent_texture.cc
@@ -8,6 +8,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/canvas.h"
+#include "ui/gfx/font_list.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/vector2d.h"
 #include "ui/gfx/paint_vector_icon.h"
@@ -20,6 +21,11 @@
 
 const SkColor kBackgroundColor = SK_ColorWHITE;
 const SkColor kForegroundColor = 0xFF444444;
+constexpr float kBorderFactor = 0.1;
+constexpr float kIconSizeFactor = 0.7;
+constexpr float kFontSizeFactor = 0.45;
+constexpr float kTextHeightFactor = 1.0 - 2 * kBorderFactor;
+constexpr float kTextWidthFactor = 4.0 - 3 * kBorderFactor - kIconSizeFactor;
 
 }  // namespace
 
@@ -37,21 +43,44 @@
 void InsecureContentPermanentTexture::Draw(gfx::Canvas* canvas) {
   cc::PaintFlags flags;
   flags.setColor(kBackgroundColor);
-  canvas->DrawRoundRect(gfx::Rect(texture_size_, height_), height_ * 0.1,
+
+  int text_flags = gfx::Canvas::TEXT_ALIGN_CENTER | gfx::Canvas::NO_ELLIPSIS;
+  auto text =
+      l10n_util::GetStringUTF16(IDS_PAGE_INFO_INSECURE_WEBVR_CONTENT_PERMANENT);
+  auto fonts = GetFontList(height_ * kFontSizeFactor, text);
+  int text_height = kTextHeightFactor * height_;
+  int text_width = kTextWidthFactor * height_;
+  gfx::Canvas::SizeStringInt(text, fonts, &text_width, &text_height, 0,
+                             text_flags);
+  // Giving some extra width without reaching the texture limit.
+  text_width = static_cast<int>(std::min(
+      text_width + 2 * kBorderFactor * height_, kTextWidthFactor * height_));
+  width_ = static_cast<int>(
+      ceil((3 * kBorderFactor + kIconSizeFactor) * height_ + text_width));
+
+  canvas->DrawRoundRect(gfx::Rect(width_, height_), height_ * kBorderFactor,
                         flags);
+
   canvas->Save();
-  canvas->Translate({height_ * 0.1, height_ * 0.1});
-  PaintVectorIcon(canvas, ui::kInfoOutlineIcon, height_ * 0.8,
+  canvas->Translate({IsRTL() ? 2 * kBorderFactor * height_ + text_width
+                             : height_ * kBorderFactor,
+                     height_ * (1.0 - kIconSizeFactor) / 2.0});
+  PaintVectorIcon(canvas, ui::kInfoOutlineIcon, height_ * kIconSizeFactor,
                   kForegroundColor);
   canvas->Restore();
+
   canvas->Save();
-  canvas->Translate({height_, height_ * 0.1});
-  // TODO(acondor): Draw text IDS_PAGE_INFO_INSECURE_WEBVR_CONTENT_PERMANENT.
+  canvas->Translate({height_ * (IsRTL() ? kBorderFactor
+                                        : 2 * kBorderFactor + kIconSizeFactor),
+                     height_ * kBorderFactor});
+  canvas->DrawStringRectWithFlags(
+      text, fonts, kForegroundColor,
+      gfx::Rect(text_width, kTextHeightFactor * height_), text_flags);
   canvas->Restore();
 }
 
 void InsecureContentPermanentTexture::SetSize() {
-  size_.SetSize(texture_size_, height_);
+  size_.SetSize(width_, height_);
 }
 
 }  // namespace vr_shell
diff --git a/chrome/browser/android/vr_shell/textures/insecure_content_permanent_texture.h b/chrome/browser/android/vr_shell/textures/insecure_content_permanent_texture.h
index 1b1353f..d492b1d 100644
--- a/chrome/browser/android/vr_shell/textures/insecure_content_permanent_texture.h
+++ b/chrome/browser/android/vr_shell/textures/insecure_content_permanent_texture.h
@@ -19,6 +19,7 @@
   void SetSize() override;
 
   int height_;
+  int width_;
 };
 
 }  // namespace vr_shell
diff --git a/chrome/browser/android/vr_shell/textures/insecure_content_transient_texture.cc b/chrome/browser/android/vr_shell/textures/insecure_content_transient_texture.cc
index f215587..8b84544 100644
--- a/chrome/browser/android/vr_shell/textures/insecure_content_transient_texture.cc
+++ b/chrome/browser/android/vr_shell/textures/insecure_content_transient_texture.cc
@@ -8,6 +8,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/canvas.h"
+#include "ui/gfx/font_list.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/vector2d.h"
 #include "ui/gfx/paint_vector_icon.h"
@@ -20,6 +21,10 @@
 
 const SkColor kBackgroundColor = 0xCC1A1A1A;
 const SkColor kForegroundColor = SK_ColorWHITE;
+constexpr float kBorderFactor = 0.045;
+constexpr float kIconSizeFactor = 0.25;
+constexpr float kFontSizeFactor = 0.045;
+constexpr float kTextWidthFactor = 1.0 - 3 * kBorderFactor - kIconSizeFactor;
 
 }  // namespace
 
@@ -27,9 +32,6 @@
     int texture_handle,
     int texture_size)
     : UITexture(texture_handle, texture_size) {
-  // Ensuring height is half the width.
-  DCHECK(texture_size_ % 2 == 0);
-  height_ = texture_size_ / 2;
 }
 
 InsecureContentTransientTexture::~InsecureContentTransientTexture() = default;
@@ -37,16 +39,44 @@
 void InsecureContentTransientTexture::Draw(gfx::Canvas* canvas) {
   cc::PaintFlags flags;
   flags.setColor(kBackgroundColor);
-  canvas->DrawRoundRect(gfx::Rect(texture_size_, height_), height_ * 0.1,
-                        flags);
+
+  int text_flags = gfx::Canvas::TEXT_ALIGN_CENTER | gfx::Canvas::MULTI_LINE;
+  auto text =
+      l10n_util::GetStringUTF16(IDS_PAGE_INFO_INSECURE_WEBVR_CONTENT_TRANSIENT);
+  auto fonts = GetFontList(texture_size_ * kFontSizeFactor, text);
+
+  int text_width = texture_size_ * kTextWidthFactor;
+  int text_height = 0;  // Will be increased during text measurement.
+  gfx::Canvas::SizeStringInt(text, fonts, &text_width, &text_height, 0,
+                             text_flags);
+  // Making sure that the icon fits in the box.
+  text_height = std::max(
+      text_height, static_cast<int>(ceil(texture_size_ * kIconSizeFactor)));
+  // Making sure the drawing fits within the texture.
+  text_height = std::min(
+      text_height, static_cast<int>((1.0 - 2 * kBorderFactor) * texture_size_));
+  height_ =
+      static_cast<int>(ceil(texture_size_ * 2 * kBorderFactor + text_height));
+
+  canvas->DrawRoundRect(gfx::Rect(texture_size_, height_),
+                        height_ * kBorderFactor, flags);
+
   canvas->Save();
-  canvas->Translate({height_ * 0.1, height_ * 0.25});
-  PaintVectorIcon(canvas, ui::kInfoOutlineIcon, height_ * 0.5,
+  canvas->Translate({IsRTL() ? 2 * kBorderFactor * texture_size_ + text_width
+                             : texture_size_ * kBorderFactor,
+                     (height_ - kIconSizeFactor * texture_size_) / 2.0});
+  PaintVectorIcon(canvas, ui::kInfoOutlineIcon, texture_size_ * kIconSizeFactor,
                   kForegroundColor);
   canvas->Restore();
+
   canvas->Save();
-  canvas->Translate({height_ * 0.7, height_ * 0.1});
-  // TODO(acondor): Draw text IDS_PAGE_INFO_INSECURE_WEBVR_CONTENT_TRANSIENT.
+  canvas->Translate(
+      {IsRTL() ? kBorderFactor * texture_size_
+               : texture_size_ * (2 * kBorderFactor + kIconSizeFactor),
+       texture_size_ * kBorderFactor});
+  canvas->DrawStringRectWithFlags(
+      text, fonts, kForegroundColor,
+      gfx::Rect(kTextWidthFactor * texture_size_, text_height), text_flags);
   canvas->Restore();
 }
 
diff --git a/chrome/browser/android/vr_shell/textures/ui_texture.cc b/chrome/browser/android/vr_shell/textures/ui_texture.cc
index c93ca29e..7cac42d2 100644
--- a/chrome/browser/android/vr_shell/textures/ui_texture.cc
+++ b/chrome/browser/android/vr_shell/textures/ui_texture.cc
@@ -4,17 +4,27 @@
 
 #include "chrome/browser/android/vr_shell/textures/ui_texture.h"
 
+#include <set>
 #include <string>
 #include <vector>
 
+#include "base/i18n/char_iterator.h"
+#include "base/i18n/rtl.h"
 #include "base/memory/ptr_util.h"
 #include "base/strings/string_util.h"
+#include "chrome/browser/browser_process.h"
 #include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/ports/SkFontMgr.h"
 #include "ui/gfx/canvas.h"
+#include "ui/gfx/font_list.h"
 #include "ui/gl/gl_bindings.h"
 
 namespace vr_shell {
 
+namespace {
+constexpr char kDefaultFontFamily[] = "sans-serif";
+}  // namespace
+
 // TODO(acondor): Create non-square textures to reduce memory usage.
 UITexture::UITexture(int texture_handle, int texture_size)
     : texture_handle_(texture_handle),
@@ -46,4 +56,34 @@
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 }
 
+bool UITexture::IsRTL() {
+  return base::i18n::IsRTL();
+}
+
+gfx::FontList UITexture::GetFontList(int size, base::string16 text) {
+  gfx::Font default_font(kDefaultFontFamily, size);
+  std::vector<gfx::Font> fonts{default_font};
+
+  // TODO(acondor): Obtain fallback fonts with gfx::GetFallbackFonts
+  // (which is not implemented for android yet) in order to avoid
+  // querying per character.
+
+  sk_sp<SkFontMgr> font_mgr(SkFontMgr::RefDefault());
+  std::set<std::string> names;
+  // TODO(acondor): Query BrowserProcess to obtain the application locale.
+  for (base::i18n::UTF16CharIterator it(&text); !it.end(); it.Advance()) {
+    sk_sp<SkTypeface> tf(font_mgr->matchFamilyStyleCharacter(
+        default_font.GetFontName().c_str(), SkFontStyle(), nullptr, 0,
+        it.get()));
+    SkString sk_name;
+    tf->getFamilyName(&sk_name);
+    std::string name(sk_name.c_str());
+    if (name != kDefaultFontFamily)
+      names.insert(name);
+  }
+  for (const auto& name : names)
+    fonts.push_back(gfx::Font(name, size));
+  return gfx::FontList(fonts);
+}
+
 }  // namespace vr_shell
diff --git a/chrome/browser/android/vr_shell/textures/ui_texture.h b/chrome/browser/android/vr_shell/textures/ui_texture.h
index 2b40429..ea96cb83 100644
--- a/chrome/browser/android/vr_shell/textures/ui_texture.h
+++ b/chrome/browser/android/vr_shell/textures/ui_texture.h
@@ -7,11 +7,13 @@
 
 #include <memory>
 
+#include "base/strings/string16.h"
 #include "third_party/skia/include/core/SkSurface.h"
 #include "ui/gfx/geometry/size.h"
 
 namespace gfx {
 class Canvas;
+class FontList;
 }  // namespace gfx
 
 namespace vr_shell {
@@ -28,6 +30,8 @@
  protected:
   virtual void Draw(gfx::Canvas* canvas) = 0;
   virtual void SetSize() = 0;
+  static bool IsRTL();
+  static gfx::FontList GetFontList(int size, base::string16 text);
 
   int texture_handle_;
   int texture_size_;
diff --git a/chrome/browser/android/vr_shell/vr_shell.cc b/chrome/browser/android/vr_shell/vr_shell.cc
index 224ea19..83360fb 100644
--- a/chrome/browser/android/vr_shell/vr_shell.cc
+++ b/chrome/browser/android/vr_shell/vr_shell.cc
@@ -413,6 +413,8 @@
                                            jint height,
                                            jfloat dpr) {
   TRACE_EVENT0("gpu", "VrShell::ContentPhysicalBoundsChanged");
+  // TODO(acondor): Set the device scale factor for font rendering on the
+  // VR Shell textures.
   PostToGlThreadWhenReady(base::Bind(&VrShellGl::ContentPhysicalBoundsChanged,
                                      gl_thread_->GetVrShellGl(), width,
                                      height));
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index 74e2add..ac62700 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -1298,6 +1298,12 @@
              NO_TEST_SERVER);
 }
 
+IN_PROC_BROWSER_TEST_P(WebViewTest,
+                       Shim_TestContentInitiatedNavigationToDataUrlBlocked) {
+  TestHelper("testContentInitiatedNavigationToDataUrlBlocked", "web_view/shim",
+             NO_TEST_SERVER);
+}
+
 IN_PROC_BROWSER_TEST_P(WebViewTest, Shim_TestDisplayNoneWebviewLoad) {
   TestHelper("testDisplayNoneWebviewLoad", "web_view/shim", NO_TEST_SERVER);
 }
diff --git a/chrome/browser/chromeos/login/DEPS b/chrome/browser/chromeos/login/DEPS
index 56f65d7..866b880 100644
--- a/chrome/browser/chromeos/login/DEPS
+++ b/chrome/browser/chromeos/login/DEPS
@@ -6,6 +6,7 @@
   "+components/user_manager",
   "+components/wallpaper",
   "+extensions/components/native_app_window",
+  "+services/device/public/interfaces",
 
   # Library used for calculating CRC-32 needed for HWID verification.
   "+third_party/zlib",
diff --git a/chrome/browser/chromeos/login/lock/screen_locker.cc b/chrome/browser/chromeos/login/lock/screen_locker.cc
index 42a3399..f236f1e 100644
--- a/chrome/browser/chromeos/login/lock/screen_locker.cc
+++ b/chrome/browser/chromeos/login/lock/screen_locker.cc
@@ -59,7 +59,10 @@
 #include "content/public/browser/url_data_source.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_ui.h"
+#include "content/public/common/service_manager_connection.h"
 #include "media/audio/sounds/sounds_manager.h"
+#include "services/device/public/interfaces/constants.mojom.h"
+#include "services/service_manager/public/cpp/connector.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/gfx/image/image.h"
 #include "url/gurl.h"
@@ -76,6 +79,21 @@
 // unlock happens even if animations are broken.
 const int kUnlockGuardTimeoutMs = 400;
 
+// Returns true if fingerprint authentication is available for one of the
+// |users|.
+bool IsFingerprintAuthenticationAvailableForUsers(
+    const user_manager::UserList& users) {
+  for (auto* user : users) {
+    quick_unlock::QuickUnlockStorage* quick_unlock_storage =
+        quick_unlock::QuickUnlockFactory::GetForUser(user);
+    if (quick_unlock_storage &&
+        quick_unlock_storage->IsFingerprintAuthenticationAvailable()) {
+      return true;
+    }
+  }
+  return false;
+}
+
 // Observer to start ScreenLocker when locking the screen is requested.
 class ScreenLockObserver : public SessionManagerClient::StubDelegate,
                            public content::NotificationObserver,
@@ -144,8 +162,7 @@
 // ScreenLocker, public:
 
 ScreenLocker::ScreenLocker(const user_manager::UserList& users)
-    : users_(users),
-      weak_factory_(this) {
+    : users_(users), binding_(this), weak_factory_(this) {
   DCHECK(!screen_locker_);
   screen_locker_ = this;
 
@@ -155,6 +172,10 @@
                       bundle.GetRawDataResource(IDR_SOUND_LOCK_WAV));
   manager->Initialize(SOUND_UNLOCK,
                       bundle.GetRawDataResource(IDR_SOUND_UNLOCK_WAV));
+  service_manager::Connector* connector =
+      content::ServiceManagerConnection::GetForProcess()->GetConnector();
+  connector->BindInterface(device::mojom::kServiceName, &fp_service_);
+  fp_service_->AddFingerprintObserver(binding_.CreateInterfacePtrAndBind());
 }
 
 void ScreenLocker::Init() {
@@ -190,10 +211,8 @@
     UMA_HISTOGRAM_TIMES("ScreenLocker.AuthenticationFailureTime", delta);
   }
 
-  UMA_HISTOGRAM_ENUMERATION(
-      "ScreenLocker.AuthenticationFailure",
-      is_pin_attempt_ ? UnlockType::AUTH_PIN : UnlockType::AUTH_PASSWORD,
-      UnlockType::AUTH_COUNT);
+  UMA_HISTOGRAM_ENUMERATION("ScreenLocker.AuthenticationFailure",
+                            unlock_attempt_type_, UnlockType::AUTH_COUNT);
 
   EnableInput();
   // Don't enable signout button here as we're showing
@@ -219,10 +238,8 @@
     UMA_HISTOGRAM_TIMES("ScreenLocker.AuthenticationSuccessTime", delta);
   }
 
-  UMA_HISTOGRAM_ENUMERATION(
-      "ScreenLocker.AuthenticationSuccess",
-      is_pin_attempt_ ? UnlockType::AUTH_PIN : UnlockType::AUTH_PASSWORD,
-      UnlockType::AUTH_COUNT);
+  UMA_HISTOGRAM_ENUMERATION("ScreenLocker.AuthenticationSuccess",
+                            unlock_attempt_type_, UnlockType::AUTH_COUNT);
 
   const user_manager::User* user =
       user_manager::UserManager::Get()->FindUser(user_context.GetAccountId());
@@ -246,6 +263,10 @@
       quick_unlock_storage->pin_storage()->ResetUnlockAttemptCount();
       quick_unlock_storage->fingerprint_storage()->ResetUnlockAttemptCount();
     }
+    if (should_have_fingerprint_auth_session_) {
+      fp_service_->EndCurrentAuthSession(base::Bind(
+          &ScreenLocker::OnEndCurrentAuthSession, weak_factory_.GetWeakPtr()));
+    }
 
     UserSessionManager::GetInstance()->UpdateEasyUnlockKeys(user_context);
   } else {
@@ -271,6 +292,10 @@
           user_context.GetAccountId());
   if (quick_unlock_storage)
     quick_unlock_storage->MarkStrongAuth();
+  if (should_have_fingerprint_auth_session_) {
+    fp_service_->EndCurrentAuthSession(base::Bind(
+        &ScreenLocker::OnEndCurrentAuthSession, weak_factory_.GetWeakPtr()));
+  }
 }
 
 void ScreenLocker::UnlockOnLoginSuccess() {
@@ -297,7 +322,8 @@
 
   authentication_start_time_ = base::Time::Now();
   web_ui()->SetInputEnabled(false);
-  is_pin_attempt_ = user_context.IsUsingPin();
+  if (user_context.IsUsingPin())
+    unlock_attempt_type_ = AUTH_PIN;
 
   const user_manager::User* user = FindUnlockUser(user_context.GetAccountId());
   if (user) {
@@ -308,7 +334,8 @@
     // otherwise we will timeout PIN if the user enters their account password
     // incorrectly more than a few times.
     int dummy_value;
-    if (is_pin_attempt_ && base::StringToInt(pin, &dummy_value)) {
+    if (unlock_attempt_type_ == AUTH_PIN &&
+        base::StringToInt(pin, &dummy_value)) {
       quick_unlock::QuickUnlockStorage* quick_unlock_storage =
           quick_unlock::QuickUnlockFactory::GetForUser(user);
       if (quick_unlock_storage &&
@@ -555,6 +582,10 @@
   input_method::InputMethodManager::Get()
       ->GetActiveIMEState()
       ->EnableLockScreenLayouts();
+  should_have_fingerprint_auth_session_ =
+      IsFingerprintAuthenticationAvailableForUsers(users_);
+  if (should_have_fingerprint_auth_session_)
+    fp_service_->StartAuthSession();
 }
 
 bool ScreenLocker::IsUserLoggedIn(const AccountId& account_id) const {
@@ -565,4 +596,67 @@
   return false;
 }
 
+void ScreenLocker::OnAuthScanDone(
+    uint32_t scan_result,
+    const std::unordered_map<std::string, std::vector<std::string>>& matches) {
+  unlock_attempt_type_ = AUTH_FINGERPRINT;
+  user_manager::User* active_user =
+      user_manager::UserManager::Get()->GetActiveUser();
+  quick_unlock::QuickUnlockStorage* quick_unlock_storage =
+      quick_unlock::QuickUnlockFactory::GetForUser(active_user);
+  if (!quick_unlock_storage ||
+      !quick_unlock_storage->IsFingerprintAuthenticationAvailable()) {
+    return;
+  }
+
+  if (scan_result != biod::ScanResult::SCAN_RESULT_SUCCESS) {
+    OnFingerprintAuthFailure(*active_user);
+    return;
+  }
+
+  UserContext user_context(active_user->GetAccountId());
+  if (!base::ContainsKey(matches, active_user->username_hash())) {
+    OnFingerprintAuthFailure(*active_user);
+    return;
+  }
+  web_ui()->SetFingerprintState(active_user->GetAccountId(),
+                                WebUIScreenLocker::FingerprintState::kSignin);
+  OnAuthSuccess(user_context);
+}
+
+void ScreenLocker::OnSessionFailed() {
+  LOG(ERROR) << "Fingerprint session failed.";
+}
+
+void ScreenLocker::OnFingerprintAuthFailure(const user_manager::User& user) {
+  UMA_HISTOGRAM_ENUMERATION("ScreenLocker.AuthenticationFailure",
+                            unlock_attempt_type_, UnlockType::AUTH_COUNT);
+
+  web_ui()->SetFingerprintState(user.GetAccountId(),
+                                WebUIScreenLocker::FingerprintState::kFailed);
+
+  quick_unlock::QuickUnlockStorage* quick_unlock_storage =
+      quick_unlock::QuickUnlockFactory::GetForUser(&user);
+  if (quick_unlock_storage &&
+      quick_unlock_storage->IsFingerprintAuthenticationAvailable()) {
+    quick_unlock_storage->fingerprint_storage()->AddUnlockAttempt();
+    if (quick_unlock_storage->fingerprint_storage()->ExceededUnlockAttempts()) {
+      web_ui()->SetFingerprintState(
+          user.GetAccountId(), WebUIScreenLocker::FingerprintState::kRemoved);
+      web_ui()->ShowErrorMessage(IDS_LOGIN_ERROR_FINGERPRINT_MAX_ATTEMPT,
+                                 HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT);
+    }
+  }
+
+  if (auth_status_consumer_) {
+    AuthFailure failure(AuthFailure::UNLOCK_FAILED);
+    auth_status_consumer_->OnAuthFailure(failure);
+  }
+}
+
+void ScreenLocker::OnEndCurrentAuthSession(bool success) {
+  LOG_IF(ERROR, !success)
+      << "Failed to end current fingerprint authentication session.";
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/lock/screen_locker.h b/chrome/browser/chromeos/login/lock/screen_locker.h
index e15ecd6..d72d793 100644
--- a/chrome/browser/chromeos/login/lock/screen_locker.h
+++ b/chrome/browser/chromeos/login/lock/screen_locker.h
@@ -19,6 +19,8 @@
 #include "chromeos/login/auth/auth_status_consumer.h"
 #include "chromeos/login/auth/user_context.h"
 #include "components/user_manager/user.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "services/device/public/interfaces/fingerprint.mojom.h"
 #include "ui/base/accelerators/accelerator.h"
 #include "ui/base/ime/chromeos/input_method_manager.h"
 
@@ -39,7 +41,8 @@
 // ScreenLocker creates a WebUIScreenLocker which will display the lock UI.
 // As well, it takes care of authenticating the user and managing a global
 // instance of itself which will be deleted when the system is unlocked.
-class ScreenLocker : public AuthStatusConsumer {
+class ScreenLocker : public AuthStatusConsumer,
+                     public device::mojom::FingerprintObserver {
  public:
   explicit ScreenLocker(const user_manager::UserList& users);
 
@@ -122,7 +125,7 @@
   // Track whether the user used pin or password to unlock the lock screen.
   // Values corrospond to UMA histograms, do not modify, or add or delete other
   // than directly before AUTH_COUNT.
-  enum UnlockType { AUTH_PASSWORD = 0, AUTH_PIN, AUTH_COUNT };
+  enum UnlockType { AUTH_PASSWORD = 0, AUTH_PIN, AUTH_FINGERPRINT, AUTH_COUNT };
 
   struct AuthenticationParametersCapture {
     UserContext user_context;
@@ -130,6 +133,19 @@
 
   ~ScreenLocker() override;
 
+  // fingerprint::mojom::FingerprintObserver:
+  void OnAuthScanDone(
+      uint32_t scan_result,
+      const std::unordered_map<std::string, std::vector<std::string>>& matches)
+      override;
+  void OnSessionFailed() override;
+  void OnRestarted() override{};
+  void OnEnrollScanDone(uint32_t scan_result,
+                        bool enroll_session_complete) override{};
+
+  void OnFingerprintAuthFailure(const user_manager::User& user);
+  void OnEndCurrentAuthSession(bool success);
+
   // Sets the authenticator.
   void SetAuthenticator(Authenticator* authenticator);
 
@@ -183,8 +199,8 @@
   // Number of bad login attempts in a row.
   int incorrect_passwords_count_ = 0;
 
-  // Whether the last password entered was a pin or not.
-  bool is_pin_attempt_ = false;
+  // Type of the last unlock attempt.
+  UnlockType unlock_attempt_type_ = AUTH_PASSWORD;
 
   // Copy of parameters passed to last call of OnLoginSuccess for usage in
   // UnlockOnLoginSuccess().
@@ -195,6 +211,12 @@
 
   scoped_refptr<input_method::InputMethodManager::State> saved_ime_state_;
 
+  device::mojom::FingerprintPtr fp_service_;
+  mojo::Binding<device::mojom::FingerprintObserver> binding_;
+
+  // True if lock screen should have a fingerprint auth session.
+  bool should_have_fingerprint_auth_session_ = false;
+
   base::WeakPtrFactory<ScreenLocker> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(ScreenLocker);
diff --git a/chrome/browser/chromeos/login/lock/webui_screen_locker.cc b/chrome/browser/chromeos/login/lock/webui_screen_locker.cc
index 8b87e22..efaf936 100644
--- a/chrome/browser/chromeos/login/lock/webui_screen_locker.cc
+++ b/chrome/browser/chromeos/login/lock/webui_screen_locker.cc
@@ -16,6 +16,8 @@
 #include "chrome/browser/chromeos/accessibility/accessibility_util.h"
 #include "chrome/browser/chromeos/login/helper.h"
 #include "chrome/browser/chromeos/login/lock/screen_locker.h"
+#include "chrome/browser/chromeos/login/quick_unlock/quick_unlock_factory.h"
+#include "chrome/browser/chromeos/login/quick_unlock/quick_unlock_storage.h"
 #include "chrome/browser/chromeos/login/ui/preloaded_web_view.h"
 #include "chrome/browser/chromeos/login/ui/preloaded_web_view_factory.h"
 #include "chrome/browser/chromeos/login/ui/webui_login_display.h"
@@ -28,6 +30,7 @@
 #include "chrome/common/url_constants.h"
 #include "chrome/grit/generated_resources.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
+#include "components/login/base_screen_handler_utils.h"
 #include "components/prefs/pref_service.h"
 #include "components/user_manager/user.h"
 #include "content/public/browser/browser_thread.h"
@@ -266,6 +269,29 @@
       "cr.ui.Oobe.animateOnceFullyDisplayed");
 }
 
+void WebUIScreenLocker::SetFingerprintState(const AccountId& account_id,
+                                            FingerprintState state) {
+  // TODO(xiaoyinh@): Modify JS side to consolidate removeUserPodFingerprintIcon
+  // and setUserPodFingerprintIcon into single JS function.
+  if (state == FingerprintState::kRemoved) {
+    GetWebUI()->CallJavascriptFunctionUnsafe(
+        "login.AccountPickerScreen.removeUserPodFingerprintIcon",
+        ::login::MakeValue(account_id));
+    return;
+  }
+
+  chromeos::quick_unlock::QuickUnlockStorage* quick_unlock_storage =
+      chromeos::quick_unlock::QuickUnlockFactory::GetForAccountId(account_id);
+  if (!quick_unlock_storage ||
+      !quick_unlock_storage->IsFingerprintAuthenticationAvailable()) {
+    state = FingerprintState::kHidden;
+  }
+  GetWebUI()->CallJavascriptFunctionUnsafe(
+      "login.AccountPickerScreen.setUserPodFingerprintIcon",
+      ::login::MakeValue(account_id),
+      ::login::MakeValue(static_cast<int>(state)));
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // WebUIScreenLocker, LoginDisplay::Delegate:
 
diff --git a/chrome/browser/chromeos/login/lock/webui_screen_locker.h b/chrome/browser/chromeos/login/lock/webui_screen_locker.h
index b8b87180..3bf0432 100644
--- a/chrome/browser/chromeos/login/lock/webui_screen_locker.h
+++ b/chrome/browser/chromeos/login/lock/webui_screen_locker.h
@@ -49,6 +49,14 @@
                           public display::DisplayObserver,
                           public content::WebContentsObserver {
  public:
+  enum class FingerprintState {
+    kHidden,
+    kDefault,
+    kSignin,
+    kFailed,
+    kRemoved,
+  };
+
   // Request lock screen preload when the user is idle. Does nothing if
   // preloading is disabled or if the preload hueristics return false.
   static void RequestPreload();
@@ -89,6 +97,8 @@
   // Called by ScreenLocker to notify that ash lock animation finishes.
   void OnLockAnimationFinished();
 
+  void SetFingerprintState(const AccountId& account_id, FingerprintState state);
+
  private:
   friend class test::WebUIScreenLockerTester;
 
diff --git a/chrome/browser/chromeos/login/quick_unlock/fingerprint_storage.cc b/chrome/browser/chromeos/login/quick_unlock/fingerprint_storage.cc
index 87644c9..1f0294f9 100644
--- a/chrome/browser/chromeos/login/quick_unlock/fingerprint_storage.cc
+++ b/chrome/browser/chromeos/login/quick_unlock/fingerprint_storage.cc
@@ -23,10 +23,7 @@
 FingerprintStorage::~FingerprintStorage() {}
 
 bool FingerprintStorage::IsFingerprintAuthenticationAvailable() const {
-  const bool exceeded_unlock_attempts =
-      unlock_attempt_count() >= kMaximumUnlockAttempts;
-
-  return IsFingerprintEnabled() && HasRecord() && !exceeded_unlock_attempts;
+  return !ExceededUnlockAttempts() && IsFingerprintEnabled() && HasRecord();
 }
 
 bool FingerprintStorage::HasRecord() const {
@@ -41,5 +38,9 @@
   unlock_attempt_count_ = 0;
 }
 
+bool FingerprintStorage::ExceededUnlockAttempts() const {
+  return unlock_attempt_count() >= kMaximumUnlockAttempts;
+}
+
 }  // namespace quick_unlock
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/quick_unlock/fingerprint_storage.h b/chrome/browser/chromeos/login/quick_unlock/fingerprint_storage.h
index 385dc661..9f70550 100644
--- a/chrome/browser/chromeos/login/quick_unlock/fingerprint_storage.h
+++ b/chrome/browser/chromeos/login/quick_unlock/fingerprint_storage.h
@@ -37,6 +37,9 @@
   // Reset the number of unlock attempts to 0.
   void ResetUnlockAttemptCount();
 
+  // Returns true if the user has exceeded fingerprint unlock attempts.
+  bool ExceededUnlockAttempts() const;
+
   int unlock_attempt_count() const { return unlock_attempt_count_; }
 
  private:
diff --git a/chrome/browser/metrics/metrics_memory_details.cc b/chrome/browser/metrics/metrics_memory_details.cc
index d0e5ceb..1ba164c 100644
--- a/chrome/browser/metrics/metrics_memory_details.cc
+++ b/chrome/browser/metrics/metrics_memory_details.cc
@@ -179,7 +179,7 @@
         // Physical footprint was introduced in macOS 10.11.
         if (base::mac::IsAtLeastOS10_11()) {
           UMA_HISTOGRAM_MEMORY_LARGE_MB(
-              "Memory.Gpu.PhysicalFootprint.MacOS",
+              "Memory.Experimental.Gpu.PhysicalFootprint.MacOS",
               browser.processes[index].phys_footprint / 1024 / 1024);
         }
 #endif
diff --git a/chrome/browser/resources/print_preview/native_layer.js b/chrome/browser/resources/print_preview/native_layer.js
index 652c87c..22a9469 100644
--- a/chrome/browser/resources/print_preview/native_layer.js
+++ b/chrome/browser/resources/print_preview/native_layer.js
@@ -273,7 +273,7 @@
      *   - PAGE_PREVIEW_READY
      *   - PREVIEW_GENERATION_DONE
      *   - PREVIEW_GENERATION_FAIL
-     * @param {print_preview.Destination} destination Destination to print to.
+     * @param {!print_preview.Destination} destination Destination to print to.
      * @param {!print_preview.PrintTicketStore} printTicketStore Used to get the
      *     state of the print ticket.
      * @param {!print_preview.DocumentInfo} documentInfo Document data model.
@@ -294,28 +294,27 @@
         'isFirstRequest': requestId == 0,
         'requestID': requestId,
         'previewModifiable': documentInfo.isModifiable,
-        'printToPDF':
-            destination != null &&
-            destination.id ==
-                print_preview.Destination.GooglePromotedId.SAVE_AS_PDF,
-        'printWithCloudPrint': destination != null && !destination.isLocal,
-        'printWithPrivet': destination != null && destination.isPrivet,
-        'printWithExtension': destination != null && destination.isExtension,
-        'deviceName': destination == null ? 'foo' : destination.id,
         'generateDraftData': documentInfo.isModifiable,
         'fitToPageEnabled': printTicketStore.fitToPage.getValue(),
         'scaleFactor': printTicketStore.scaling.getValueAsNumber(),
         // NOTE: Even though the following fields don't directly relate to the
         // preview, they still need to be included.
-        'duplex': printTicketStore.duplex.getValue() ?
-            NativeLayer.DuplexMode.LONG_EDGE : NativeLayer.DuplexMode.SIMPLEX,
-        'copies': 1,
+        // e.g. printing::PrintSettingsFromJobSettings() still checks for them.
         'collate': true,
-        'rasterizePDF': false,
+        'copies': 1,
+        'deviceName': destination.id,
         'dpiHorizontal': "horizontal_dpi" in printTicketStore.dpi.getValue() ?
            printTicketStore.dpi.getValue().horizontal_dpi : 0,
         'dpiVertical': "vertical_dpi" in printTicketStore.dpi.getValue() ?
            printTicketStore.dpi.getValue().vertical_dpi : 0,
+        'duplex': printTicketStore.duplex.getValue() ?
+            NativeLayer.DuplexMode.LONG_EDGE : NativeLayer.DuplexMode.SIMPLEX,
+        'printToPDF': destination.id ==
+                print_preview.Destination.GooglePromotedId.SAVE_AS_PDF,
+        'printWithCloudPrint': !destination.isLocal,
+        'printWithPrivet': destination.isPrivet,
+        'printWithExtension': destination.isExtension,
+        'rasterizePDF': false,
         'shouldPrintBackgrounds': printTicketStore.cssBackground.getValue(),
         'shouldPrintSelectionOnly': printTicketStore.selectionOnly.getValue()
       };
@@ -369,20 +368,18 @@
              'Implemented for Windows only');
 
       var ticket = {
-        'pageRange': printTicketStore.pageRange.getDocumentPageRanges(),
         'mediaSize': printTicketStore.mediaSize.getValue(),
         'pageCount': printTicketStore.pageRange.getPageNumberSet().size,
         'landscape': printTicketStore.landscape.getValue(),
         'color': this.getNativeColorModel_(destination, printTicketStore.color),
-        'headerFooterEnabled': printTicketStore.headerFooter.getValue(),
+        'headerFooterEnabled': false,  // Only used in print preview
         'marginsType': printTicketStore.marginsType.getValue(),
-        'generateDraftData': true, // TODO(rltoscano): What should this be?
         'duplex': printTicketStore.duplex.getValue() ?
             NativeLayer.DuplexMode.LONG_EDGE : NativeLayer.DuplexMode.SIMPLEX,
         'copies': printTicketStore.copies.getValueAsNumber(),
         'collate': printTicketStore.collate.getValue(),
         'shouldPrintBackgrounds': printTicketStore.cssBackground.getValue(),
-        'shouldPrintSelectionOnly': printTicketStore.selectionOnly.getValue(),
+        'shouldPrintSelectionOnly': false,  // Only used in print preview
         'previewModifiable': documentInfo.isModifiable,
         'printToPDF': destination.id ==
             print_preview.Destination.GooglePromotedId.SAVE_AS_PDF,
@@ -396,8 +393,6 @@
         'dpiVertical': "vertical_dpi" in printTicketStore.dpi.getValue() ?
            printTicketStore.dpi.getValue().vertical_dpi : 0,
         'deviceName': destination.id,
-        'isFirstRequest': false,
-        'requestID': -1,
         'fitToPageEnabled': printTicketStore.fitToPage.getValue(),
         'pageWidth': documentInfo.pageSize.width,
         'pageHeight': documentInfo.pageSize.height,
diff --git a/chrome/browser/tab_contents/navigation_metrics_recorder.cc b/chrome/browser/tab_contents/navigation_metrics_recorder.cc
index cebf0281..d97c7df 100644
--- a/chrome/browser/tab_contents/navigation_metrics_recorder.cc
+++ b/chrome/browser/tab_contents/navigation_metrics_recorder.cc
@@ -88,6 +88,7 @@
       !ui::PageTransitionCoreTypeIs(navigation_handle->GetPageTransition(),
                                     ui::PAGE_TRANSITION_TYPED)) {
     if (!navigation_handle->GetPreviousURL().is_empty()) {
+      // TODO(meacer): Remove once data URL navigations are blocked.
       rappor::SampleDomainAndRegistryFromGURL(
           rappor_service_, "Navigation.Scheme.Data",
           navigation_handle->GetPreviousURL());
@@ -96,6 +97,7 @@
     // Also record the mime type of the data: URL.
     std::string mime_type;
     std::string charset;
+    // TODO(meacer): Remove once data URL navigations are blocked.
     if (net::DataURL::Parse(last_committed_entry->GetVirtualURL(), &mime_type,
                             &charset, nullptr)) {
       RecordDataURLMimeType(mime_type);
diff --git a/chrome/browser/tab_contents/navigation_metrics_recorder_browsertest.cc b/chrome/browser/tab_contents/navigation_metrics_recorder_browsertest.cc
index 02a38d5..7883d7b 100644
--- a/chrome/browser/tab_contents/navigation_metrics_recorder_browsertest.cc
+++ b/chrome/browser/tab_contents/navigation_metrics_recorder_browsertest.cc
@@ -17,14 +17,6 @@
 
 typedef InProcessBrowserTest NavigationMetricsRecorderBrowserTest;
 
-// Performs a content initiated navigation to |url|.
-void RedirectToUrl(content::WebContents* web_contents, const GURL& url) {
-  content::TestNavigationObserver observer(web_contents, 1);
-  EXPECT_TRUE(content::ExecuteScript(
-      web_contents, std::string("window.location.href='") + url.spec() + "'"));
-  observer.Wait();
-}
-
 IN_PROC_BROWSER_TEST_F(NavigationMetricsRecorderBrowserTest, TestMetrics) {
   content::WebContents* web_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
@@ -46,79 +38,54 @@
                                5 /* data: */, 1);
   // Since there was no previous URL, Rappor shouldn't record anything.
   EXPECT_EQ(0, rappor_service.GetReportsCount());
-
-  // Navigate to an empty page and redirect it to a data: URL. Rappor should
-  // record a report.
-  ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));
-  content::TestNavigationObserver observer(web_contents, 1);
-  EXPECT_TRUE(content::ExecuteScript(
-      web_contents, "window.location.href='data:text/html, <html></html>'"));
-  observer.Wait();
-
-  EXPECT_EQ(1, rappor_service.GetReportsCount());
-  std::string sample;
-  rappor::RapporType type;
-  EXPECT_TRUE(rappor_service.GetRecordedSampleForMetric(
-      "Navigation.Scheme.Data", &sample, &type));
-  EXPECT_EQ("about://", sample);
-  EXPECT_EQ(rappor::ETLD_PLUS_ONE_RAPPOR_TYPE, type);
 }
 
 IN_PROC_BROWSER_TEST_F(NavigationMetricsRecorderBrowserTest, DataURLMimeTypes) {
   base::HistogramTester histograms;
-  content::WebContents* web_contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
 
   // HTML:
-  RedirectToUrl(web_contents, GURL("data:text/html, <html></html>"));
+  ui_test_utils::NavigateToURL(browser(),
+                               GURL("data:text/html, <html></html>"));
   histograms.ExpectTotalCount("Navigation.MainFrameScheme", 1);
   histograms.ExpectBucketCount("Navigation.MainFrameScheme", 5 /* data: */, 1);
   histograms.ExpectTotalCount("Navigation.MainFrameSchemeDifferentPage", 1);
   histograms.ExpectBucketCount("Navigation.MainFrameSchemeDifferentPage",
                                5 /* data: */, 1);
-  histograms.ExpectTotalCount("Navigation.MainFrameScheme.DataUrl.MimeType", 1);
-  histograms.ExpectBucketCount("Navigation.MainFrameScheme.DataUrl.MimeType",
-                               NavigationMetricsRecorder::MIME_TYPE_HTML, 1);
+  histograms.ExpectTotalCount("Navigation.MainFrameScheme.DataUrl.MimeType", 0);
 
   // SVG:
-  RedirectToUrl(web_contents,
-                GURL("data:image/svg+xml,<!DOCTYPE svg><svg version=\"1.1\" "
-                     "xmlns=\"http://www.w3.org/2000/svg\"></svg>"));
+  ui_test_utils::NavigateToURL(
+      browser(), GURL("data:image/svg+xml,<!DOCTYPE svg><svg version=\"1.1\" "
+                      "xmlns=\"http://www.w3.org/2000/svg\"></svg>"));
   histograms.ExpectTotalCount("Navigation.MainFrameScheme", 2);
   histograms.ExpectBucketCount("Navigation.MainFrameScheme", 5 /* data: */, 2);
   histograms.ExpectTotalCount("Navigation.MainFrameSchemeDifferentPage", 2);
   histograms.ExpectBucketCount("Navigation.MainFrameSchemeDifferentPage",
                                5 /* data: */, 2);
-  histograms.ExpectTotalCount("Navigation.MainFrameScheme.DataUrl.MimeType", 2);
-  histograms.ExpectBucketCount("Navigation.MainFrameScheme.DataUrl.MimeType",
-                               NavigationMetricsRecorder::MIME_TYPE_SVG, 1);
+  histograms.ExpectTotalCount("Navigation.MainFrameScheme.DataUrl.MimeType", 0);
 
   // Base64 encoded HTML:
-  RedirectToUrl(web_contents,
-                GURL("data:text/html;base64, PGh0bWw+YmFzZTY0PC9odG1sPg=="));
+  ui_test_utils::NavigateToURL(
+      browser(), GURL("data:text/html;base64, PGh0bWw+YmFzZTY0PC9odG1sPg=="));
   histograms.ExpectTotalCount("Navigation.MainFrameScheme", 3);
   histograms.ExpectBucketCount("Navigation.MainFrameScheme", 5 /* data: */, 3);
   histograms.ExpectTotalCount("Navigation.MainFrameSchemeDifferentPage", 3);
   histograms.ExpectBucketCount("Navigation.MainFrameSchemeDifferentPage",
                                5 /* data: */, 3);
-  histograms.ExpectTotalCount("Navigation.MainFrameScheme.DataUrl.MimeType", 3);
-  histograms.ExpectBucketCount("Navigation.MainFrameScheme.DataUrl.MimeType",
-                               NavigationMetricsRecorder::MIME_TYPE_HTML, 2);
+  histograms.ExpectTotalCount("Navigation.MainFrameScheme.DataUrl.MimeType", 0);
 
   // Plain text:
-  RedirectToUrl(web_contents, GURL("data:text/plain, test"));
+  ui_test_utils::NavigateToURL(browser(), GURL("data:text/plain, test"));
   histograms.ExpectTotalCount("Navigation.MainFrameScheme", 4);
   histograms.ExpectBucketCount("Navigation.MainFrameScheme", 5 /* data: */, 4);
   histograms.ExpectTotalCount("Navigation.MainFrameSchemeDifferentPage", 4);
   histograms.ExpectBucketCount("Navigation.MainFrameSchemeDifferentPage",
                                5 /* data: */, 4);
-  histograms.ExpectTotalCount("Navigation.MainFrameScheme.DataUrl.MimeType", 4);
-  histograms.ExpectBucketCount("Navigation.MainFrameScheme.DataUrl.MimeType",
-                               NavigationMetricsRecorder::MIME_TYPE_OTHER, 1);
+  histograms.ExpectTotalCount("Navigation.MainFrameScheme.DataUrl.MimeType", 0);
 
   // Base64 encoded PNG:
-  RedirectToUrl(
-      web_contents,
+  ui_test_utils::NavigateToURL(
+      browser(),
       GURL("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA"
            "AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO"
            "9TXL0Y4OHwAAAABJRU5ErkJggg=="));
@@ -127,9 +94,7 @@
   histograms.ExpectTotalCount("Navigation.MainFrameSchemeDifferentPage", 5);
   histograms.ExpectBucketCount("Navigation.MainFrameSchemeDifferentPage",
                                5 /* data: */, 5);
-  histograms.ExpectTotalCount("Navigation.MainFrameScheme.DataUrl.MimeType", 5);
-  histograms.ExpectBucketCount("Navigation.MainFrameScheme.DataUrl.MimeType",
-                               NavigationMetricsRecorder::MIME_TYPE_OTHER, 2);
+  histograms.ExpectTotalCount("Navigation.MainFrameScheme.DataUrl.MimeType", 0);
 }
 
 }  // namespace
diff --git a/chrome/browser/ui/app_list/search_answer_web_contents_delegate.cc b/chrome/browser/ui/app_list/search_answer_web_contents_delegate.cc
index 5eae234..d90fe66f 100644
--- a/chrome/browser/ui/app_list/search_answer_web_contents_delegate.cc
+++ b/chrome/browser/ui/app_list/search_answer_web_contents_delegate.cc
@@ -30,6 +30,7 @@
               content::SiteInstance::Create(browser_context)))),
       answer_server_url_(switches::AnswerServerUrl()) {
   Observe(web_contents_.get());
+  web_contents_->SetDelegate(this);
   web_view_->set_owned_by_client();
   web_view_->SetWebContents(web_contents_.get());
 }
@@ -72,6 +73,12 @@
   web_contents_->GetRenderViewHost()->EnablePreferredSizeMode();
 }
 
+void SearchAnswerWebContentsDelegate::UpdatePreferredSize(
+    content::WebContents* web_contents,
+    const gfx::Size& pref_size) {
+  web_view_->SetPreferredSize(pref_size);
+}
+
 void SearchAnswerWebContentsDelegate::DidFinishNavigation(
     content::NavigationHandle* navigation_handle) {
   if (navigation_handle->GetURL() != current_request_url_)
@@ -96,7 +103,6 @@
   if (!received_answer_)
     return;
 
-  web_view_->SetPreferredSize(web_contents_->GetPreferredSize());
   model_->SetSearchAnswerAvailable(true);
 }
 
diff --git a/chrome/browser/ui/app_list/search_answer_web_contents_delegate.h b/chrome/browser/ui/app_list/search_answer_web_contents_delegate.h
index 0de6377f..aa295c1 100644
--- a/chrome/browser/ui/app_list/search_answer_web_contents_delegate.h
+++ b/chrome/browser/ui/app_list/search_answer_web_contents_delegate.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <string>
 
+#include "content/public/browser/web_contents_delegate.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "url/gurl.h"
 
@@ -27,7 +28,8 @@
 namespace app_list {
 
 // Manages the web contents for the search answer web view.
-class SearchAnswerWebContentsDelegate : public content::WebContentsObserver {
+class SearchAnswerWebContentsDelegate : public content::WebContentsDelegate,
+                                        public content::WebContentsObserver {
  public:
   SearchAnswerWebContentsDelegate(content::BrowserContext* browser_context,
                                   app_list::AppListModel* model);
@@ -43,6 +45,10 @@
   // 'set_owned_by_client()' set.
   views::View* web_view();
 
+  // content::WebContentsDelegate overrides:
+  void UpdatePreferredSize(content::WebContents* web_contents,
+                           const gfx::Size& pref_size) override;
+
   // content::WebContentsObserver overrides:
   void DidFinishNavigation(
       content::NavigationHandle* navigation_handle) override;
diff --git a/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.cc b/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.cc
index 2c95114..a5980d58d 100644
--- a/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.cc
+++ b/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.cc
@@ -275,9 +275,8 @@
         base::Bind(&ChromeNativeAppWindowViewsAuraAsh::OnMenuClosed,
                    base::Unretained(this))));
     menu_runner_.reset(new views::MenuRunner(
-        menu_model_adapter_->CreateMenu(), views::MenuRunner::HAS_MNEMONICS |
-                                               views::MenuRunner::CONTEXT_MENU |
-                                               views::MenuRunner::ASYNC));
+        menu_model_adapter_->CreateMenu(),
+        views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU));
     menu_runner_->RunMenuAt(source->GetWidget(), NULL,
                             gfx::Rect(p, gfx::Size(0, 0)),
                             views::MENU_ANCHOR_TOPLEFT, source_type);
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_context_menu.cc b/chrome/browser/ui/views/bookmarks/bookmark_context_menu.cc
index e6ce22e..cd2b00e 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_context_menu.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_context_menu.cc
@@ -54,8 +54,7 @@
       menu_runner_(new views::MenuRunner(menu_,
                                          views::MenuRunner::HAS_MNEMONICS |
                                              views::MenuRunner::IS_NESTED |
-                                             views::MenuRunner::CONTEXT_MENU |
-                                             views::MenuRunner::ASYNC)),
+                                             views::MenuRunner::CONTEXT_MENU)),
       observer_(NULL),
       close_on_remove_(close_on_remove) {
   ui::SimpleMenuModel* menu_model = controller_->menu_model();
@@ -110,8 +109,7 @@
   return (id != IDC_BOOKMARK_BAR_REMOVE) || close_on_remove_;
 }
 
-void BookmarkContextMenu::OnMenuClosed(views::MenuItemView* menu,
-                                       views::MenuRunner::RunResult result) {
+void BookmarkContextMenu::OnMenuClosed(views::MenuItemView* menu) {
   if (observer_)
     observer_->OnContextMenuClosed();
 }
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_context_menu.h b/chrome/browser/ui/views/bookmarks/bookmark_context_menu.h
index 60de49e..476c049 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_context_menu.h
+++ b/chrome/browser/ui/views/bookmarks/bookmark_context_menu.h
@@ -67,8 +67,7 @@
   bool IsCommandEnabled(int command_id) const override;
   bool IsCommandVisible(int command_id) const override;
   bool ShouldCloseAllMenusOnExecute(int id) override;
-  void OnMenuClosed(views::MenuItemView* menu,
-                    views::MenuRunner::RunResult result) override;
+  void OnMenuClosed(views::MenuItemView* menu) override;
 
   // Overridden from BookmarkContextMenuControllerDelegate:
   void CloseMenu() override;
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc b/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc
index 220e34ba..022aa5a 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc
@@ -246,18 +246,12 @@
        tree_model_->GetRoot());
 
   context_menu_runner_.reset(new views::MenuRunner(
-      GetMenuModel(), views::MenuRunner::HAS_MNEMONICS |
-                          views::MenuRunner::CONTEXT_MENU |
-                          views::MenuRunner::ASYNC));
+      GetMenuModel(),
+      views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU));
 
-  if (context_menu_runner_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(),
-                                      NULL,
-                                      gfx::Rect(point, gfx::Size()),
-                                      views::MENU_ANCHOR_TOPRIGHT,
-                                      source_type) ==
-      views::MenuRunner::MENU_DELETED) {
-    return;
-  }
+  context_menu_runner_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(),
+                                  NULL, gfx::Rect(point, gfx::Size()),
+                                  views::MENU_ANCHOR_TOPRIGHT, source_type);
 }
 
 const char* BookmarkEditorView::GetClassName() const {
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.cc b/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.cc
index eda0430..35fa9780 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.cc
@@ -38,7 +38,7 @@
   menu_delegate_->Init(this, NULL, node, start_child_index,
                        BookmarkMenuDelegate::HIDE_PERMANENT_FOLDERS,
                        BOOKMARK_LAUNCH_LOCATION_BAR_SUBFOLDER);
-  int run_type = views::MenuRunner::ASYNC;
+  int run_type = 0;
   if (for_drop)
     run_type |= views::MenuRunner::FOR_DROP;
   menu_runner_.reset(new views::MenuRunner(menu_delegate_->menu(), run_type));
@@ -58,11 +58,8 @@
   menu_delegate_->GetBookmarkModel()->AddObserver(this);
   // We only delete ourself after the menu completes, so we can safely ignore
   // the return value.
-  ignore_result(menu_runner_->RunMenuAt(menu_delegate_->parent(),
-                                        menu_button,
-                                        bounds,
-                                        anchor,
-                                        ui::MENU_SOURCE_NONE));
+  menu_runner_->RunMenuAt(menu_delegate_->parent(), menu_button, bounds, anchor,
+                          ui::MENU_SOURCE_NONE);
 }
 
 void BookmarkMenuController::Cancel() {
@@ -152,8 +149,7 @@
   return menu_delegate_->GetDragOperations(sender);
 }
 
-void BookmarkMenuController::OnMenuClosed(views::MenuItemView* menu,
-                                          views::MenuRunner::RunResult result) {
+void BookmarkMenuController::OnMenuClosed(views::MenuItemView* menu) {
   delete this;
 }
 
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.h b/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.h
index 6896e5f..d0f4d1b 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.h
+++ b/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.h
@@ -106,8 +106,7 @@
   void WriteDragData(views::MenuItemView* sender,
                      ui::OSExchangeData* data) override;
   int GetDragOperations(views::MenuItemView* sender) override;
-  void OnMenuClosed(views::MenuItemView* menu,
-                    views::MenuRunner::RunResult result) override;
+  void OnMenuClosed(views::MenuItemView* menu) override;
   views::MenuItemView* GetSiblingMenu(views::MenuItemView* menu,
                                       const gfx::Point& screen_point,
                                       views::MenuAnchorPosition* anchor,
diff --git a/chrome/browser/ui/views/download/download_shelf_context_menu_view.cc b/chrome/browser/ui/views/download/download_shelf_context_menu_view.cc
index 7ada929..2c9b4c8 100644
--- a/chrome/browser/ui/views/download/download_shelf_context_menu_view.cc
+++ b/chrome/browser/ui/views/download/download_shelf_context_menu_view.cc
@@ -30,8 +30,7 @@
 
   menu_runner_.reset(new views::MenuRunner(
       menu_model,
-      views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU |
-          views::MenuRunner::ASYNC,
+      views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU,
       base::Bind(&DownloadShelfContextMenuView::OnMenuClosed,
                  base::Unretained(this), on_menu_closed_callback)));
 
diff --git a/chrome/browser/ui/views/extensions/media_galleries_dialog_views.cc b/chrome/browser/ui/views/extensions/media_galleries_dialog_views.cc
index 51218d0..d8583a6d 100644
--- a/chrome/browser/ui/views/extensions/media_galleries_dialog_views.cc
+++ b/chrome/browser/ui/views/extensions/media_galleries_dialog_views.cc
@@ -302,8 +302,7 @@
                                                 MediaGalleryPrefId id) {
   context_menu_runner_.reset(new views::MenuRunner(
       controller_->GetContextMenu(id),
-      views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU |
-          views::MenuRunner::ASYNC,
+      views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU,
       base::Bind(&MediaGalleriesDialogViews::OnMenuClosed,
                  base::Unretained(this))));
 
diff --git a/chrome/browser/ui/views/frame/browser_frame.cc b/chrome/browser/ui/views/frame/browser_frame.cc
index 0df9950..14d7bbf 100644
--- a/chrome/browser/ui/views/frame/browser_frame.cc
+++ b/chrome/browser/ui/views/frame/browser_frame.cc
@@ -250,8 +250,7 @@
   if (hit_test == HTCAPTION || hit_test == HTNOWHERE) {
     menu_runner_.reset(new views::MenuRunner(
         GetSystemMenuModel(),
-        views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU |
-            views::MenuRunner::ASYNC,
+        views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU,
         base::Bind(&BrowserFrame::OnMenuClosed, base::Unretained(this))));
     menu_runner_->RunMenuAt(source->GetWidget(), nullptr,
                             gfx::Rect(p, gfx::Size(0, 0)),
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc b/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
index 0cf4dc0..c68146b2 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
@@ -295,11 +295,9 @@
 #if defined(OS_LINUX)
   views::MenuRunner menu_runner(frame()->GetSystemMenuModel(),
                                 views::MenuRunner::HAS_MNEMONICS);
-  ignore_result(menu_runner.RunMenuAt(browser_view()->GetWidget(),
-                                      window_icon_,
-                                      window_icon_->GetBoundsInScreen(),
-                                      views::MENU_ANCHOR_TOPLEFT,
-                                      ui::MENU_SOURCE_MOUSE));
+  menu_runner.RunMenuAt(browser_view()->GetWidget(), window_icon_,
+                        window_icon_->GetBoundsInScreen(),
+                        views::MENU_ANCHOR_TOPLEFT, ui::MENU_SOURCE_MOUSE);
 #endif
 }
 
diff --git a/chrome/browser/ui/views/menu_model_adapter_test.cc b/chrome/browser/ui/views/menu_model_adapter_test.cc
index 732e2fd1..1bde65a2 100644
--- a/chrome/browser/ui/views/menu_model_adapter_test.cc
+++ b/chrome/browser/ui/views/menu_model_adapter_test.cc
@@ -174,8 +174,8 @@
                                     this, true);
 
     menu_ = menu_model_adapter_.CreateMenu();
-    menu_runner_.reset(new views::MenuRunner(
-        menu_, views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::ASYNC));
+    menu_runner_.reset(
+        new views::MenuRunner(menu_, views::MenuRunner::HAS_MNEMONICS));
 
     ViewEventTestBase::SetUp();
   }
@@ -199,11 +199,8 @@
     gfx::Point screen_location;
     views::View::ConvertPointToScreen(source, &screen_location);
     gfx::Rect bounds(screen_location, source->size());
-    ignore_result(menu_runner_->RunMenuAt(source->GetWidget(),
-                                          button_,
-                                          bounds,
-                                          views::MENU_ANCHOR_TOPLEFT,
-                                          ui::MENU_SOURCE_NONE));
+    menu_runner_->RunMenuAt(source->GetWidget(), button_, bounds,
+                            views::MENU_ANCHOR_TOPLEFT, ui::MENU_SOURCE_NONE);
   }
 
   // ViewEventTestBase implementation
diff --git a/chrome/browser/ui/views/menu_test_base.cc b/chrome/browser/ui/views/menu_test_base.cc
index f7b5aa91..b44d2f8 100644
--- a/chrome/browser/ui/views/menu_test_base.cc
+++ b/chrome/browser/ui/views/menu_test_base.cc
@@ -38,7 +38,7 @@
 }
 
 int MenuTestBase::GetMenuRunnerFlags() {
-  return views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::ASYNC;
+  return views::MenuRunner::HAS_MNEMONICS;
 }
 
 void MenuTestBase::SetUp() {
@@ -79,11 +79,8 @@
   gfx::Point screen_location;
   views::View::ConvertPointToScreen(source, &screen_location);
   gfx::Rect bounds(screen_location, source->size());
-  ignore_result(menu_runner_->RunMenuAt(source->GetWidget(),
-                                        button_,
-                                        bounds,
-                                        views::MENU_ANCHOR_TOPLEFT,
-                                        ui::MENU_SOURCE_NONE));
+  menu_runner_->RunMenuAt(source->GetWidget(), button_, bounds,
+                          views::MENU_ANCHOR_TOPLEFT, ui::MENU_SOURCE_NONE);
 }
 
 void MenuTestBase::ExecuteCommand(int id) {
diff --git a/chrome/browser/ui/views/menu_view_drag_and_drop_test.cc b/chrome/browser/ui/views/menu_view_drag_and_drop_test.cc
index 84e09ce..143dc420 100644
--- a/chrome/browser/ui/views/menu_view_drag_and_drop_test.cc
+++ b/chrome/browser/ui/views/menu_view_drag_and_drop_test.cc
@@ -463,7 +463,7 @@
 
 int MenuViewDragAndDropForDropStayOpen::GetMenuRunnerFlags() {
   return views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::NESTED_DRAG |
-         views::MenuRunner::FOR_DROP | views::MenuRunner::ASYNC;
+         views::MenuRunner::FOR_DROP;
 }
 
 void MenuViewDragAndDropForDropStayOpen::DoTestWithMenuOpen() {
@@ -495,8 +495,7 @@
 };
 
 int MenuViewDragAndDropForDropCancel::GetMenuRunnerFlags() {
-  return views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::FOR_DROP |
-         views::MenuRunner::ASYNC;
+  return views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::FOR_DROP;
 }
 
 void MenuViewDragAndDropForDropCancel::DoTestWithMenuOpen() {
diff --git a/chrome/browser/ui/views/page_info/permission_selector_row.cc b/chrome/browser/ui/views/page_info/permission_selector_row.cc
index 8a30dca..c07a3b85 100644
--- a/chrome/browser/ui/views/page_info/permission_selector_row.cc
+++ b/chrome/browser/ui/views/page_info/permission_selector_row.cc
@@ -109,9 +109,8 @@
 void PermissionMenuButton::OnMenuButtonClicked(views::MenuButton* source,
                                                const gfx::Point& point,
                                                const ui::Event* event) {
-  menu_runner_.reset(new views::MenuRunner(
-      menu_model_,
-      views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::ASYNC));
+  menu_runner_.reset(
+      new views::MenuRunner(menu_model_, views::MenuRunner::HAS_MNEMONICS));
 
   gfx::Point p(point);
   p.Offset(is_rtl_display_ ? source->width() : -source->width(), 0);
diff --git a/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc b/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc
index 0933b3b..9ca35ce 100644
--- a/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc
+++ b/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc
@@ -117,9 +117,8 @@
 void PermissionCombobox::OnMenuButtonClicked(views::MenuButton* source,
                                              const gfx::Point& point,
                                              const ui::Event* event) {
-  menu_runner_.reset(new views::MenuRunner(
-      model_.get(),
-      views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::ASYNC));
+  menu_runner_.reset(
+      new views::MenuRunner(model_.get(), views::MenuRunner::HAS_MNEMONICS));
 
   gfx::Point p(point);
   p.Offset(-source->width(), 0);
diff --git a/chrome/browser/ui/views/status_icons/status_icon_win.cc b/chrome/browser/ui/views/status_icons/status_icon_win.cc
index e153be21..7b98969be 100644
--- a/chrome/browser/ui/views/status_icons/status_icon_win.cc
+++ b/chrome/browser/ui/views/status_icons/status_icon_win.cc
@@ -64,11 +64,8 @@
 
   menu_runner_.reset(new views::MenuRunner(menu_model_,
                                            views::MenuRunner::HAS_MNEMONICS));
-  ignore_result(menu_runner_->RunMenuAt(NULL,
-                                        NULL,
-                                        gfx::Rect(cursor_pos, gfx::Size()),
-                                        views::MENU_ANCHOR_TOPLEFT,
-                                        ui::MENU_SOURCE_MOUSE));
+  menu_runner_->RunMenuAt(NULL, NULL, gfx::Rect(cursor_pos, gfx::Size()),
+                          views::MENU_ANCHOR_TOPLEFT, ui::MENU_SOURCE_MOUSE);
 }
 
 void StatusIconWin::HandleBalloonClickEvent() {
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
index c1473736a..efc5b8bc 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
@@ -125,14 +125,9 @@
   }
 
   void RunMenuAt(const gfx::Point& point, ui::MenuSourceType source_type) {
-    if (menu_runner_->RunMenuAt(tab_->GetWidget(),
-                                NULL,
-                                gfx::Rect(point, gfx::Size()),
-                                views::MENU_ANCHOR_TOPLEFT,
-                                source_type) ==
-        views::MenuRunner::MENU_DELETED) {
-      return;
-    }
+    menu_runner_->RunMenuAt(tab_->GetWidget(), NULL,
+                            gfx::Rect(point, gfx::Size()),
+                            views::MENU_ANCHOR_TOPLEFT, source_type);
   }
 
   // Overridden from ui::SimpleMenuModel::Delegate:
diff --git a/chrome/browser/ui/views/task_manager_view.cc b/chrome/browser/ui/views/task_manager_view.cc
index 0e722e8..333c97b 100644
--- a/chrome/browser/ui/views/task_manager_view.cc
+++ b/chrome/browser/ui/views/task_manager_view.cc
@@ -268,9 +268,8 @@
                               l10n_util::GetStringUTF16(table_column.id));
   }
 
-  menu_runner_.reset(new views::MenuRunner(
-      menu_model_.get(),
-      views::MenuRunner::CONTEXT_MENU | views::MenuRunner::ASYNC));
+  menu_runner_.reset(new views::MenuRunner(menu_model_.get(),
+                                           views::MenuRunner::CONTEXT_MENU));
 
   menu_runner_->RunMenuAt(GetWidget(), nullptr, gfx::Rect(point, gfx::Size()),
                           views::MENU_ANCHOR_TOPLEFT, source_type);
diff --git a/chrome/browser/ui/views/toolbar/app_menu.cc b/chrome/browser/ui/views/toolbar/app_menu.cc
index cc9f443..e1a39e9 100644
--- a/chrome/browser/ui/views/toolbar/app_menu.cc
+++ b/chrome/browser/ui/views/toolbar/app_menu.cc
@@ -802,7 +802,7 @@
                                // so we get the taller menu style.
   PopulateMenu(root_, model);
 
-  int32_t types = views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::ASYNC;
+  int32_t types = views::MenuRunner::HAS_MNEMONICS;
   if (for_drop()) {
     // We add NESTED_DRAG since currently the only operation to open the app
     // menu for is an extension action drag, which is controlled by the child
@@ -1035,8 +1035,7 @@
   return false;
 }
 
-void AppMenu::OnMenuClosed(views::MenuItemView* menu,
-                           views::MenuRunner::RunResult result) {
+void AppMenu::OnMenuClosed(views::MenuItemView* menu) {
   if (bookmark_menu_delegate_.get()) {
     BookmarkModel* model =
         BookmarkModelFactory::GetForBrowserContext(browser_->profile());
diff --git a/chrome/browser/ui/views/toolbar/app_menu.h b/chrome/browser/ui/views/toolbar/app_menu.h
index 10bb303..7e34995c 100644
--- a/chrome/browser/ui/views/toolbar/app_menu.h
+++ b/chrome/browser/ui/views/toolbar/app_menu.h
@@ -96,8 +96,7 @@
   void WillShowMenu(views::MenuItemView* menu) override;
   void WillHideMenu(views::MenuItemView* menu) override;
   bool ShouldCloseOnDragComplete() override;
-  void OnMenuClosed(views::MenuItemView* menu,
-                    views::MenuRunner::RunResult result) override;
+  void OnMenuClosed(views::MenuItemView* menu) override;
   bool ShouldExecuteCommandWithoutClosingMenu(int command_id,
                                               const ui::Event& event) override;
 
diff --git a/chrome/browser/ui/views/toolbar/toolbar_action_view.cc b/chrome/browser/ui/views/toolbar/toolbar_action_view.cc
index c447979..710d41fc 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_action_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_action_view.cc
@@ -274,8 +274,8 @@
   gfx::Point screen_loc;
   ConvertPointToScreen(this, &screen_loc);
 
-  int run_types = views::MenuRunner::HAS_MNEMONICS |
-                  views::MenuRunner::CONTEXT_MENU | views::MenuRunner::ASYNC;
+  int run_types =
+      views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU;
   if (delegate_->ShownInsideMenu())
     run_types |= views::MenuRunner::IS_NESTED;
 
@@ -294,9 +294,8 @@
   menu_ = menu_adapter_->CreateMenu();
   menu_runner_.reset(new views::MenuRunner(menu_, run_types));
 
-  ignore_result(
-      menu_runner_->RunMenuAt(parent, this, gfx::Rect(screen_loc, size()),
-                              views::MENU_ANCHOR_TOPLEFT, source_type));
+  menu_runner_->RunMenuAt(parent, this, gfx::Rect(screen_loc, size()),
+                          views::MENU_ANCHOR_TOPLEFT, source_type);
 }
 
 bool ToolbarActionView::CloseActiveMenuIfNeeded() {
diff --git a/chrome/browser/ui/views/toolbar/toolbar_button.cc b/chrome/browser/ui/views/toolbar/toolbar_button.cc
index 50ee412e..17b5979 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_button.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_button.cc
@@ -214,12 +214,11 @@
       model_.get(),
       base::Bind(&ToolbarButton::OnMenuClosed, base::Unretained(this))));
   menu_model_adapter_->set_triggerable_event_flags(triggerable_event_flags());
-  menu_runner_.reset(new views::MenuRunner(
-      menu_model_adapter_->CreateMenu(),
-      views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::ASYNC));
-  ignore_result(menu_runner_->RunMenuAt(
-      GetWidget(), nullptr, gfx::Rect(menu_position, gfx::Size(0, 0)),
-      views::MENU_ANCHOR_TOPLEFT, source_type));
+  menu_runner_.reset(new views::MenuRunner(menu_model_adapter_->CreateMenu(),
+                                           views::MenuRunner::HAS_MNEMONICS));
+  menu_runner_->RunMenuAt(GetWidget(), nullptr,
+                          gfx::Rect(menu_position, gfx::Size(0, 0)),
+                          views::MENU_ANCHOR_TOPLEFT, source_type);
 }
 
 void ToolbarButton::OnMenuClosed() {
diff --git a/chrome/browser/ui/views/translate/translate_bubble_view.cc b/chrome/browser/ui/views/translate/translate_bubble_view.cc
index 7c6d8c65..5febd67 100644
--- a/chrome/browser/ui/views/translate/translate_bubble_view.cc
+++ b/chrome/browser/ui/views/translate/translate_bubble_view.cc
@@ -281,8 +281,8 @@
         DenialMenuItem::NEVER_TRANSLATE_SITE,
         IDS_TRANSLATE_BUBBLE_NEVER_TRANSLATE_SITE);
 
-    denial_menu_runner_.reset(new views::MenuRunner(denial_menu_model_.get(),
-                                                    views::MenuRunner::ASYNC));
+    denial_menu_runner_.reset(
+        new views::MenuRunner(denial_menu_model_.get(), 0));
   }
   gfx::Rect screen_bounds = source->GetBoundsInScreen();
   denial_menu_runner_->RunMenuAt(source->GetWidget(), source, screen_bounds,
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_handler.cc b/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
index 05844861..77e357e 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
@@ -832,9 +832,6 @@
   ReportPrintDocumentTypeHistogram(print_preview_ui()->source_is_modifiable() ?
                                    HTML_DOCUMENT : PDF_DOCUMENT);
 
-  // Never try to add headers/footers here. It's already in the generated PDF.
-  settings->SetBoolean(printing::kSettingHeaderFooterEnabled, false);
-
   bool print_to_pdf = false;
   bool is_cloud_printer = false;
   bool print_with_privet = false;
@@ -979,12 +976,6 @@
   // current print preview dialog is still handling its print job.
   ClearInitiatorDetails();
 
-  // The PDF being printed contains only the pages that the user selected,
-  // so ignore the page range and print all pages.
-  settings->Remove(printing::kSettingPageRange, nullptr);
-  // Reset selection only flag for the same reason.
-  settings->SetBoolean(printing::kSettingShouldPrintSelectionOnly, false);
-
   // Set ID to know whether printing is for preview.
   settings->SetInteger(printing::kPreviewUIID,
                        print_preview_ui()->GetIDForPrintPreviewUI());
diff --git a/chrome/browser/ui/webui/sync_internals_browsertest.js b/chrome/browser/ui/webui/sync_internals_browsertest.js
index 3f52e2e1..ad896732 100644
--- a/chrome/browser/ui/webui/sync_internals_browsertest.js
+++ b/chrome/browser/ui/webui/sync_internals_browsertest.js
@@ -198,18 +198,22 @@
   ],
   'type_status': [
     {
+      'status': 'header',
       'name': 'Model Type',
       'num_entries': 'Total Entries',
       'num_live': 'Live Entries',
-      'status': 'header',
-      'value': 'Group Type'
+      'message': 'Message',
+      'state': 'State',
+      'group_type': 'Group Type',
     },
     {
+      'status': 'ok',
       'name': 'Bookmarks',
       'num_entries': 2793,
       'num_live': 2793,
-      'status': 'ok',
-      'value': 'Active: GROUP_UI'
+      'message': '',
+      'state': 'Running',
+      'group_type': 'Group UI',
     },
   ],
   'unrecoverable_error_detected': false
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 709f029..8e54d5edb 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -5177,7 +5177,7 @@
     testonly = true
     embedded_services = [ ":mash_browser_tests_manifest" ]
     standalone_services = [ "//services/tracing:manifest" ]
-    catalog_deps = [ "//chrome/app:catalog_for_mus" ]
+    catalog_deps = [ "//chrome/app:catalog_for_mash" ]
   }
 
   copy("mash_browser_tests_catalog_copy") {
@@ -5217,7 +5217,7 @@
     testonly = true
     embedded_services = [ ":mus_browser_tests_manifest" ]
     standalone_services = [ "//services/tracing:manifest" ]
-    catalog_deps = [ "//chrome/app:catalog_for_mus" ]
+    catalog_deps = [ "//chrome/app:catalog" ]
   }
 
   copy("mus_browser_tests_catalog_copy") {
diff --git a/chrome/test/data/android/popup_test.html b/chrome/test/data/android/popup_test.html
index 544a162..626bcde 100644
--- a/chrome/test/data/android/popup_test.html
+++ b/chrome/test/data/android/popup_test.html
@@ -3,9 +3,9 @@
     <title>Popup test page</title>
     <script type="text/javascript">
       function spawnWindows() {
-        window.open('data:text/html,<html><head><title>Popup #1</title></head><body /></html>');
-        window.open('data:text/html,<html><head><title>Popup #2</title></head><body /></html>');
-        window.open('data:text/html,<html><head><title>Popup #3</title></head><body /></html>');
+        window.open('navigate/one.html');
+        window.open('navigate/two.html');
+        window.open('navigate/three.html');
       }
     </script>
   </head>
diff --git a/chrome/test/data/extensions/platform_apps/web_view/shim/main.js b/chrome/test/data/extensions/platform_apps/web_view/shim/main.js
index ddabbcd..6aceb59 100644
--- a/chrome/test/data/extensions/platform_apps/web_view/shim/main.js
+++ b/chrome/test/data/extensions/platform_apps/web_view/shim/main.js
@@ -430,6 +430,28 @@
   document.body.appendChild(webview);
 }
 
+// This test verifies that guests are blocked from navigating the webview to a
+// data URL.
+function testContentInitiatedNavigationToDataUrlBlocked() {
+  var navUrl = "data:text/html,foo";
+  var webview = document.createElement('webview');
+  webview.addEventListener('consolemessage', function(e) {
+    if (e.message.startsWith(
+        'Not allowed to navigate top frame to data URL:')) {
+      embedder.test.succeed();
+    }
+  });
+  webview.addEventListener('loadstop', function(e) {
+    if (webview.getAttribute('src') == navUrl) {
+      embedder.test.fail();
+    }
+  });
+  webview.setAttribute('src',
+      'data:text/html,<script>window.location.href = "' + navUrl +
+      '";</scr' + 'ipt>');
+  document.body.appendChild(webview);
+}
+
 // Tests that a <webview> that starts with "display: none" style loads
 // properly.
 function testDisplayNoneWebviewLoad() {
@@ -3076,6 +3098,8 @@
   'testAddAndRemoveContentScripts': testAddAndRemoveContentScripts,
   'testAddContentScriptsWithNewWindowAPI':
       testAddContentScriptsWithNewWindowAPI,
+  'testContentInitiatedNavigationToDataUrlBlocked':
+      testContentInitiatedNavigationToDataUrlBlocked,
   'testContentScriptIsInjectedAfterTerminateAndReloadWebView':
       testContentScriptIsInjectedAfterTerminateAndReloadWebView,
   'testContentScriptExistsAsLongAsWebViewTagExists':
diff --git a/chromecast/base/BUILD.gn b/chromecast/base/BUILD.gn
index a4de4ee..16bc654 100644
--- a/chromecast/base/BUILD.gn
+++ b/chromecast/base/BUILD.gn
@@ -38,6 +38,8 @@
     "bind_to_task_runner.h",
     "cast_constants.cc",
     "cast_constants.h",
+    "cast_features.cc",
+    "cast_features.h",
     "cast_paths.cc",
     "cast_paths.h",
     "cast_resource.cc",
@@ -126,6 +128,7 @@
   sources = [
     "alarm_manager_unittest.cc",
     "bind_to_task_runner_unittest.cc",
+    "cast_features_unittest.cc",
     "device_capabilities_impl_unittest.cc",
     "error_codes_unittest.cc",
     "path_utils_unittest.cc",
diff --git a/chromecast/base/cast_features.cc b/chromecast/base/cast_features.cc
new file mode 100644
index 0000000..636f145
--- /dev/null
+++ b/chromecast/base/cast_features.cc
@@ -0,0 +1,197 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/base/cast_features.h"
+
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/lazy_instance.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/field_trial_param_associator.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+
+namespace chromecast {
+namespace {
+
+// A constant used to always activate a FieldTrial.
+const base::FieldTrial::Probability k100PercentProbability = 100;
+
+// The name of the default group to use for Cast DCS features.
+const char kDefaultDCSFeaturesGroup[] = "default_dcs_features_group";
+
+base::LazyInstance<std::unordered_set<int32_t>>::Leaky g_experiment_ids =
+    LAZY_INSTANCE_INITIALIZER;
+bool g_experiment_ids_initialized = false;
+
+void SetExperimentIds(const base::ListValue& list) {
+  DCHECK(!g_experiment_ids_initialized);
+  std::unordered_set<int32_t> ids;
+  for (size_t i = 0; i < list.GetSize(); ++i) {
+    int32_t id;
+    if (list.GetInteger(i, &id)) {
+      ids.insert(id);
+    } else {
+      LOG(ERROR) << "Non-integer value found in experiment id list!";
+    }
+  }
+  g_experiment_ids.Get().swap(ids);
+  g_experiment_ids_initialized = true;
+}
+}  // namespace
+
+// An iterator for a base::DictionaryValue. Use an alias for brevity in loops.
+using Iterator = base::DictionaryValue::Iterator;
+
+void InitializeFeatureList(const base::DictionaryValue& dcs_features,
+                           const base::ListValue& dcs_experiment_ids,
+                           const std::string& cmd_line_enable_features,
+                           const std::string& cmd_line_disable_features) {
+  DCHECK(!base::FeatureList::GetInstance());
+
+  // Set the experiments.
+  SetExperimentIds(dcs_experiment_ids);
+
+  // Initialize the FeatureList from the command line.
+  auto feature_list = base::MakeUnique<base::FeatureList>();
+  feature_list->InitializeFromCommandLine(cmd_line_enable_features,
+                                          cmd_line_disable_features);
+
+  // Override defaults from the DCS config.
+  for (Iterator it(dcs_features); !it.IsAtEnd(); it.Advance()) {
+    // Each feature must have its own FieldTrial object. Since experiments are
+    // controlled server-side for Chromecast, and this class is designed with a
+    // client-side experimentation framework in mind, these parameters are
+    // carefully chosen:
+    //   - The field trial name is unused for our purposes. However, we need to
+    //     maintain a 1:1 mapping with Features in order to properly store and
+    //     access parameters associated with each Feature. Therefore, use the
+    //     Feature's name as the FieldTrial name to ensure uniqueness.
+    //   - The probability is hard-coded to 100% so that the FeatureList always
+    //     respects the value from DCS.
+    //   - The default group is unused; it will be the same for every feature.
+    //   - Expiration year, month, and day use a special value such that the
+    //     feature will never expire.
+    //   - SESSION_RANDOMIZED is used to prevent the need for an
+    //     entropy_provider. However, this value doesn't matter.
+    //   - We don't care about the group_id.
+    //
+    const std::string& feature_name = it.key();
+    auto* field_trial = base::FieldTrialList::FactoryGetFieldTrial(
+        feature_name, k100PercentProbability, kDefaultDCSFeaturesGroup,
+        base::FieldTrialList::kNoExpirationYear, 1 /* month */, 1 /* day */,
+        base::FieldTrial::SESSION_RANDOMIZED, nullptr);
+
+    bool enabled;
+    if (it.value().GetAsBoolean(&enabled)) {
+      // A boolean entry simply either enables or disables a feature.
+      feature_list->RegisterFieldTrialOverride(
+          feature_name,
+          enabled ? base::FeatureList::OVERRIDE_ENABLE_FEATURE
+                  : base::FeatureList::OVERRIDE_DISABLE_FEATURE,
+          field_trial);
+      continue;
+    }
+
+    const base::DictionaryValue* params_dict;
+    if (it.value().GetAsDictionary(&params_dict)) {
+      // A dictionary entry implies that the feature is enabled.
+      feature_list->RegisterFieldTrialOverride(
+          feature_name, base::FeatureList::OVERRIDE_ENABLE_FEATURE,
+          field_trial);
+
+      // If the feature has not been overriden from the command line, set its
+      // parameters accordingly.
+      if (!feature_list->IsFeatureOverriddenFromCommandLine(
+              feature_name, base::FeatureList::OVERRIDE_DISABLE_FEATURE)) {
+        // Build a map of the FieldTrial parameters and associate it to the
+        // FieldTrial.
+        base::FieldTrialParamAssociator::FieldTrialParams params;
+        for (Iterator p(*params_dict); !p.IsAtEnd(); p.Advance()) {
+          std::string val;
+          if (p.value().GetAsString(&val)) {
+            params[p.key()] = val;
+          } else {
+            LOG(ERROR) << "Entry in params dict for \"" << feature_name << "\""
+                       << " feature is not a string. Skipping.";
+          }
+        }
+
+        // Register the params, so that they can be queried by client code.
+        bool success = base::AssociateFieldTrialParams(
+            feature_name, kDefaultDCSFeaturesGroup, params);
+        DCHECK(success);
+      }
+      continue;
+    }
+
+    // Other base::Value types are not supported.
+    LOG(ERROR) << "A DCS feature mapped to an unsupported value. key: "
+               << feature_name << " type: " << it.value().type();
+  }
+
+  base::FeatureList::SetInstance(std::move(feature_list));
+}
+
+base::DictionaryValue GetOverriddenFeaturesForStorage(
+    const base::DictionaryValue& features) {
+  base::DictionaryValue persistent_dict;
+
+  // |features| maps feature names to either a boolean or a dict of params.
+  for (Iterator it(features); !it.IsAtEnd(); it.Advance()) {
+    bool enabled;
+    if (it.value().GetAsBoolean(&enabled)) {
+      persistent_dict.SetBoolean(it.key(), enabled);
+      continue;
+    }
+
+    const base::DictionaryValue* params_dict;
+    if (it.value().GetAsDictionary(&params_dict)) {
+      auto params = base::MakeUnique<base::DictionaryValue>();
+
+      bool bval;
+      int ival;
+      double dval;
+      std::string sval;
+      for (Iterator p(*params_dict); !p.IsAtEnd(); p.Advance()) {
+        const auto& param_key = p.key();
+        const auto& param_val = p.value();
+        if (param_val.GetAsBoolean(&bval)) {
+          params->SetString(param_key, bval ? "true" : "false");
+        } else if (param_val.GetAsInteger(&ival)) {
+          params->SetString(param_key, base::IntToString(ival));
+        } else if (param_val.GetAsDouble(&dval)) {
+          params->SetString(param_key, base::DoubleToString(dval));
+        } else if (param_val.GetAsString(&sval)) {
+          params->SetString(param_key, sval);
+        } else {
+          LOG(ERROR) << "Entry in params dict for \"" << it.key() << "\""
+                     << " is not of a supported type (key: " << p.key()
+                     << ", type: " << param_val.type();
+        }
+      }
+      persistent_dict.Set(it.key(), std::move(params));
+      continue;
+    }
+
+    // Other base::Value types are not supported.
+    LOG(ERROR) << "A DCS feature mapped to an unsupported value. key: "
+               << it.key() << " type: " << it.value().type();
+  }
+
+  return persistent_dict;
+}
+
+const std::unordered_set<int32_t>& GetDCSExperimentIds() {
+  DCHECK(g_experiment_ids_initialized);
+  return g_experiment_ids.Get();
+}
+
+void ResetCastFeaturesForTesting() {
+  g_experiment_ids_initialized = false;
+  base::FeatureList::ClearInstanceForTesting();
+}
+
+}  // namespace chromecast
diff --git a/chromecast/base/cast_features.h b/chromecast/base/cast_features.h
new file mode 100644
index 0000000..928399a
--- /dev/null
+++ b/chromecast/base/cast_features.h
@@ -0,0 +1,52 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_BASE_CAST_FEATURES_H_
+#define CHROMECAST_BASE_CAST_FEATURES_H_
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <unordered_set>
+
+#include "base/feature_list.h"
+#include "base/macros.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+}
+
+namespace chromecast {
+
+// Initialize the global base::FeatureList instance, taking into account
+// overrides from DCS and the command line. |dcs_features| and
+// |dcs_experiment_ids| are read from the PrefService in the browser process.
+// |cmd_line_enable_features| and |cmd_line_disable_features| should be passed
+// to this function, unmodified from the command line.
+//
+// This function should be called before the browser's main loop. After this is
+// called, the other functions in this file may be called on any thread.
+void InitializeFeatureList(const base::DictionaryValue& dcs_features,
+                           const base::ListValue& dcs_experiment_ids,
+                           const std::string& cmd_line_enable_features,
+                           const std::string& cmd_line_disable_features);
+
+// Given a dictionary of features, create a copy that is ready to be persisted
+// to disk. Encodes all values as strings,  which is how the FieldTrial
+// classes expect the param data.
+base::DictionaryValue GetOverriddenFeaturesForStorage(
+    const base::DictionaryValue& features);
+
+// Query the set of experiment ids set for this run. Intended only for metrics
+// reporting. Must be called after InitializeFeatureList(). May be called on any
+// thread.
+const std::unordered_set<int32_t>& GetDCSExperimentIds();
+
+// Reset static state to ensure clean unittests. For tests only.
+void ResetCastFeaturesForTesting();
+
+}  // namespace chromecast
+
+#endif  // CHROMECAST_BASE_CAST_FEATURES_H_
\ No newline at end of file
diff --git a/chromecast/base/cast_features_unittest.cc b/chromecast/base/cast_features_unittest.cc
new file mode 100644
index 0000000..b64efcd
--- /dev/null
+++ b/chromecast/base/cast_features_unittest.cc
@@ -0,0 +1,285 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/base/cast_features.h"
+
+#include "base/feature_list.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/values.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromecast {
+namespace {
+
+const char kTestBooleanFeatureName[] = "test_boolean_feature";
+const char kTestBooleanFeatureName2[] = "test_boolean_feature_2";
+const char kTestBooleanFeatureName3[] = "test_boolean_feature_3";
+const char kTestBooleanFeatureName4[] = "test_boolean_feature_4";
+
+const char kTestParamsFeatureName[] = "test_params_feature";
+
+}  // namespace
+
+class CastFeaturesTest : public testing::Test {
+ public:
+  CastFeaturesTest() : field_trial_list_(nullptr) {}
+  ~CastFeaturesTest() override {}
+
+  // testing::Test implementation:
+  void SetUp() override { ResetCastFeaturesForTesting(); }
+
+ private:
+  // A field trial list must be created before attempting to create FieldTrials.
+  // In production, this instance lives in CastBrowserMainParts.
+  base::FieldTrialList field_trial_list_;
+
+  DISALLOW_COPY_AND_ASSIGN(CastFeaturesTest);
+};
+
+TEST_F(CastFeaturesTest, EnableDisableMultipleBooleanFeatures) {
+  // Declare several boolean features.
+  base::Feature bool_feature(kTestBooleanFeatureName,
+                             base::FEATURE_DISABLED_BY_DEFAULT);
+  base::Feature bool_feature_2(kTestBooleanFeatureName2,
+                               base::FEATURE_ENABLED_BY_DEFAULT);
+  base::Feature bool_feature_3(kTestBooleanFeatureName3,
+                               base::FEATURE_DISABLED_BY_DEFAULT);
+  base::Feature bool_feature_4(kTestBooleanFeatureName4,
+                               base::FEATURE_ENABLED_BY_DEFAULT);
+
+  // Override those features with DCS configs.
+  auto experiments = base::MakeUnique<base::ListValue>();
+  auto features = base::MakeUnique<base::DictionaryValue>();
+  features->SetBoolean(kTestBooleanFeatureName, false);
+  features->SetBoolean(kTestBooleanFeatureName2, false);
+  features->SetBoolean(kTestBooleanFeatureName3, true);
+  features->SetBoolean(kTestBooleanFeatureName4, true);
+
+  InitializeFeatureList(*features, *experiments, "", "");
+
+  // Test that features are properly enabled (they should match the
+  // DCS config).
+  ASSERT_FALSE(base::FeatureList::IsEnabled(bool_feature));
+  ASSERT_FALSE(base::FeatureList::IsEnabled(bool_feature_2));
+  ASSERT_TRUE(base::FeatureList::IsEnabled(bool_feature_3));
+  ASSERT_TRUE(base::FeatureList::IsEnabled(bool_feature_4));
+}
+
+TEST_F(CastFeaturesTest, EnableSingleFeatureWithParams) {
+  // Define a feature with params.
+  base::Feature test_feature(kTestParamsFeatureName,
+                             base::FEATURE_DISABLED_BY_DEFAULT);
+
+  // Pass params via DCS.
+  auto experiments = base::MakeUnique<base::ListValue>();
+  auto features = base::MakeUnique<base::DictionaryValue>();
+  auto params = base::MakeUnique<base::DictionaryValue>();
+  params->SetString("foo_key", "foo");
+  params->SetString("bar_key", "bar");
+  params->SetString("doub_key", "3.14159");
+  params->SetString("long_doub_key", "1.23459999999999999");
+  params->SetString("int_key", "4242");
+  params->SetString("bool_key", "true");
+  features->Set(kTestParamsFeatureName, std::move(params));
+
+  InitializeFeatureList(*features, *experiments, "", "");
+
+  // Test that this feature is enabled, and params are correct.
+  ASSERT_TRUE(base::FeatureList::IsEnabled(test_feature));
+  ASSERT_EQ("foo",
+            base::GetFieldTrialParamValueByFeature(test_feature, "foo_key"));
+  ASSERT_EQ("bar",
+            base::GetFieldTrialParamValueByFeature(test_feature, "bar_key"));
+  ASSERT_EQ(3.14159, base::GetFieldTrialParamByFeatureAsDouble(
+                         test_feature, "doub_key", 0.000));
+  ASSERT_EQ(1.23459999999999999, base::GetFieldTrialParamByFeatureAsDouble(
+                                     test_feature, "long_doub_key", 0.000));
+  ASSERT_EQ(4242, base::GetFieldTrialParamByFeatureAsInt(test_feature,
+                                                         "int_key", -1));
+  ASSERT_EQ(true, base::GetFieldTrialParamByFeatureAsBool(test_feature,
+                                                          "bool_key", false));
+}
+
+TEST_F(CastFeaturesTest, CommandLineOverridesDcsAndDefault) {
+  // Declare several boolean features.
+  base::Feature bool_feature(kTestBooleanFeatureName,
+                             base::FEATURE_DISABLED_BY_DEFAULT);
+  base::Feature bool_feature_2(kTestBooleanFeatureName2,
+                               base::FEATURE_ENABLED_BY_DEFAULT);
+  base::Feature bool_feature_3(kTestBooleanFeatureName3,
+                               base::FEATURE_DISABLED_BY_DEFAULT);
+  base::Feature bool_feature_4(kTestBooleanFeatureName4,
+                               base::FEATURE_ENABLED_BY_DEFAULT);
+
+  // Override those features with DCS configs.
+  auto experiments = base::MakeUnique<base::ListValue>();
+  auto features = base::MakeUnique<base::DictionaryValue>();
+  features->SetBoolean(kTestBooleanFeatureName, false);
+  features->SetBoolean(kTestBooleanFeatureName2, false);
+  features->SetBoolean(kTestBooleanFeatureName3, true);
+  features->SetBoolean(kTestBooleanFeatureName4, true);
+
+  // Also override a param feature with DCS config.
+  base::Feature params_feature(kTestParamsFeatureName,
+                               base::FEATURE_ENABLED_BY_DEFAULT);
+  auto params = base::MakeUnique<base::DictionaryValue>();
+  params->SetString("foo_key", "foo");
+  features->Set(kTestParamsFeatureName, std::move(params));
+
+  // Now override with command line flags. Command line flags should have the
+  // final say.
+  std::string enabled_features = std::string(kTestBooleanFeatureName)
+                                     .append(",")
+                                     .append(kTestBooleanFeatureName2);
+
+  std::string disabled_features = std::string(kTestBooleanFeatureName4)
+                                      .append(",")
+                                      .append(kTestParamsFeatureName);
+
+  InitializeFeatureList(*features, *experiments, enabled_features,
+                        disabled_features);
+
+  // Test that features are properly enabled (they should match the
+  // DCS config).
+  ASSERT_TRUE(base::FeatureList::IsEnabled(bool_feature));
+  ASSERT_TRUE(base::FeatureList::IsEnabled(bool_feature_2));
+  ASSERT_TRUE(base::FeatureList::IsEnabled(bool_feature_3));
+  ASSERT_FALSE(base::FeatureList::IsEnabled(bool_feature_4));
+
+  // Test that the params feature is disabled, and params are not set.
+  ASSERT_FALSE(base::FeatureList::IsEnabled(params_feature));
+  ASSERT_EQ("",
+            base::GetFieldTrialParamValueByFeature(params_feature, "foo_key"));
+}
+
+TEST_F(CastFeaturesTest, SetEmptyExperiments) {
+  // Override those features with DCS configs.
+  auto experiments = base::MakeUnique<base::ListValue>();
+  auto features = base::MakeUnique<base::DictionaryValue>();
+
+  InitializeFeatureList(*features, *experiments, "", "");
+  ASSERT_EQ(0u, GetDCSExperimentIds().size());
+}
+
+TEST_F(CastFeaturesTest, SetGoodExperiments) {
+  // Override those features with DCS configs.
+  auto experiments = base::MakeUnique<base::ListValue>();
+  auto features = base::MakeUnique<base::DictionaryValue>();
+
+  int32_t ids[] = {12345678, 123, 0, -1};
+  std::unordered_set<int32_t> expected;
+  for (int32_t id : ids) {
+    experiments->AppendInteger(id);
+    expected.insert(id);
+  }
+
+  InitializeFeatureList(*features, *experiments, "", "");
+  ASSERT_EQ(expected, GetDCSExperimentIds());
+}
+
+TEST_F(CastFeaturesTest, SetSomeGoodExperiments) {
+  // Override those features with DCS configs.
+  auto experiments = base::MakeUnique<base::ListValue>();
+  auto features = base::MakeUnique<base::DictionaryValue>();
+  experiments->AppendInteger(1234);
+  experiments->AppendString("foobar");
+  experiments->AppendBoolean(true);
+  experiments->AppendInteger(1);
+  experiments->AppendDouble(1.23456);
+
+  std::unordered_set<int32_t> expected;
+  expected.insert(1234);
+  expected.insert(1);
+
+  InitializeFeatureList(*features, *experiments, "", "");
+  ASSERT_EQ(expected, GetDCSExperimentIds());
+}
+
+TEST_F(CastFeaturesTest, SetAllBadExperiments) {
+  // Override those features with DCS configs.
+  auto experiments = base::MakeUnique<base::ListValue>();
+  auto features = base::MakeUnique<base::DictionaryValue>();
+  experiments->AppendString("foobar");
+  experiments->AppendBoolean(true);
+  experiments->AppendDouble(1.23456);
+
+  std::unordered_set<int32_t> expected;
+
+  InitializeFeatureList(*features, *experiments, "", "");
+  ASSERT_EQ(expected, GetDCSExperimentIds());
+}
+
+TEST_F(CastFeaturesTest, GetOverriddenFeaturesForStorage) {
+  auto features = base::MakeUnique<base::DictionaryValue>();
+  features->SetBoolean("bool_key", false);
+  features->SetBoolean("bool_key_2", true);
+
+  auto params = base::MakeUnique<base::DictionaryValue>();
+  params->SetString("foo_key", "foo");
+  params->SetString("bar_key", "bar");
+  params->SetDouble("doub_key", 3.14159);
+  params->SetDouble("long_doub_key", 1.234599999999999);
+  params->SetInteger("int_key", 4242);
+  params->SetInteger("negint_key", -273);
+  params->SetBoolean("bool_key", true);
+  features->Set("params_key", std::move(params));
+
+  auto dict = GetOverriddenFeaturesForStorage(*features);
+  bool bval;
+  ASSERT_EQ(3u, dict.size());
+  ASSERT_TRUE(dict.GetBoolean("bool_key", &bval));
+  ASSERT_EQ(false, bval);
+  ASSERT_TRUE(dict.GetBoolean("bool_key_2", &bval));
+  ASSERT_EQ(true, bval);
+
+  const base::DictionaryValue* dval;
+  std::string sval;
+  ASSERT_TRUE(dict.GetDictionary("params_key", &dval));
+  ASSERT_EQ(7u, dval->size());
+  ASSERT_TRUE(dval->GetString("foo_key", &sval));
+  ASSERT_EQ("foo", sval);
+  ASSERT_TRUE(dval->GetString("bar_key", &sval));
+  ASSERT_EQ("bar", sval);
+  ASSERT_TRUE(dval->GetString("doub_key", &sval));
+  ASSERT_EQ("3.14159", sval);
+  ASSERT_TRUE(dval->GetString("long_doub_key", &sval));
+  ASSERT_EQ("1.234599999999999", sval);
+  ASSERT_TRUE(dval->GetString("int_key", &sval));
+  ASSERT_EQ("4242", sval);
+  ASSERT_TRUE(dval->GetString("negint_key", &sval));
+  ASSERT_EQ("-273", sval);
+  ASSERT_TRUE(dval->GetString("bool_key", &sval));
+  ASSERT_EQ("true", sval);
+}
+
+TEST_F(CastFeaturesTest, GetOverriddenFeaturesForStorage_BadParams) {
+  auto features = base::MakeUnique<base::DictionaryValue>();
+  features->SetBoolean("bool_key", false);
+  features->SetString("str_key", "foobar");
+  features->SetInteger("int_key", 12345);
+  features->SetDouble("doub_key", 4.5678);
+
+  auto params = base::MakeUnique<base::DictionaryValue>();
+  params->SetString("foo_key", "foo");
+  features->Set("params_key", std::move(params));
+
+  auto dict = GetOverriddenFeaturesForStorage(*features);
+  bool bval;
+  ASSERT_EQ(2u, dict.size());
+  ASSERT_TRUE(dict.GetBoolean("bool_key", &bval));
+  ASSERT_EQ(false, bval);
+
+  const base::DictionaryValue* dval;
+  std::string sval;
+  ASSERT_TRUE(dict.GetDictionary("params_key", &dval));
+  ASSERT_EQ(1u, dval->size());
+  ASSERT_TRUE(dval->GetString("foo_key", &sval));
+  ASSERT_EQ("foo", sval);
+}
+
+}  // namespace chromecast
\ No newline at end of file
diff --git a/chromecast/base/pref_names.cc b/chromecast/base/pref_names.cc
index 237f1336..9ef0f061 100644
--- a/chromecast/base/pref_names.cc
+++ b/chromecast/base/pref_names.cc
@@ -7,6 +7,9 @@
 namespace chromecast {
 namespace prefs {
 
+// List of experiments enabled by DCS. For metrics purposes only.
+const char kActiveDCSExperiments[] = "experiments.ids";
+
 // Boolean which specifies if remote debugging is enabled
 const char kEnableRemoteDebugging[] = "enable_remote_debugging";
 
@@ -14,6 +17,9 @@
 // due to bug b/9487011.
 const char kMetricsIsNewClientID[] = "user_experience_metrics.is_new_client_id";
 
+// Dictionary of remotely-enabled features from the latest DCS config fetch.
+const char kLatestDCSFeatures[] = "experiments.features";
+
 // Whether or not to report metrics and crashes.
 const char kOptInStats[] = "opt-in.stats";
 
diff --git a/chromecast/base/pref_names.h b/chromecast/base/pref_names.h
index 7454885d..9003ee2 100644
--- a/chromecast/base/pref_names.h
+++ b/chromecast/base/pref_names.h
@@ -8,7 +8,9 @@
 namespace chromecast {
 namespace prefs {
 
+extern const char kActiveDCSExperiments[];
 extern const char kEnableRemoteDebugging[];
+extern const char kLatestDCSFeatures[];
 extern const char kMetricsIsNewClientID[];
 extern const char kOptInStats[];
 extern const char kStabilityChildProcessCrashCount[];
diff --git a/chromecast/browser/BUILD.gn b/chromecast/browser/BUILD.gn
index 6b5f660..5ae5acec 100644
--- a/chromecast/browser/BUILD.gn
+++ b/chromecast/browser/BUILD.gn
@@ -246,6 +246,7 @@
   sources = [
     "cast_media_blocker_browsertest.cc",
     "renderer_prelauncher_test.cc",
+    "test/cast_features_browsertest.cc",
     "test/cast_navigation_browsertest.cc",
   ]
 
@@ -254,8 +255,10 @@
   deps = [
     ":test_support",
     "//chromecast:chromecast_features",
+    "//chromecast/base",
     "//chromecast/base:chromecast_switches",
     "//chromecast/base/metrics",
+    "//components/prefs",
     "//media/base:test_support",
   ]
 }
diff --git a/chromecast/browser/cast_browser_main_parts.cc b/chromecast/browser/cast_browser_main_parts.cc
index ccb0fa8..75c39e05 100644
--- a/chromecast/browser/cast_browser_main_parts.cc
+++ b/chromecast/browser/cast_browser_main_parts.cc
@@ -22,11 +22,13 @@
 #include "build/build_config.h"
 #include "cc/base/switches.h"
 #include "chromecast/base/cast_constants.h"
+#include "chromecast/base/cast_features.h"
 #include "chromecast/base/cast_paths.h"
 #include "chromecast/base/cast_sys_info_util.h"
 #include "chromecast/base/chromecast_switches.h"
 #include "chromecast/base/metrics/cast_metrics_helper.h"
 #include "chromecast/base/metrics/grouped_histogram.h"
+#include "chromecast/base/pref_names.h"
 #include "chromecast/base/version.h"
 #include "chromecast/browser/cast_browser_context.h"
 #include "chromecast/browser/cast_browser_process.h"
@@ -277,6 +279,7 @@
     URLRequestContextFactory* url_request_context_factory)
     : BrowserMainParts(),
       cast_browser_process_(new CastBrowserProcess()),
+      field_trial_list_(nullptr),
       parameters_(parameters),
       url_request_context_factory_(url_request_context_factory),
       net_log_(new CastNetLog()),
@@ -412,6 +415,26 @@
   if (!base::CreateDirectory(home_dir))
     return 1;
 
+  scoped_refptr<PrefRegistrySimple> pref_registry(new PrefRegistrySimple());
+  metrics::RegisterPrefs(pref_registry.get());
+  PrefProxyConfigTrackerImpl::RegisterPrefs(pref_registry.get());
+  cast_browser_process_->SetPrefService(
+      PrefServiceHelper::CreatePrefService(pref_registry.get()));
+
+  // As soon as the PrefService is set, initialize the base::FeatureList, so
+  // objects initialized after this point can use features from
+  // base::FeatureList.
+  const auto* features_dict =
+      cast_browser_process_->pref_service()->GetDictionary(
+          prefs::kLatestDCSFeatures);
+  const auto* experiment_ids = cast_browser_process_->pref_service()->GetList(
+      prefs::kActiveDCSExperiments);
+  auto* command_line = base::CommandLine::ForCurrentProcess();
+  InitializeFeatureList(
+      *features_dict, *experiment_ids,
+      command_line->GetSwitchValueASCII(switches::kEnableFeatures),
+      command_line->GetSwitchValueASCII(switches::kDisableFeatures));
+
   // Hook for internal code
   cast_browser_process_->browser_client()->PreCreateThreads();
 
@@ -436,11 +459,6 @@
 }
 
 void CastBrowserMainParts::PreMainMessageLoopRun() {
-  scoped_refptr<PrefRegistrySimple> pref_registry(new PrefRegistrySimple());
-  metrics::RegisterPrefs(pref_registry.get());
-  PrefProxyConfigTrackerImpl::RegisterPrefs(pref_registry.get());
-  cast_browser_process_->SetPrefService(
-      PrefServiceHelper::CreatePrefService(pref_registry.get()));
 
 #if !defined(OS_ANDROID)
   memory_pressure_monitor_.reset(new CastMemoryPressureMonitor());
diff --git a/chromecast/browser/cast_browser_main_parts.h b/chromecast/browser/cast_browser_main_parts.h
index 822e930..d4d19e8 100644
--- a/chromecast/browser/cast_browser_main_parts.h
+++ b/chromecast/browser/cast_browser_main_parts.h
@@ -9,6 +9,7 @@
 
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/metrics/field_trial.h"
 #include "build/buildflag.h"
 #include "chromecast/chromecast_features.h"
 #include "content/public/browser/browser_main_parts.h"
@@ -65,6 +66,7 @@
 
  private:
   std::unique_ptr<CastBrowserProcess> cast_browser_process_;
+  base::FieldTrialList field_trial_list_;
   const content::MainFunctionParams parameters_;  // For running browser tests.
   URLRequestContextFactory* const url_request_context_factory_;
   std::unique_ptr<net::NetLog> net_log_;
diff --git a/chromecast/browser/pref_service_helper.cc b/chromecast/browser/pref_service_helper.cc
index c1b6e5c..d6f825dd9 100644
--- a/chromecast/browser/pref_service_helper.cc
+++ b/chromecast/browser/pref_service_helper.cc
@@ -53,6 +53,8 @@
   //     opts out, nothing further will be sent (honoring the user's setting).
   //  2) Dogfood users (see dogfood agreement).
   registry->RegisterBooleanPref(prefs::kOptInStats, true);
+  registry->RegisterListPref(prefs::kActiveDCSExperiments);
+  registry->RegisterDictionaryPref(prefs::kLatestDCSFeatures);
 
   RegisterPlatformPrefs(registry);
 
diff --git a/chromecast/browser/test/cast_features_browsertest.cc b/chromecast/browser/test/cast_features_browsertest.cc
new file mode 100644
index 0000000..6093b96
--- /dev/null
+++ b/chromecast/browser/test/cast_features_browsertest.cc
@@ -0,0 +1,174 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/base/cast_features.h"
+
+#include "base/feature_list.h"
+#include "base/macros.h"
+#include "base/metrics/field_trial_params.h"
+#include "chromecast/base/pref_names.h"
+#include "chromecast/browser/cast_browser_process.h"
+#include "chromecast/browser/test/cast_browser_test.h"
+#include "components/prefs/pref_service.h"
+
+namespace chromecast {
+namespace shell {
+namespace {
+
+const base::Feature kEnableFoo("enable_foo", base::FEATURE_DISABLED_BY_DEFAULT);
+const base::Feature kEnableBar("enable_bar", base::FEATURE_ENABLED_BY_DEFAULT);
+const base::Feature kEnableBaz("enable_baz", base::FEATURE_DISABLED_BY_DEFAULT);
+const base::Feature kEnableBat("enable_bat", base::FEATURE_ENABLED_BY_DEFAULT);
+
+const base::Feature kEnableParams("enable_params",
+                                  base::FEATURE_DISABLED_BY_DEFAULT);
+
+}  // namespace
+
+class CastFeaturesBrowserTest : public CastBrowserTest {
+ public:
+  CastFeaturesBrowserTest() {}
+  ~CastFeaturesBrowserTest() override {}
+
+  // Write |dcs_features| to the pref store. This method is intended to be
+  // overridden in internal test to utilize the real production codepath for
+  // setting features from the server.
+  virtual void SetFeatures(const base::DictionaryValue& dcs_features) {
+    auto pref_features = GetOverriddenFeaturesForStorage(dcs_features);
+    CastBrowserProcess::GetInstance()->pref_service()->Set(
+        prefs::kLatestDCSFeatures, pref_features);
+  }
+
+  static void ClearFeaturesInPrefs() {
+    DCHECK(CastBrowserProcess::GetInstance());
+    DCHECK(CastBrowserProcess::GetInstance()->pref_service());
+    CastBrowserProcess::GetInstance()->pref_service()->Set(
+        prefs::kLatestDCSFeatures, base::DictionaryValue());
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CastFeaturesBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_F(CastFeaturesBrowserTest, EmptyTest) {
+  // Default values should be returned.
+  ASSERT_FALSE(base::FeatureList::IsEnabled(kEnableFoo));
+  ASSERT_TRUE(base::FeatureList::IsEnabled(kEnableBar));
+}
+
+// Test that set features activate on the next boot. Part 1 of 2.
+IN_PROC_BROWSER_TEST_F(CastFeaturesBrowserTest,
+                       PRE_TestFeaturesActivateOnBoot) {
+  // Default values should be returned.
+  ASSERT_FALSE(base::FeatureList::IsEnabled(kEnableFoo));
+  ASSERT_TRUE(base::FeatureList::IsEnabled(kEnableBar));
+  ASSERT_FALSE(base::FeatureList::IsEnabled(kEnableBaz));
+  ASSERT_TRUE(base::FeatureList::IsEnabled(kEnableBat));
+
+  // Set the features to be used on next boot.
+  base::DictionaryValue features;
+  features.SetBoolean("enable_foo", true);
+  features.SetBoolean("enable_bat", false);
+  SetFeatures(features);
+
+  // Default values should still be returned until next boot.
+  EXPECT_FALSE(base::FeatureList::IsEnabled(kEnableFoo));
+  EXPECT_TRUE(base::FeatureList::IsEnabled(kEnableBar));
+  EXPECT_FALSE(base::FeatureList::IsEnabled(kEnableBaz));
+  EXPECT_TRUE(base::FeatureList::IsEnabled(kEnableBat));
+}
+
+// Test that features activate on the next boot. Part 2 of 2.
+IN_PROC_BROWSER_TEST_F(CastFeaturesBrowserTest, TestFeaturesActivateOnBoot) {
+  // Overriden values set in test case above should be set.
+  ASSERT_TRUE(base::FeatureList::IsEnabled(kEnableFoo));
+  ASSERT_TRUE(base::FeatureList::IsEnabled(kEnableBar));
+  ASSERT_FALSE(base::FeatureList::IsEnabled(kEnableBaz));
+  ASSERT_FALSE(base::FeatureList::IsEnabled(kEnableBat));
+
+  // Clear the features for other tests.
+  ClearFeaturesInPrefs();
+}
+
+// Test that features with params activate on boot. Part 1 of 2.
+IN_PROC_BROWSER_TEST_F(CastFeaturesBrowserTest, PRE_TestParamsActivateOnBoot) {
+  // Default value should be returned.
+  ASSERT_FALSE(base::FeatureList::IsEnabled(kEnableParams));
+
+  // Set the features to be used on next boot.
+  base::DictionaryValue features;
+  auto params = base::MakeUnique<base::DictionaryValue>();
+  params->SetBoolean("bool_param", true);
+  params->SetBoolean("bool_param_2", false);
+  params->SetString("str_param", "foo");
+  params->SetDouble("doub_param", 3.14159);
+  params->SetInteger("int_param", 76543);
+  features.Set("enable_params", std::move(params));
+  SetFeatures(features);
+
+  // Default value should still be returned until next boot.
+  EXPECT_FALSE(base::FeatureList::IsEnabled(kEnableParams));
+}
+
+// Test that features with params activate on boot. Part 2 of 2.
+IN_PROC_BROWSER_TEST_F(CastFeaturesBrowserTest, TestParamsActivateOnBoot) {
+  // Check that the feature is now enabled.
+  ASSERT_TRUE(base::FeatureList::IsEnabled(kEnableParams));
+
+  // Check that the params are populated and correct.
+  ASSERT_TRUE(base::GetFieldTrialParamByFeatureAsBool(
+      kEnableParams, "bool_param", false /* default_value */));
+  ASSERT_FALSE(base::GetFieldTrialParamByFeatureAsBool(
+      kEnableParams, "bool_param_2", true /* default_value */));
+  ASSERT_EQ("foo",
+            base::GetFieldTrialParamValueByFeature(kEnableParams, "str_param"));
+  ASSERT_EQ(76543, base::GetFieldTrialParamByFeatureAsInt(
+                       kEnableParams, "int_param", 0 /* default_value */));
+  ASSERT_EQ(3.14159, base::GetFieldTrialParamByFeatureAsDouble(
+                         kEnableParams, "doub_param", 0.0 /* default_value */));
+
+  // Check that no extra parameters are set.
+  std::map<std::string, std::string> params_out;
+  ASSERT_TRUE(base::GetFieldTrialParamsByFeature(kEnableParams, &params_out));
+  ASSERT_EQ(5u, params_out.size());
+
+  // Clear the features for other tests.
+  ClearFeaturesInPrefs();
+}
+
+// Test that only well-formed features are persisted to disk. Part 1 of 2.
+IN_PROC_BROWSER_TEST_F(CastFeaturesBrowserTest,
+                       PRE_TestOnlyWellFormedFeaturesPersisted) {
+  // Default values should be returned.
+  ASSERT_FALSE(base::FeatureList::IsEnabled(kEnableFoo));
+  ASSERT_TRUE(base::FeatureList::IsEnabled(kEnableBar));
+  ASSERT_FALSE(base::FeatureList::IsEnabled(kEnableBaz));
+  ASSERT_TRUE(base::FeatureList::IsEnabled(kEnableBat));
+
+  // Set both good parameters...
+  base::DictionaryValue features;
+  features.SetBoolean("enable_foo", true);
+  features.SetBoolean("enable_bat", false);
+
+  // ... and bad parameters.
+  features.SetString("enable_bar", "False");
+  features.Set("enable_baz", base::MakeUnique<base::ListValue>());
+
+  SetFeatures(features);
+}
+
+// Test that only well-formed features are persisted to disk. Part 2 of 2.
+IN_PROC_BROWSER_TEST_F(CastFeaturesBrowserTest,
+                       TestOnlyWellFormedFeaturesPersisted) {
+  // Only the well-formed parameters should be overriden.
+  ASSERT_TRUE(base::FeatureList::IsEnabled(kEnableFoo));
+  ASSERT_FALSE(base::FeatureList::IsEnabled(kEnableBat));
+
+  // The other should take default values.
+  ASSERT_TRUE(base::FeatureList::IsEnabled(kEnableBar));
+  ASSERT_FALSE(base::FeatureList::IsEnabled(kEnableBaz));
+}
+
+}  // namespace shell
+}  // namespace chromecast
diff --git a/chromecast/graphics/BUILD.gn b/chromecast/graphics/BUILD.gn
index 1c4c0b8..ad8699e 100644
--- a/chromecast/graphics/BUILD.gn
+++ b/chromecast/graphics/BUILD.gn
@@ -16,7 +16,7 @@
     "//ui/gfx",
   ]
 
-  if (use_aura && !is_cast_audio_only) {
+  if (use_aura) {
     sources += [
       "cast_focus_client_aura.cc",
       "cast_focus_client_aura.h",
diff --git a/components/browser_sync/profile_sync_service.cc b/components/browser_sync/profile_sync_service.cc
index 5eb7949..25067a0 100644
--- a/components/browser_sync/profile_sync_service.cc
+++ b/components/browser_sync/profile_sync_service.cc
@@ -1783,41 +1783,33 @@
     return std::move(result);
   }
 
-  DataTypeStatusTable::TypeErrorMap error_map =
-      data_type_status_table_.GetAllErrors();
-  ModelTypeSet active_types;
-  ModelTypeSet passive_types;
-  ModelSafeRoutingInfo routing_info;
-  engine_->GetModelSafeRoutingInfo(&routing_info);
-  for (ModelSafeRoutingInfo::const_iterator it = routing_info.begin();
-       it != routing_info.end(); ++it) {
-    if (it->second == syncer::GROUP_PASSIVE) {
-      passive_types.Put(it->first);
-    } else {
-      active_types.Put(it->first);
-    }
-  }
-
   SyncEngine::Status detailed_status = engine_->GetDetailedStatus();
-  ModelTypeSet& throttled_types(detailed_status.throttled_types);
-  ModelTypeSet& backed_off_types(detailed_status.backed_off_types);
-  ModelTypeSet registered = GetRegisteredDataTypes();
+  const ModelTypeSet& throttled_types(detailed_status.throttled_types);
+  const ModelTypeSet& backed_off_types(detailed_status.backed_off_types);
+
   std::unique_ptr<base::DictionaryValue> type_status_header(
       new base::DictionaryValue());
-
-  type_status_header->SetString("name", "Model Type");
   type_status_header->SetString("status", "header");
-  type_status_header->SetString("value", "Group Type");
+  type_status_header->SetString("name", "Model Type");
   type_status_header->SetString("num_entries", "Total Entries");
   type_status_header->SetString("num_live", "Live Entries");
+  type_status_header->SetString("message", "Message");
+  type_status_header->SetString("state", "State");
+  type_status_header->SetString("group_type", "Group Type");
   result->Append(std::move(type_status_header));
 
-  std::unique_ptr<base::DictionaryValue> type_status;
+  const DataTypeStatusTable::TypeErrorMap error_map =
+      data_type_status_table_.GetAllErrors();
+  ModelSafeRoutingInfo routing_info;
+  engine_->GetModelSafeRoutingInfo(&routing_info);
+  const ModelTypeSet registered = GetRegisteredDataTypes();
   for (ModelTypeSet::Iterator it = registered.First(); it.Good(); it.Inc()) {
     ModelType type = it.Get();
 
-    type_status = base::MakeUnique<base::DictionaryValue>();
+    auto type_status = base::MakeUnique<base::DictionaryValue>();
     type_status->SetString("name", ModelTypeToString(type));
+    type_status->SetString("group_type",
+                           ModelSafeGroupToString(routing_info[type]));
 
     if (error_map.find(type) != error_map.end()) {
       const syncer::SyncError& error = error_map.find(type)->second;
@@ -1826,44 +1818,26 @@
         case syncer::SyncError::SYNC_ERROR_SEVERITY_ERROR:
           type_status->SetString("status", "error");
           type_status->SetString(
-              "value", "Error: " + error.location().ToString() + ", " +
-                           error.GetMessagePrefix() + error.message());
+              "message", "Error: " + error.location().ToString() + ", " +
+                             error.GetMessagePrefix() + error.message());
           break;
         case syncer::SyncError::SYNC_ERROR_SEVERITY_INFO:
           type_status->SetString("status", "disabled");
-          type_status->SetString("value", error.message());
-          break;
-        default:
-          NOTREACHED() << "Unexpected error severity.";
+          type_status->SetString("message", error.message());
           break;
       }
-    } else if (syncer::IsProxyType(type) && passive_types.Has(type)) {
-      // Show a proxy type in "ok" state unless it is disabled by user.
-      DCHECK(!throttled_types.Has(type));
-      type_status->SetString("status", "ok");
-      type_status->SetString("value", "Passive");
-    } else if (throttled_types.Has(type) && passive_types.Has(type)) {
-      type_status->SetString("status", "warning");
-      type_status->SetString("value", "Passive, Throttled");
-    } else if (backed_off_types.Has(type) && passive_types.Has(type)) {
-      type_status->SetString("status", "warning");
-      type_status->SetString("value", "Passive, Backed off");
-    } else if (passive_types.Has(type)) {
-      type_status->SetString("status", "warning");
-      type_status->SetString("value", "Passive");
     } else if (throttled_types.Has(type)) {
       type_status->SetString("status", "warning");
-      type_status->SetString("value", "Throttled");
+      type_status->SetString("message", " Throttled");
     } else if (backed_off_types.Has(type)) {
       type_status->SetString("status", "warning");
-      type_status->SetString("value", "Backed off");
-    } else if (active_types.Has(type)) {
+      type_status->SetString("message", "Backed off");
+    } else if (routing_info.find(type) != routing_info.end()) {
       type_status->SetString("status", "ok");
-      type_status->SetString(
-          "value", "Active: " + ModelSafeGroupToString(routing_info[type]));
+      type_status->SetString("message", "");
     } else {
       type_status->SetString("status", "warning");
-      type_status->SetString("value", "Disabled by User");
+      type_status->SetString("message", "Disabled by User");
     }
 
     const auto& dtc_iter = data_type_controllers_.find(type);
@@ -1874,6 +1848,8 @@
       dtc_iter->second->GetStatusCounters(BindToCurrentThread(
           base::Bind(&ProfileSyncService::OnDatatypeStatusCounterUpdated,
                      base::Unretained(this))));
+      type_status->SetString("state", DataTypeController::StateToString(
+                                          dtc_iter->second->state()));
     }
 
     result->Append(std::move(type_status));
diff --git a/components/exo/shell_surface.cc b/components/exo/shell_surface.cc
index 6687ddd4..602b5a4 100644
--- a/components/exo/shell_surface.cc
+++ b/components/exo/shell_surface.cc
@@ -501,6 +501,17 @@
   ash::wm::SetAutoHideShelf(widget_->GetNativeWindow(), autohide);
 }
 
+void ShellSurface::SetAlwaysOnTop(bool always_on_top) {
+  TRACE_EVENT1("exo", "ShellSurface::SetAlwaysOnTop", "always_on_top",
+               always_on_top);
+
+  if (!widget_)
+    CreateShellSurfaceWidget(ui::SHOW_STATE_NORMAL);
+
+  widget_->GetNativeWindow()->SetProperty(aura::client::kAlwaysOnTopKey,
+                                          always_on_top);
+}
+
 void ShellSurface::SetTitle(const base::string16& title) {
   TRACE_EVENT1("exo", "ShellSurface::SetTitle", "title",
                base::UTF16ToUTF8(title));
diff --git a/components/exo/shell_surface.h b/components/exo/shell_surface.h
index 1356c9a..c8087ad 100644
--- a/components/exo/shell_surface.h
+++ b/components/exo/shell_surface.h
@@ -138,6 +138,9 @@
   // Sets whether or not the shell surface should autohide the system UI.
   void SetSystemUiVisibility(bool autohide);
 
+  // Set whether the surface is always on top.
+  void SetAlwaysOnTop(bool always_on_top);
+
   // Set title for surface.
   void SetTitle(const base::string16& title);
 
diff --git a/components/exo/wayland/server.cc b/components/exo/wayland/server.cc
index ca0aba2..0d27ff60 100644
--- a/components/exo/wayland/server.cc
+++ b/components/exo/wayland/server.cc
@@ -2000,6 +2000,16 @@
       visibility != ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_VISIBLE);
 }
 
+void remote_surface_set_always_on_top(wl_client* client,
+                                      wl_resource* resource) {
+  GetUserDataAs<ShellSurface>(resource)->SetAlwaysOnTop(true);
+}
+
+void remote_surface_unset_always_on_top(wl_client* client,
+                                        wl_resource* resource) {
+  GetUserDataAs<ShellSurface>(resource)->SetAlwaysOnTop(false);
+}
+
 void remote_surface_ack_configure(wl_client* client,
                                   wl_resource* resource,
                                   uint32_t serial) {
@@ -2031,6 +2041,8 @@
     remote_surface_unset_system_modal,
     remote_surface_set_rectangular_surface_shadow,
     remote_surface_set_systemui_visibility,
+    remote_surface_set_always_on_top,
+    remote_surface_unset_always_on_top,
     remote_surface_ack_configure,
     remote_surface_move};
 
@@ -2077,7 +2089,7 @@
   }
 
   bool IsMultiDisplaySupported() const {
-    return wl_resource_get_version(remote_shell_resource_) >= 4;
+    return wl_resource_get_version(remote_shell_resource_) >= 5;
   }
 
   std::unique_ptr<ShellSurface> CreateShellSurface(Surface* surface,
@@ -2377,7 +2389,7 @@
     remote_shell_destroy, remote_shell_get_remote_surface,
     remote_shell_get_notification_surface};
 
-const uint32_t remote_shell_version = 4;
+const uint32_t remote_shell_version = 5;
 
 void bind_remote_shell(wl_client* client,
                        void* data,
diff --git a/components/renderer_context_menu/views/toolkit_delegate_views.cc b/components/renderer_context_menu/views/toolkit_delegate_views.cc
index 81dfe55f..1e5357c08 100644
--- a/components/renderer_context_menu/views/toolkit_delegate_views.cc
+++ b/components/renderer_context_menu/views/toolkit_delegate_views.cc
@@ -22,17 +22,16 @@
        type == ui::MENU_SOURCE_TOUCH_EDIT_MENU)
       ? views::MENU_ANCHOR_BOTTOMCENTER
       : views::MENU_ANCHOR_TOPLEFT;
-  ignore_result(menu_runner_->RunMenuAt(
-      parent, NULL, gfx::Rect(point, gfx::Size()), anchor_position, type));
+  menu_runner_->RunMenuAt(parent, NULL, gfx::Rect(point, gfx::Size()),
+                          anchor_position, type);
 }
 
 void ToolkitDelegateViews::Init(ui::SimpleMenuModel* menu_model) {
   menu_adapter_.reset(new views::MenuModelAdapter(menu_model));
   menu_view_ = menu_adapter_->CreateMenu();
-  menu_runner_.reset(
-      new views::MenuRunner(menu_view_, views::MenuRunner::HAS_MNEMONICS |
-                                            views::MenuRunner::CONTEXT_MENU |
-                                            views::MenuRunner::ASYNC));
+  menu_runner_.reset(new views::MenuRunner(
+      menu_view_,
+      views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU));
 }
 
 void ToolkitDelegateViews::Cancel() {
diff --git a/components/sync/driver/data_type_controller.cc b/components/sync/driver/data_type_controller.cc
index de3e7f6..79c78f7 100644
--- a/components/sync/driver/data_type_controller.cc
+++ b/components/sync/driver/data_type_controller.cc
@@ -13,14 +13,38 @@
 
 DataTypeController::~DataTypeController() {}
 
+// static
 bool DataTypeController::IsUnrecoverableResult(ConfigureResult result) {
   return (result == UNRECOVERABLE_ERROR);
 }
 
+// static
 bool DataTypeController::IsSuccessfulResult(ConfigureResult result) {
   return (result == OK || result == OK_FIRST_RUN);
 }
 
+// static
+std::string DataTypeController::StateToString(State state) {
+  switch (state) {
+    case NOT_RUNNING:
+      return "Not Running";
+    case MODEL_STARTING:
+      return "Model Starting";
+    case MODEL_LOADED:
+      return "Model Loaded";
+    case ASSOCIATING:
+      return "Associating";
+    case RUNNING:
+      return "Running";
+    case STOPPING:
+      return "Stopping";
+    case DISABLED:
+      return "Disabled";
+  }
+  NOTREACHED();
+  return "Invalid";
+}
+
 bool DataTypeController::ReadyForStart() const {
   return true;
 }
diff --git a/components/sync/driver/data_type_controller.h b/components/sync/driver/data_type_controller.h
index ed0b6f7..7f9aecc 100644
--- a/components/sync/driver/data_type_controller.h
+++ b/components/sync/driver/data_type_controller.h
@@ -82,6 +82,8 @@
   // Returns true if the datatype started successfully.
   static bool IsSuccessfulResult(ConfigureResult result);
 
+  static std::string StateToString(State state);
+
   virtual ~DataTypeController();
 
   // Returns true if DataTypeManager should wait for LoadModels to complete
diff --git a/components/sync/driver/resources/about.html b/components/sync/driver/resources/about.html
index 31725a9..865c1d9 100644
--- a/components/sync/driver/resources/about.html
+++ b/components/sync/driver/resources/about.html
@@ -45,10 +45,12 @@
     <h2>Type Info</h2>
     <table id="typeInfo">
       <tr jsselect="type_status" jsvalues="class:$this.status">
-        <td jscontent="name" width=50%></td>
-        <td jscontent="value" width=30%></td>
+        <td jscontent="name" width=30%></td>
         <td jscontent="num_entries" width=10%></td>
         <td jscontent="num_live" width=10%></td>
+        <td jscontent="message" width=30%></td>
+        <td jscontent="state" width=10%></td>
+        <td jscontent="group_type" width=10%></td>
       </tr>
     </table>
   </div>
diff --git a/components/sync/driver/resources/about.js b/components/sync/driver/resources/about.js
index cebf496e..a0b657d1 100644
--- a/components/sync/driver/resources/about.js
+++ b/components/sync/driver/resources/about.js
@@ -43,10 +43,10 @@
       if (row.name == modelType) {
         // There are three types of counters, only "status" counters have these
         // fields. Keep the old values if updated fields are not present.
-        if (counters.numEntriesAndTombstones) {
+        if (counters.numEntriesAndTombstones !== undefined) {
           row.num_entries = counters.numEntriesAndTombstones;
         }
-        if (counters.numEntries) {
+        if (counters.numEntries !== undefined) {
           row.num_live = counters.numEntries;
         }
       }
diff --git a/components/sync/engine/model_safe_worker.cc b/components/sync/engine/model_safe_worker.cc
index 7735716..bd2ac21 100644
--- a/components/sync/engine/model_safe_worker.cc
+++ b/components/sync/engine/model_safe_worker.cc
@@ -53,23 +53,22 @@
 std::string ModelSafeGroupToString(ModelSafeGroup group) {
   switch (group) {
     case GROUP_UI:
-      return "GROUP_UI";
+      return "Group UI";
     case GROUP_DB:
-      return "GROUP_DB";
+      return "Group DB";
     case GROUP_FILE:
-      return "GROUP_FILE";
+      return "Group File";
     case GROUP_HISTORY:
-      return "GROUP_HISTORY";
+      return "Group History";
     case GROUP_PASSIVE:
-      return "GROUP_PASSIVE";
+      return "Group Passive";
     case GROUP_PASSWORD:
-      return "GROUP_PASSWORD";
+      return "Group Password";
     case GROUP_NON_BLOCKING:
-      return "GROUP_NON_BLOCKING";
-    default:
-      NOTREACHED();
-      return "INVALID";
+      return "Group Non Blocking";
   }
+  NOTREACHED();
+  return "Invalid";
 }
 
 ModelSafeWorker::ModelSafeWorker()
diff --git a/components/sync/engine/model_safe_worker_unittest.cc b/components/sync/engine/model_safe_worker_unittest.cc
index cb6cedd..b9e63583 100644
--- a/components/sync/engine/model_safe_worker_unittest.cc
+++ b/components/sync/engine/model_safe_worker_unittest.cc
@@ -90,10 +90,10 @@
   routing_info[PREFERENCES] = GROUP_DB;
   routing_info[APPS] = GROUP_NON_BLOCKING;
   base::DictionaryValue expected_value;
-  expected_value.SetString("Apps", "GROUP_NON_BLOCKING");
-  expected_value.SetString("Bookmarks", "GROUP_PASSIVE");
-  expected_value.SetString("Encryption Keys", "GROUP_UI");
-  expected_value.SetString("Preferences", "GROUP_DB");
+  expected_value.SetString("Apps", "Group Non Blocking");
+  expected_value.SetString("Bookmarks", "Group Passive");
+  expected_value.SetString("Encryption Keys", "Group UI");
+  expected_value.SetString("Preferences", "Group DB");
   std::unique_ptr<base::DictionaryValue> value(
       ModelSafeRoutingInfoToValue(routing_info));
   EXPECT_TRUE(value->Equals(&expected_value));
@@ -106,8 +106,8 @@
   routing_info[NIGORI] = GROUP_UI;
   routing_info[PREFERENCES] = GROUP_DB;
   EXPECT_EQ(
-      "{\"Apps\":\"GROUP_NON_BLOCKING\",\"Bookmarks\":\"GROUP_PASSIVE\","
-      "\"Encryption Keys\":\"GROUP_UI\",\"Preferences\":\"GROUP_DB\"}",
+      "{\"Apps\":\"Group Non Blocking\",\"Bookmarks\":\"Group Passive\","
+      "\"Encryption Keys\":\"Group UI\",\"Preferences\":\"Group DB\"}",
       ModelSafeRoutingInfoToString(routing_info));
 }
 
diff --git a/components/sync/engine_impl/model_type_registry.cc b/components/sync/engine_impl/model_type_registry.cc
index 7d15dd6..b6bc2e31a 100644
--- a/components/sync/engine_impl/model_type_registry.cc
+++ b/components/sync/engine_impl/model_type_registry.cc
@@ -270,7 +270,9 @@
   for (const auto& kv : data_type_debug_info_emitter_map_) {
     kv.second->EmitCommitCountersUpdate();
     kv.second->EmitUpdateCountersUpdate();
-    kv.second->EmitStatusCountersUpdate();
+    // Although this breaks encapsulation, don't emit status counters here.
+    // They've already been asked for manually on the UI thread because USS
+    // emitters don't have a working implementation yet.
   }
 }
 
diff --git a/components/sync/syncable/model_type.cc b/components/sync/syncable/model_type.cc
index 942b898..e1f4d49d 100644
--- a/components/sync/syncable/model_type.cc
+++ b/components/sync/syncable/model_type.cc
@@ -547,7 +547,7 @@
   if (model_type >= UNSPECIFIED && model_type < MODEL_TYPE_COUNT)
     return kModelTypeInfoMap[model_type].model_type_string;
   NOTREACHED() << "No known extension for model type.";
-  return "INVALID";
+  return "Invalid";
 }
 
 // The normal rules about histograms apply here.  Always append to the bottom of
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index da2de209..ec34aab 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -648,6 +648,8 @@
     "frame_host/ancestor_throttle.h",
     "frame_host/cross_process_frame_connector.cc",
     "frame_host/cross_process_frame_connector.h",
+    "frame_host/data_url_navigation_throttle.cc",
+    "frame_host/data_url_navigation_throttle.h",
     "frame_host/debug_urls.cc",
     "frame_host/debug_urls.h",
     "frame_host/form_submission_throttle.cc",
diff --git a/content/browser/devtools/protocol/schema_handler.cc b/content/browser/devtools/protocol/schema_handler.cc
index 6cf5b21..f4f862c 100644
--- a/content/browser/devtools/protocol/schema_handler.cc
+++ b/content/browser/devtools/protocol/schema_handler.cc
@@ -22,14 +22,37 @@
     std::unique_ptr<protocol::Array<Schema::Domain>>* domains) {
   // TODO(kozyatisnkiy): get this from the target instead of hardcoding a list.
   static const char kVersion[] = "1.2";
-  static const char* kDomains[] = {
-    "Inspector", "Memory", "Page", "Rendering", "Emulation", "Security",
-    "Network", "Database", "IndexedDB", "CacheStorage", "DOMStorage", "CSS",
-    "ApplicationCache", "DOM", "IO", "DOMDebugger", "ServiceWorker",
-    "Input", "LayerTree", "DeviceOrientation", "Tracing", "Animation",
-    "Accessibility", "Storage", "Log", "Runtime", "Debugger",
-    "Profiler", "HeapProfiler", "Schema", "Target"
-  };
+  static const char* kDomains[] = {"Inspector",
+                                   "Memory",
+                                   "Page",
+                                   "Emulation",
+                                   "Security",
+                                   "Network",
+                                   "Database",
+                                   "IndexedDB",
+                                   "CacheStorage",
+                                   "DOMStorage",
+                                   "CSS",
+                                   "ApplicationCache",
+                                   "DOM",
+                                   "IO",
+                                   "DOMDebugger",
+                                   "ServiceWorker",
+                                   "Input",
+                                   "LayerTree",
+                                   "DeviceOrientation",
+                                   "Tracing",
+                                   "Animation",
+                                   "Accessibility",
+                                   "Storage",
+                                   "Log",
+                                   "Runtime",
+                                   "Debugger",
+                                   "Profiler",
+                                   "HeapProfiler",
+                                   "Schema",
+                                   "Target",
+                                   "Overlay"};
   *domains = protocol::Array<Schema::Domain>::create();
   for (size_t i = 0; i < arraysize(kDomains); ++i) {
     (*domains)->addItem(Schema::Domain::Create()
diff --git a/content/browser/frame_host/data_url_navigation_browsertest.cc b/content/browser/frame_host/data_url_navigation_browsertest.cc
new file mode 100644
index 0000000..d500fb2b
--- /dev/null
+++ b/content/browser/frame_host/data_url_navigation_browsertest.cc
@@ -0,0 +1,942 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/strings/pattern.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
+#include "build/buildflag.h"
+#include "content/browser/site_per_process_browsertest.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/browser_side_navigation_policy.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/content_paths.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/public/test/download_test_observer.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "content/shell/browser/shell.h"
+#include "content/shell/browser/shell_download_manager_delegate.h"
+#include "net/base/escape.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "ppapi/features/features.h"
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+#include "content/public/browser/plugin_service.h"
+#include "content/public/common/webplugininfo.h"
+#endif
+
+namespace content {
+
+namespace {
+
+// The pattern to catch messages printed by the browser when a data URL
+// navigation is blocked.
+const char kDataUrlBlockedPattern[] =
+    "Not allowed to navigate top frame to data URL:*";
+
+// The message printed by the data URL when it successfully navigates.
+const char kDataUrlSuccessfulMessage[] = "NAVIGATION_SUCCESSFUL";
+
+// A "Hello World" PDF encoded as a data URL. Source of this PDF:
+// -------------------------
+// %PDF-1.7
+// 1 0 obj << /Type /Page /Parent 3 0 R /Resources 5 0 R /Contents 2 0 R >>
+// endobj
+// 2 0 obj << /Length 51 >>
+//  stream BT
+//  /F1 12 Tf
+//  1 0 0 1 100 20 Tm
+//  (Hello World)Tj
+//  ET
+//  endstream
+// endobj
+// 3 0 obj << /Type /Pages /Kids [ 1 0 R ] /Count 1 /MediaBox [ 0 0 300 50] >>
+// endobj
+// 4 0 obj << /Type /Font /Subtype /Type1 /Name /F1 /BaseFont/Arial >>
+// endobj
+// 5 0 obj << /ProcSet[/PDF/Text] /Font <</F1 4 0 R >> >>
+// endobj
+// 6 0 obj << /Type /Catalog /Pages 3 0 R >>
+// endobj
+// trailer << /Root 6 0 R >>
+// -------------------------
+const char kPdfUrl[] =
+    "data:application/pdf;base64,JVBERi0xLjcKMSAwIG9iaiA8PCAvVHlwZSAvUGFnZSAvUG"
+    "FyZW50IDMgMCBSIC9SZXNvdXJjZXMgNSAwIFIgL0NvbnRlbnRzIDIgMCBSID4+CmVuZG9iagoy"
+    "IDAgb2JqIDw8IC9MZW5ndGggNTEgPj4KIHN0cmVhbSBCVAogL0YxIDEyIFRmCiAxIDAgMCAxID"
+    "EwMCAyMCBUbQogKEhlbGxvIFdvcmxkKVRqCiBFVAogZW5kc3RyZWFtCmVuZG9iagozIDAgb2Jq"
+    "IDw8IC9UeXBlIC9QYWdlcyAvS2lkcyBbIDEgMCBSIF0gL0NvdW50IDEgL01lZGlhQm94IFsgMC"
+    "AwIDMwMCA1MF0gPj4KZW5kb2JqCjQgMCBvYmogPDwgL1R5cGUgL0ZvbnQgL1N1YnR5cGUgL1R5"
+    "cGUxIC9OYW1lIC9GMSAvQmFzZUZvbnQvQXJpYWwgPj4KZW5kb2JqCjUgMCBvYmogPDwgL1Byb2"
+    "NTZXRbL1BERi9UZXh0XSAvRm9udCA8PC9GMSA0IDAgUiA+PiA+PgplbmRvYmoKNiAwIG9iaiA8"
+    "PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMyAwIFIgPj4KZW5kb2JqCnRyYWlsZXIgPDwgL1Jvb3"
+    "QgNiAwIFIgPj4K";
+
+enum ExpectedNavigationStatus { NAVIGATION_BLOCKED, NAVIGATION_ALLOWED };
+
+// This class is similar to ConsoleObserverDelegate in that it listens and waits
+// for specific console messages. The difference from ConsoleObserverDelegate is
+// that this class immediately stops waiting if it sees a message matching
+// fail_pattern, instead of waiting for a message matching success_pattern.
+class DataURLWarningConsoleObserverDelegate : public WebContentsDelegate {
+ public:
+  DataURLWarningConsoleObserverDelegate(
+      WebContents* web_contents,
+      ExpectedNavigationStatus expected_navigation_status)
+      : web_contents_(web_contents),
+        success_filter_(expected_navigation_status == NAVIGATION_ALLOWED
+                            ? kDataUrlSuccessfulMessage
+                            : kDataUrlBlockedPattern),
+        fail_filter_(expected_navigation_status == NAVIGATION_ALLOWED
+                         ? kDataUrlBlockedPattern
+                         : kDataUrlSuccessfulMessage),
+        message_loop_runner_(
+            new MessageLoopRunner(MessageLoopRunner::QuitMode::IMMEDIATE)),
+        saw_failure_message_(false) {}
+  ~DataURLWarningConsoleObserverDelegate() override {}
+
+  void Wait() { message_loop_runner_->Run(); }
+
+  // WebContentsDelegate method:
+  bool DidAddMessageToConsole(WebContents* source,
+                              int32_t level,
+                              const base::string16& message,
+                              int32_t line_no,
+                              const base::string16& source_id) override {
+    DCHECK(source == web_contents_);
+    const std::string ascii_message = base::UTF16ToASCII(message);
+    if (base::MatchPattern(ascii_message, fail_filter_)) {
+      saw_failure_message_ = true;
+      message_loop_runner_->Quit();
+    }
+    if (base::MatchPattern(ascii_message, success_filter_)) {
+      message_loop_runner_->Quit();
+    }
+    return false;
+  }
+
+  // Returns true if the observer encountered a message that matches
+  // |fail_filter_|.
+  bool saw_failure_message() const { return saw_failure_message_; }
+
+ private:
+  WebContents* web_contents_;
+  const std::string success_filter_;
+  const std::string fail_filter_;
+  scoped_refptr<MessageLoopRunner> message_loop_runner_;
+  bool saw_failure_message_;
+};
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+// This class registers a fake PDF plugin handler so that data URL navigations
+// with a PDF mime type end up with a navigation and don't simply download the
+// file.
+class ScopedPluginRegister {
+ public:
+  ScopedPluginRegister(content::PluginService* plugin_service)
+      : plugin_service_(plugin_service) {
+    const char kPluginName[] = "PDF";
+    const char kPdfMimeType[] = "application/pdf";
+    const char kPdfFileType[] = "pdf";
+    WebPluginInfo plugin_info;
+    plugin_info.type = WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS;
+    plugin_info.name = base::ASCIIToUTF16(kPluginName);
+    plugin_info.mime_types.push_back(
+        WebPluginMimeType(kPdfMimeType, kPdfFileType, std::string()));
+    plugin_service_->RegisterInternalPlugin(plugin_info, false);
+    plugin_service_->RefreshPlugins();
+  }
+
+  ~ScopedPluginRegister() {
+    std::vector<WebPluginInfo> plugins;
+    plugin_service_->GetInternalPlugins(&plugins);
+    EXPECT_EQ(1u, plugins.size());
+    plugin_service_->UnregisterInternalPlugin(plugins[0].path);
+    plugin_service_->RefreshPlugins();
+
+    plugins.clear();
+    plugin_service_->GetInternalPlugins(&plugins);
+    EXPECT_TRUE(plugins.empty());
+  }
+
+ private:
+  content::PluginService* plugin_service_;
+};
+#endif  // BUILDFLAG(ENABLE_PLUGINS)
+
+}  // namespace
+
+class DataUrlNavigationBrowserTest : public ContentBrowserTest {
+ public:
+#if BUILDFLAG(ENABLE_PLUGINS)
+  DataUrlNavigationBrowserTest()
+      : scoped_plugin_register_(PluginService::GetInstance()) {}
+#else
+  DataUrlNavigationBrowserTest() {}
+#endif  // BUILDFLAG(ENABLE_PLUGINS)
+
+ protected:
+  void SetUpOnMainThread() override {
+    host_resolver()->AddRule("*", "127.0.0.1");
+    ASSERT_TRUE(embedded_test_server()->Start());
+
+    base::FilePath path;
+    ASSERT_TRUE(PathService::Get(content::DIR_TEST_DATA, &path));
+    path = path.AppendASCII("data_url_navigations.html");
+    ASSERT_TRUE(base::PathExists(path));
+
+    std::string contents;
+    ASSERT_TRUE(base::ReadFileToString(path, &contents));
+    data_url_ = GURL(std::string("data:text/html,") + contents);
+
+    ASSERT_TRUE(downloads_directory_.CreateUniqueTempDir());
+    ShellDownloadManagerDelegate* delegate =
+        static_cast<ShellDownloadManagerDelegate*>(
+            shell()
+                ->web_contents()
+                ->GetBrowserContext()
+                ->GetDownloadManagerDelegate());
+    delegate->SetDownloadBehaviorForTesting(downloads_directory_.GetPath());
+  }
+
+  // Adds an iframe to |rfh| pointing to |url|.
+  void AddIFrame(RenderFrameHost* rfh, const GURL& url) {
+    const std::string javascript = base::StringPrintf(
+        "f = document.createElement('iframe'); f.src = '%s';"
+        "document.body.appendChild(f);",
+        url.spec().c_str());
+    TestNavigationObserver observer(shell()->web_contents());
+    EXPECT_TRUE(ExecuteScript(rfh, javascript));
+    observer.Wait();
+  }
+
+  // Runs |javascript| on the first child frame and checks for a navigation.
+  void TestNavigationFromFrame(
+      const std::string& javascript,
+      ExpectedNavigationStatus expected_navigation_status) {
+    RenderFrameHost* child =
+        ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
+    ASSERT_TRUE(child);
+    if (AreAllSitesIsolatedForTesting()) {
+      ASSERT_TRUE(child->IsCrossProcessSubframe());
+    }
+    ExecuteScriptAndCheckNavigation(child, javascript,
+                                    expected_navigation_status);
+  }
+
+  // Runs |javascript| on the first child frame and expects a download to occur.
+  void TestDownloadFromFrame(const std::string& javascript) {
+    RenderFrameHost* child =
+        ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
+    ASSERT_TRUE(child);
+    if (AreAllSitesIsolatedForTesting()) {
+      ASSERT_TRUE(child->IsCrossProcessSubframe());
+    }
+    ExecuteScriptAndCheckNavigationDownload(child, javascript);
+  }
+
+  // Runs |javascript| on the first child frame and checks for a navigation to
+  // the PDF file pointed by the test case.
+  void TestPDFNavigationFromFrame(
+      const std::string& javascript,
+      ExpectedNavigationStatus expected_navigation_status) {
+    RenderFrameHost* child =
+        ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
+    ASSERT_TRUE(child);
+    if (AreAllSitesIsolatedForTesting()) {
+      ASSERT_TRUE(child->IsCrossProcessSubframe());
+    }
+    ExecuteScriptAndCheckPDFNavigation(child, javascript,
+                                       expected_navigation_status);
+  }
+
+  // Same as TestNavigationFromFrame, but instead of navigating, the child frame
+  // tries to open a new window with a data URL.
+  void TestWindowOpenFromFrame(
+      const std::string& javascript,
+      ExpectedNavigationStatus expected_navigation_status) {
+    RenderFrameHost* child =
+        ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
+    if (AreAllSitesIsolatedForTesting()) {
+      ASSERT_TRUE(child->IsCrossProcessSubframe());
+    }
+    ExecuteScriptAndCheckWindowOpen(child, javascript,
+                                    expected_navigation_status);
+  }
+
+  // Executes |javascript| on |rfh| and waits for a console message based on
+  // |expected_navigation_status|.
+  // - Blocked navigations should print kDataUrlBlockedPattern.
+  // - Allowed navigations should print kDataUrlSuccessfulMessage.
+  void ExecuteScriptAndCheckNavigation(
+      RenderFrameHost* rfh,
+      const std::string& javascript,
+      ExpectedNavigationStatus expected_navigation_status) {
+    const GURL original_url(shell()->web_contents()->GetLastCommittedURL());
+    const std::string expected_message;
+
+    DataURLWarningConsoleObserverDelegate console_delegate(
+        shell()->web_contents(), expected_navigation_status);
+    shell()->web_contents()->SetDelegate(&console_delegate);
+
+    TestNavigationObserver navigation_observer(shell()->web_contents());
+    EXPECT_TRUE(ExecuteScript(rfh, javascript));
+    console_delegate.Wait();
+    EXPECT_FALSE(console_delegate.saw_failure_message());
+    shell()->web_contents()->SetDelegate(nullptr);
+
+    switch (expected_navigation_status) {
+      case NAVIGATION_ALLOWED:
+        navigation_observer.Wait();
+        // The new page should have a data URL.
+        EXPECT_TRUE(shell()->web_contents()->GetLastCommittedURL().SchemeIs(
+            url::kDataScheme));
+        EXPECT_TRUE(navigation_observer.last_navigation_url().SchemeIs(
+            url::kDataScheme));
+        EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
+        break;
+
+      case NAVIGATION_BLOCKED:
+        // Original page shouldn't navigate away.
+        EXPECT_EQ(original_url, shell()->web_contents()->GetLastCommittedURL());
+        EXPECT_FALSE(navigation_observer.last_navigation_succeeded());
+        break;
+
+      default:
+        NOTREACHED();
+    }
+  }
+
+  // Similar to ExecuteScriptAndCheckNavigation(), but doesn't wait for a
+  // console message if the navigation is expected to be allowed (this is
+  // because PDF files can't print to the console).
+  void ExecuteScriptAndCheckPDFNavigation(
+      RenderFrameHost* rfh,
+      const std::string& javascript,
+      ExpectedNavigationStatus expected_navigation_status) {
+    const GURL original_url(shell()->web_contents()->GetLastCommittedURL());
+
+    const std::string expected_message =
+        (expected_navigation_status == NAVIGATION_ALLOWED)
+            ? std::string()
+            : kDataUrlBlockedPattern;
+
+    std::unique_ptr<ConsoleObserverDelegate> console_delegate;
+    if (!expected_message.empty()) {
+      console_delegate.reset(new ConsoleObserverDelegate(
+          shell()->web_contents(), expected_message));
+      shell()->web_contents()->SetDelegate(console_delegate.get());
+    }
+
+    TestNavigationObserver navigation_observer(shell()->web_contents());
+    EXPECT_TRUE(ExecuteScript(rfh, javascript));
+
+    if (console_delegate) {
+      console_delegate->Wait();
+      shell()->web_contents()->SetDelegate(nullptr);
+    }
+
+    switch (expected_navigation_status) {
+      case NAVIGATION_ALLOWED:
+        navigation_observer.Wait();
+        // The new page should have a data URL.
+        EXPECT_TRUE(shell()->web_contents()->GetLastCommittedURL().SchemeIs(
+            url::kDataScheme));
+        EXPECT_TRUE(navigation_observer.last_navigation_url().SchemeIs(
+            url::kDataScheme));
+        EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
+        break;
+
+      case NAVIGATION_BLOCKED:
+        // Original page shouldn't navigate away.
+        EXPECT_EQ(original_url, shell()->web_contents()->GetLastCommittedURL());
+        EXPECT_FALSE(navigation_observer.last_navigation_succeeded());
+        break;
+
+      default:
+        NOTREACHED();
+    }
+  }
+
+  // Executes |javascript| on |rfh| and waits for a new window to be opened.
+  // Does not check for console messages (it's currently not possible to
+  // concurrently wait for a new shell to be created and a console message to be
+  // printed on that new shell).
+  void ExecuteScriptAndCheckWindowOpen(
+      RenderFrameHost* rfh,
+      const std::string& javascript,
+      ExpectedNavigationStatus expected_navigation_status) {
+    ShellAddedObserver new_shell_observer;
+    EXPECT_TRUE(ExecuteScript(rfh, javascript));
+
+    Shell* new_shell = new_shell_observer.GetShell();
+    WaitForLoadStop(new_shell->web_contents());
+
+    switch (expected_navigation_status) {
+      case NAVIGATION_ALLOWED:
+        EXPECT_TRUE(new_shell->web_contents()->GetLastCommittedURL().SchemeIs(
+            url::kDataScheme));
+        break;
+
+      case NAVIGATION_BLOCKED:
+        EXPECT_TRUE(
+            new_shell->web_contents()->GetLastCommittedURL().is_empty());
+        break;
+
+      default:
+        NOTREACHED();
+    }
+  }
+
+  // Executes |javascript| on |rfh| and waits for a download to be started by
+  // a window.open call.
+  void ExecuteScriptAndCheckWindowOpenDownload(RenderFrameHost* rfh,
+                                               const std::string& javascript) {
+    const GURL original_url(shell()->web_contents()->GetLastCommittedURL());
+    ShellAddedObserver new_shell_observer;
+    DownloadManager* download_manager = BrowserContext::GetDownloadManager(
+        shell()->web_contents()->GetBrowserContext());
+
+    EXPECT_TRUE(ExecuteScript(rfh, javascript));
+    Shell* new_shell = new_shell_observer.GetShell();
+
+    DownloadTestObserverTerminal download_observer(
+        download_manager, 1, DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
+
+    WaitForLoadStop(new_shell->web_contents());
+    // If no download happens, this will timeout.
+    download_observer.WaitForFinished();
+
+    EXPECT_TRUE(
+        new_shell->web_contents()->GetLastCommittedURL().spec().empty());
+    // No navigation should commit.
+    EXPECT_FALSE(
+        new_shell->web_contents()->GetController().GetLastCommittedEntry());
+    // Original page shouldn't navigate away.
+    EXPECT_EQ(original_url, shell()->web_contents()->GetLastCommittedURL());
+  }
+
+  // Executes |javascript| on |rfh| and waits for a download to be started.
+  void ExecuteScriptAndCheckNavigationDownload(RenderFrameHost* rfh,
+                                               const std::string& javascript) {
+    const GURL original_url(shell()->web_contents()->GetLastCommittedURL());
+    DownloadManager* download_manager = BrowserContext::GetDownloadManager(
+        shell()->web_contents()->GetBrowserContext());
+    DownloadTestObserverTerminal download_observer(
+        download_manager, 1, DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
+
+    EXPECT_TRUE(ExecuteScript(rfh, javascript));
+    // If no download happens, this will timeout.
+    download_observer.WaitForFinished();
+
+    // Original page shouldn't navigate away.
+    EXPECT_EQ(original_url, shell()->web_contents()->GetLastCommittedURL());
+  }
+
+  // Initiates a browser initiated navigation to |url| and waits for a download
+  // to be started.
+  void NavigateAndCheckDownload(const GURL& url) {
+    const GURL original_url(shell()->web_contents()->GetLastCommittedURL());
+    DownloadManager* download_manager = BrowserContext::GetDownloadManager(
+        shell()->web_contents()->GetBrowserContext());
+    DownloadTestObserverTerminal download_observer(
+        download_manager, 1, DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
+    NavigateToURL(shell(), url);
+
+    // If no download happens, this will timeout.
+    download_observer.WaitForFinished();
+
+    // Original page shouldn't navigate away.
+    EXPECT_EQ(original_url, shell()->web_contents()->GetLastCommittedURL());
+  }
+
+  // data URL form of the file at content/test/data/data_url_navigations.html
+  GURL data_url() const { return data_url_; }
+
+ private:
+  base::ScopedTempDir downloads_directory_;
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+  ScopedPluginRegister scoped_plugin_register_;
+#endif  // BUILDFLAG(ENABLE_PLUGINS)
+
+  GURL data_url_;
+
+  DISALLOW_COPY_AND_ASSIGN(DataUrlNavigationBrowserTest);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// data URLs with HTML mimetype
+//
+// Tests that a browser initiated navigation to a data URL doesn't show a
+// console warning and is not blocked.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest, BrowserInitiated_Allow) {
+  DataURLWarningConsoleObserverDelegate console_delegate(
+      shell()->web_contents(), NAVIGATION_ALLOWED);
+  shell()->web_contents()->SetDelegate(&console_delegate);
+
+  NavigateToURL(shell(), GURL("data:text/"
+                              "html,<html><script>console.log('NAVIGATION_"
+                              "SUCCESSFUL');</script>"));
+  console_delegate.Wait();
+  shell()->web_contents()->SetDelegate(nullptr);
+
+  EXPECT_TRUE(shell()->web_contents()->GetLastCommittedURL().SchemeIs(
+      url::kDataScheme));
+}
+
+// Tests that a content initiated navigation to a data URL is blocked.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest, HTML_Navigation_Block) {
+  NavigateToURL(shell(),
+                embedded_test_server()->GetURL("/data_url_navigations.html"));
+  ExecuteScriptAndCheckNavigation(
+      shell()->web_contents()->GetMainFrame(),
+      "document.getElementById('navigate-top-frame-to-html').click()",
+      NAVIGATION_BLOCKED);
+}
+
+// Tests that a content initiated navigation to a data URL is allowed if
+// blocking is disabled with a feature flag.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       HTML_Navigation_Allow_FeatureFlag) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      features::kAllowContentInitiatedDataUrlNavigations);
+  NavigateToURL(shell(),
+                embedded_test_server()->GetURL("/data_url_navigations.html"));
+  ExecuteScriptAndCheckNavigation(
+      shell()->web_contents()->GetMainFrame(),
+      "document.getElementById('navigate-top-frame-to-html').click()",
+      NAVIGATION_ALLOWED);
+}
+
+// Tests that a window.open to a data URL with HTML mime type is blocked.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest, HTML_WindowOpen_Block) {
+  NavigateToURL(shell(),
+                embedded_test_server()->GetURL("/data_url_navigations.html"));
+  ExecuteScriptAndCheckWindowOpen(
+      shell()->web_contents()->GetMainFrame(),
+      "document.getElementById('window-open-html').click()",
+      NAVIGATION_BLOCKED);
+}
+
+// Tests that a form post to a data URL with HTML mime type is blocked.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest, HTML_FormPost_Block) {
+  NavigateToURL(shell(),
+                embedded_test_server()->GetURL("/data_url_navigations.html"));
+  ExecuteScriptAndCheckNavigation(
+      shell()->web_contents()->GetMainFrame(),
+      "document.getElementById('form-post-to-html').click()",
+      NAVIGATION_BLOCKED);
+}
+
+// Tests that navigating the main frame to a data URL with HTML mimetype from a
+// subframe is blocked.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       HTML_NavigationFromFrame_Block) {
+  // This test fails and is disabled in site-per-process + no plznavigate mode.
+  // request->originDocument is null in FrameLoader::prepareForRequest,
+  // allowing the navigation by default. See https://crbug.com/647839
+  if (AreAllSitesIsolatedForTesting() && !IsBrowserSideNavigationEnabled()) {
+    return;
+  }
+
+  NavigateToURL(shell(),
+                embedded_test_server()->GetURL("a.com", "/simple_page.html"));
+  AddIFrame(
+      shell()->web_contents()->GetMainFrame(),
+      embedded_test_server()->GetURL("b.com", "/data_url_navigations.html"));
+
+  TestNavigationFromFrame(
+      "document.getElementById('navigate-top-frame-to-html').click()",
+      NAVIGATION_BLOCKED);
+}
+
+// Tests that opening a new data URL window from a subframe is blocked.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       HTML_WindowOpenFromFrame_Block) {
+  NavigateToURL(shell(),
+                embedded_test_server()->GetURL("a.com", "/simple_page.html"));
+  AddIFrame(
+      shell()->web_contents()->GetMainFrame(),
+      embedded_test_server()->GetURL("b.com", "/data_url_navigations.html"));
+
+  TestWindowOpenFromFrame("document.getElementById('window-open-html').click()",
+                          NAVIGATION_BLOCKED);
+}
+
+// Tests that navigation to a data URL is blocked even if the top frame is
+// already a data URL.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       HTML_Navigation_DataToData_Block) {
+  NavigateToURL(shell(), data_url());
+  ExecuteScriptAndCheckNavigation(
+      shell()->web_contents()->GetMainFrame(),
+      "document.getElementById('navigate-top-frame-to-html').click()",
+      NAVIGATION_BLOCKED);
+}
+
+// Tests that a form post to a data URL with HTML mime type is blocked even if
+// the top frame is already a data URL.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       HTML_FormPost_DataToData_Block) {
+  NavigateToURL(shell(), data_url());
+  ExecuteScriptAndCheckNavigation(
+      shell()->web_contents()->GetMainFrame(),
+      "document.getElementById('form-post-to-html').click()",
+      NAVIGATION_BLOCKED);
+}
+
+// Tests that navigating the top frame to a data URL with HTML mimetype is
+// blocked even if the top frame is already a data URL.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       HTML_NavigationFromFrame_TopFrameIsDataURL_Block) {
+  // This test fails and is disabled in site-per-process + no plznavigate mode.
+  // request->originDocument is null in FrameLoader::prepareForRequest,
+  // allowing the navigation by default. See https://crbug.com/647839
+  if (AreAllSitesIsolatedForTesting() && !IsBrowserSideNavigationEnabled()) {
+    return;
+  }
+
+  const GURL top_url(
+      base::StringPrintf("data:text/html, <iframe src='%s'></iframe>",
+                         embedded_test_server()
+                             ->GetURL("/data_url_navigations.html")
+                             .spec()
+                             .c_str()));
+  NavigateToURL(shell(), top_url);
+
+  TestNavigationFromFrame(
+      "document.getElementById('navigate-top-frame-to-html').click()",
+      NAVIGATION_BLOCKED);
+}
+
+// Tests that opening a new window with a data URL with HTML mimetype is blocked
+// even if the top frame is already a data URL.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       HTML_WindowOpenFromFrame_TopFrameIsDataURL_Block) {
+  const GURL top_url(
+      base::StringPrintf("data:text/html, <iframe src='%s'></iframe>",
+                         embedded_test_server()
+                             ->GetURL("/data_url_navigations.html")
+                             .spec()
+                             .c_str()));
+  NavigateToURL(shell(), top_url);
+
+  TestWindowOpenFromFrame("document.getElementById('window-open-html').click()",
+                          NAVIGATION_BLOCKED);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// data URLs with octet-stream mimetype (binary)
+//
+// Test that a direct navigation to a binary mime type initiates a download.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       OctetStream_BrowserInitiated_Download) {
+  NavigateAndCheckDownload(GURL("data:application/octet-stream,test"));
+}
+
+// Test that window.open to a data URL results in a download if the URL has a
+// binary mime type.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       OctetStream_WindowOpen_Download) {
+  NavigateToURL(shell(),
+                embedded_test_server()->GetURL("/data_url_navigations.html"));
+  ExecuteScriptAndCheckWindowOpenDownload(
+      shell()->web_contents()->GetMainFrame(),
+      "document.getElementById('window-open-octetstream').click()");
+}
+
+// Test that a navigation to a data URL results in a download if the URL has a
+// binary mime type.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       OctetStream_Navigation_Download) {
+  NavigateToURL(shell(),
+                embedded_test_server()->GetURL("/data_url_navigations.html"));
+  ExecuteScriptAndCheckNavigationDownload(
+      shell()->web_contents()->GetMainFrame(),
+      "document.getElementById('navigate-top-frame-to-octetstream').click()");
+}
+
+// Test that a form post to a data URL results in a download if the URL has a
+// binary mime type.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       OctetStream_FormPost_Download) {
+  NavigateToURL(shell(),
+                embedded_test_server()->GetURL("/data_url_navigations.html"));
+  ExecuteScriptAndCheckNavigationDownload(
+      shell()->web_contents()->GetMainFrame(),
+      "document.getElementById('form-post-to-octetstream').click()");
+}
+
+// Tests that navigating the main frame from a subframe results in a download
+// if the URL has a binary mimetype.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       OctetStream_NavigationFromFrame_Download) {
+  NavigateToURL(shell(),
+                embedded_test_server()->GetURL("a.com", "/simple_page.html"));
+  AddIFrame(
+      shell()->web_contents()->GetMainFrame(),
+      embedded_test_server()->GetURL("b.com", "/data_url_navigations.html"));
+
+  TestDownloadFromFrame(
+      "document.getElementById('navigate-top-frame-to-octetstream').click()");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// data URLs with unknown mimetype
+//
+// Test that a direct navigation to an unknown mime type initiates a download.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       UnknownMimeType_BrowserInitiated_Download) {
+  NavigateAndCheckDownload(GURL("data:unknown/mimetype,test"));
+}
+
+// Test that window.open to a data URL results in a download if the URL has an
+// unknown mime type.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       UnknownMimeType_WindowOpen_Download) {
+  NavigateToURL(shell(),
+                embedded_test_server()->GetURL("/data_url_navigations.html"));
+  ExecuteScriptAndCheckWindowOpenDownload(
+      shell()->web_contents()->GetMainFrame(),
+      "document.getElementById('window-open-unknown-mimetype').click()");
+}
+
+// Test that a navigation to a data URL results in a download if the URL has an
+// unknown mime type.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       UnknownMimeType_Navigation_Download) {
+  NavigateToURL(shell(),
+                embedded_test_server()->GetURL("/data_url_navigations.html"));
+  ExecuteScriptAndCheckNavigationDownload(
+      shell()->web_contents()->GetMainFrame(),
+      "document.getElementById('navigate-top-"
+      "frame-to-unknown-mimetype').click()");
+}
+
+// Test that a form post to a data URL results in a download if the URL has an
+// unknown mime type.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       UnknownMimeType_FormPost_Download) {
+  NavigateToURL(shell(),
+                embedded_test_server()->GetURL("/data_url_navigations.html"));
+  ExecuteScriptAndCheckNavigationDownload(
+      shell()->web_contents()->GetMainFrame(),
+      "document.getElementById('form-post-to-unknown-mimetype').click()");
+}
+
+// Tests that navigating the main frame from a subframe results in a download
+// if the URL has an unknown mimetype.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       UnknownMimeType_NavigationFromFrame_Download) {
+  NavigateToURL(shell(),
+                embedded_test_server()->GetURL("a.com", "/simple_page.html"));
+  AddIFrame(
+      shell()->web_contents()->GetMainFrame(),
+      embedded_test_server()->GetURL("b.com", "/data_url_navigations.html"));
+
+  TestDownloadFromFrame(
+      "document.getElementById('navigate-top-frame-to-unknown-mimetype').click("
+      ")");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// data URLs with PDF mimetype
+//
+// Tests that a browser initiated navigation to a data URL with PDF mime type is
+// allowed, or initiates a download on Android.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       PDF_BrowserInitiatedNavigation_Allow) {
+#if !defined(OS_ANDROID)
+  TestNavigationObserver observer(shell()->web_contents());
+  NavigateToURL(shell(), GURL(kPdfUrl));
+  EXPECT_EQ(GURL(kPdfUrl), observer.last_navigation_url());
+  EXPECT_TRUE(observer.last_navigation_succeeded());
+  EXPECT_TRUE(shell()->web_contents()->GetLastCommittedURL().SchemeIs(
+      url::kDataScheme));
+#else
+  // On Android, PDFs are downloaded upon navigation.
+  NavigateAndCheckDownload(GURL(kPdfUrl));
+#endif
+}
+
+// Tests that a window.open to a data URL is blocked if the data URL has a
+// mime type that will be handled by a plugin (PDF in this case).
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest, PDF_WindowOpen_Block) {
+  NavigateToURL(shell(),
+                embedded_test_server()->GetURL("/data_url_navigations.html"));
+
+#if !defined(OS_ANDROID)
+  ExecuteScriptAndCheckWindowOpen(
+      shell()->web_contents()->GetMainFrame(),
+      "document.getElementById('window-open-pdf').click()", NAVIGATION_BLOCKED);
+#else
+  // On Android, PDFs are downloaded upon navigation.
+  ExecuteScriptAndCheckNavigationDownload(
+      shell()->web_contents()->GetMainFrame(),
+      "document.getElementById('window-open-pdf').click()");
+#endif
+}
+
+// Test that a navigation to a data URL is blocked if the data URL has a mime
+// type that will be handled by a plugin (PDF in this case).
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest, PDF_Navigation_Block) {
+  NavigateToURL(shell(),
+                embedded_test_server()->GetURL("/data_url_navigations.html"));
+
+#if !defined(OS_ANDROID)
+  ExecuteScriptAndCheckPDFNavigation(
+      shell()->web_contents()->GetMainFrame(),
+      "document.getElementById('navigate-top-frame-to-pdf').click()",
+      NAVIGATION_BLOCKED);
+#else
+  // On Android, PDFs are downloaded upon navigation.
+  ExecuteScriptAndCheckNavigationDownload(
+      shell()->web_contents()->GetMainFrame(),
+      "document.getElementById('navigate-top-frame-to-pdf').click()");
+#endif
+}
+
+// Test that a form post to a data URL is blocked if the data URL has a mime
+// type that will be handled by a plugin (PDF in this case).
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest, PDF_FormPost_Block) {
+  NavigateToURL(shell(),
+                embedded_test_server()->GetURL("/data_url_navigations.html"));
+
+#if !defined(OS_ANDROID)
+  ExecuteScriptAndCheckPDFNavigation(
+      shell()->web_contents()->GetMainFrame(),
+      "document.getElementById('form-post-to-pdf').click()",
+      NAVIGATION_BLOCKED);
+#else
+  // On Android, PDFs are downloaded upon navigation.
+  ExecuteScriptAndCheckNavigationDownload(
+      shell()->web_contents()->GetMainFrame(),
+      "document.getElementById('form-post-to-pdf').click()");
+#endif
+}
+
+// Tests that navigating the main frame to a data URL with PDF mimetype from a
+// subframe is blocked.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       PDF_NavigationFromFrame_Block) {
+  NavigateToURL(shell(),
+                embedded_test_server()->GetURL("a.com", "/simple_page.html"));
+  AddIFrame(
+      shell()->web_contents()->GetMainFrame(),
+      embedded_test_server()->GetURL("b.com", "/data_url_navigations.html"));
+
+#if !defined(OS_ANDROID)
+  TestPDFNavigationFromFrame(
+      "document.getElementById('navigate-top-frame-to-pdf').click()",
+      NAVIGATION_BLOCKED);
+#else
+  // On Android, PDFs are downloaded upon navigation.
+  RenderFrameHost* child =
+      ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
+  ASSERT_TRUE(child);
+  if (AreAllSitesIsolatedForTesting()) {
+    ASSERT_TRUE(child->IsCrossProcessSubframe());
+  }
+  ExecuteScriptAndCheckNavigationDownload(
+      child, "document.getElementById('navigate-top-frame-to-pdf').click()");
+#endif
+}
+
+// Tests that opening a window with a data URL with PDF mimetype from a
+// subframe is blocked.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       PDF_WindowOpenFromFrame_Block) {
+  NavigateToURL(shell(),
+                embedded_test_server()->GetURL("a.com", "/simple_page.html"));
+  AddIFrame(
+      shell()->web_contents()->GetMainFrame(),
+      embedded_test_server()->GetURL("b.com", "/data_url_navigations.html"));
+
+#if !defined(OS_ANDROID)
+  TestWindowOpenFromFrame("document.getElementById('window-open-pdf').click()",
+                          NAVIGATION_BLOCKED);
+#else
+  // On Android, PDFs are downloaded upon navigation.
+  RenderFrameHost* child =
+      ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
+  ASSERT_TRUE(child);
+  if (AreAllSitesIsolatedForTesting()) {
+    ASSERT_TRUE(child->IsCrossProcessSubframe());
+  }
+  ExecuteScriptAndCheckNavigationDownload(
+      child, "document.getElementById('window-open-pdf').click()");
+#endif
+}
+
+// Tests that navigating the top frame to a data URL with PDF mimetype from a
+// subframe is blocked even if the top frame is already a data URL.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       PDF_NavigationFromFrame_TopFrameIsDataURL_Block) {
+  const GURL top_url(
+      base::StringPrintf("data:text/html, <iframe src='%s'></iframe>",
+                         embedded_test_server()
+                             ->GetURL("/data_url_navigations.html")
+                             .spec()
+                             .c_str()));
+  NavigateToURL(shell(), top_url);
+
+#if !defined(OS_ANDROID)
+  TestPDFNavigationFromFrame(
+      "document.getElementById('navigate-top-frame-to-pdf').click()",
+      NAVIGATION_BLOCKED);
+#else
+  // On Android, PDFs are downloaded upon navigation.
+  RenderFrameHost* child =
+      ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
+  ASSERT_TRUE(child);
+  if (AreAllSitesIsolatedForTesting()) {
+    ASSERT_TRUE(child->IsCrossProcessSubframe());
+  }
+  ExecuteScriptAndCheckNavigationDownload(
+      child, "document.getElementById('navigate-top-frame-to-pdf').click()");
+#endif
+}
+
+// Tests that opening a window with a data URL with PDF mimetype from a
+// subframe is blocked even if the top frame is already a data URL.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTest,
+                       PDF_WindowOpenFromFrame_TopFrameIsDataURL_Block) {
+  const GURL top_url(
+      base::StringPrintf("data:text/html, <iframe src='%s'></iframe>",
+                         embedded_test_server()
+                             ->GetURL("/data_url_navigations.html")
+                             .spec()
+                             .c_str()));
+  NavigateToURL(shell(), top_url);
+
+#if !defined(OS_ANDROID)
+  TestWindowOpenFromFrame("document.getElementById('window-open-pdf').click()",
+                          NAVIGATION_BLOCKED);
+#else
+  // On Android, PDFs are downloaded upon navigation.
+  RenderFrameHost* child =
+      ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
+  ASSERT_TRUE(child);
+  if (AreAllSitesIsolatedForTesting()) {
+    ASSERT_TRUE(child->IsCrossProcessSubframe());
+  }
+  ExecuteScriptAndCheckNavigationDownload(
+      child, "document.getElementById('window-open-pdf').click()");
+#endif
+}
+
+}  // content
diff --git a/content/browser/frame_host/data_url_navigation_throttle.cc b/content/browser/frame_host/data_url_navigation_throttle.cc
new file mode 100644
index 0000000..b2a958a4
--- /dev/null
+++ b/content/browser/frame_host/data_url_navigation_throttle.cc
@@ -0,0 +1,62 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/frame_host/data_url_navigation_throttle.h"
+
+#include "base/feature_list.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/stringprintf.h"
+#include "content/browser/frame_host/frame_tree.h"
+#include "content/browser/frame_host/frame_tree_node.h"
+#include "content/browser/frame_host/navigation_handle_impl.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/common/console_message_level.h"
+#include "content/public/common/content_features.h"
+#include "url/url_constants.h"
+
+namespace content {
+
+namespace {
+const char kConsoleError[] =
+    "Not allowed to navigate top frame to data URL: %s";
+}
+
+DataUrlNavigationThrottle::DataUrlNavigationThrottle(
+    NavigationHandle* navigation_handle)
+    : NavigationThrottle(navigation_handle) {}
+
+DataUrlNavigationThrottle::~DataUrlNavigationThrottle() {}
+
+NavigationThrottle::ThrottleCheckResult
+DataUrlNavigationThrottle::WillProcessResponse() {
+  NavigationHandleImpl* handle =
+      static_cast<NavigationHandleImpl*>(navigation_handle());
+  if (handle->is_download())
+    return PROCEED;
+
+  RenderFrameHost* top_frame =
+      handle->frame_tree_node()->frame_tree()->root()->current_frame_host();
+  top_frame->AddMessageToConsole(
+      CONSOLE_MESSAGE_LEVEL_ERROR,
+      base::StringPrintf(kConsoleError, handle->GetURL().spec().c_str()));
+  return CANCEL;
+}
+
+// static
+std::unique_ptr<NavigationThrottle>
+DataUrlNavigationThrottle::CreateThrottleForNavigation(
+    NavigationHandle* navigation_handle) {
+  if (navigation_handle->GetURL().SchemeIs(url::kDataScheme) &&
+      navigation_handle->IsInMainFrame() &&
+      navigation_handle->IsRendererInitiated() &&
+      !navigation_handle->IsSameDocument() &&
+      !base::FeatureList::IsEnabled(
+          features::kAllowContentInitiatedDataUrlNavigations)) {
+    return base::MakeUnique<DataUrlNavigationThrottle>(navigation_handle);
+  }
+  return nullptr;
+}
+
+}  // namespace content
diff --git a/content/browser/frame_host/data_url_navigation_throttle.h b/content/browser/frame_host/data_url_navigation_throttle.h
new file mode 100644
index 0000000..2234ee6
--- /dev/null
+++ b/content/browser/frame_host/data_url_navigation_throttle.h
@@ -0,0 +1,32 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_FRAME_HOST_DATA_URL_NAVIGATION_THROTTLE_
+#define CONTENT_BROWSER_FRAME_HOST_DATA_URL_NAVIGATION_THROTTLE_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "content/public/browser/navigation_throttle.h"
+
+namespace content {
+
+class DataUrlNavigationThrottle : public NavigationThrottle {
+ public:
+  explicit DataUrlNavigationThrottle(NavigationHandle* navigation_handle);
+  ~DataUrlNavigationThrottle() override;
+
+  // NavigationThrottle method:
+  ThrottleCheckResult WillProcessResponse() override;
+
+  static std::unique_ptr<NavigationThrottle> CreateThrottleForNavigation(
+      NavigationHandle* navigation_handle);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DataUrlNavigationThrottle);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_FRAME_HOST_DATA_URL_NAVIGATION_THROTTLE_
diff --git a/content/browser/frame_host/navigation_handle_impl.cc b/content/browser/frame_host/navigation_handle_impl.cc
index 8dca644..b86f81e 100644
--- a/content/browser/frame_host/navigation_handle_impl.cc
+++ b/content/browser/frame_host/navigation_handle_impl.cc
@@ -13,6 +13,7 @@
 #include "content/browser/child_process_security_policy_impl.h"
 #include "content/browser/devtools/render_frame_devtools_agent_host.h"
 #include "content/browser/frame_host/ancestor_throttle.h"
+#include "content/browser/frame_host/data_url_navigation_throttle.h"
 #include "content/browser/frame_host/debug_urls.h"
 #include "content/browser/frame_host/form_submission_throttle.h"
 #include "content/browser/frame_host/frame_tree_node.h"
@@ -681,14 +682,6 @@
   } else {
     state_ = DID_COMMIT;
   }
-
-  if (url_.SchemeIs(url::kDataScheme) && IsInMainFrame() &&
-      IsRendererInitiated()) {
-    GetRenderFrameHost()->AddMessageToConsole(
-        CONSOLE_MESSAGE_LEVEL_WARNING,
-        "Upcoming versions will block content-initiated top frame navigations "
-        "to data: URLs. For more information, see https://goo.gl/BaZAea.");
-  }
 }
 
 void NavigationHandleImpl::Transfer() {
@@ -930,6 +923,13 @@
   std::vector<std::unique_ptr<NavigationThrottle>> throttles_to_register =
       GetDelegate()->CreateThrottlesForNavigation(this);
 
+  // Check for renderer-inititated main frame navigations to data URLs. This is
+  // done first as it may block the main frame navigation altogether.
+  std::unique_ptr<NavigationThrottle> data_url_navigation_throttle =
+      DataUrlNavigationThrottle::CreateThrottleForNavigation(this);
+  if (data_url_navigation_throttle)
+    throttles_to_register.push_back(std::move(data_url_navigation_throttle));
+
   std::unique_ptr<content::NavigationThrottle> ancestor_throttle =
       content::AncestorThrottle::MaybeCreateThrottleFor(this);
   if (ancestor_throttle)
diff --git a/content/browser/frame_host/navigation_handle_impl.h b/content/browser/frame_host/navigation_handle_impl.h
index c3c3fa7..ed097391 100644
--- a/content/browser/frame_host/navigation_handle_impl.h
+++ b/content/browser/frame_host/navigation_handle_impl.h
@@ -177,6 +177,11 @@
   // enabled. Make it work in both modes.
   bool is_form_submission() const { return is_form_submission_; }
 
+  // Whether the navigation request is a download. This is useful when the
+  // navigation hasn't committed yet, in which case HasCommitted() will return
+  // false even if the navigation request is not a download.
+  bool is_download() const { return is_download_; }
+
   // The NavigatorDelegate to notify/query for various navigation events.
   // Normally this is the WebContents, except if this NavigationHandle was
   // created during a navigation to an interstitial page. In this case it will
diff --git a/content/browser/gpu/gpu_internals_ui.cc b/content/browser/gpu/gpu_internals_ui.cc
index 3fb2e53..45f2f6d 100644
--- a/content/browser/gpu/gpu_internals_ui.cc
+++ b/content/browser/gpu/gpu_internals_ui.cc
@@ -218,16 +218,9 @@
     const char kGDMSession[] = "GDMSESSION";
     if (env->GetVar(kGDMSession, &value))
       basic_info->Append(NewDescriptionValuePair(kGDMSession, value));
-    const char* const kAtomsToCache[] = {
-        "_NET_WM_CM_S0",
-        NULL
-    };
-    ui::X11AtomCache atom_cache(gfx::GetXDisplay(), kAtomsToCache);
-    std::string compositing_manager = XGetSelectionOwner(
-        gfx::GetXDisplay(),
-        atom_cache.GetAtom("_NET_WM_CM_S0")) != None ? "Yes" : "No";
-    basic_info->Append(
-        NewDescriptionValuePair("Compositing manager", compositing_manager));
+    basic_info->Append(NewDescriptionValuePair(
+        "Compositing manager",
+        ui::IsCompositingManagerPresent() ? "Yes" : "No"));
   }
 #endif
   std::string direct_rendering = gpu_info.direct_rendering ? "Yes" : "No";
diff --git a/content/browser/renderer_host/delegated_frame_host.cc b/content/browser/renderer_host/delegated_frame_host.cc
index 2e3ac00..9cc1f75 100644
--- a/content/browser/renderer_host/delegated_frame_host.cc
+++ b/content/browser/renderer_host/delegated_frame_host.cc
@@ -90,6 +90,9 @@
           switches::kDisableResizeLock))
     return;
 
+  if (!has_frame_)
+    return;
+
   if (!client_->DelegatedFrameCanCreateResizeLock())
     return;
 
@@ -520,6 +523,7 @@
   client_->DelegatedFrameHostGetLayer()->SetShowSolidColorContent();
   support_->EvictFrame();
   has_frame_ = false;
+  resize_lock_.reset();
   frame_evictor_->DiscardedFrame();
   UpdateGutters();
 }
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.h b/content/browser/renderer_host/render_widget_host_view_aura.h
index e57aa37..c0e1fd7 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.h
+++ b/content/browser/renderer_host/render_widget_host_view_aura.h
@@ -356,6 +356,7 @@
                            SkippedDelegatedFrames);
   FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewAuraTest,
                            ResizeAfterReceivingFrame);
+  FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewAuraTest, MissingFramesDontLock);
   FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewAuraTest, OutputSurfaceIdChange);
   FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewAuraTest,
                            DiscardDelegatedFrames);
diff --git a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
index 1ddd65e..aea4d890 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
@@ -2613,6 +2613,55 @@
   view_->window_->RemoveObserver(&observer);
 }
 
+// When the DelegatedFrameHost does not have a frame from the renderer, it has
+// no reason to lock the compositor as there can't be guttering around a
+// renderer frame that doesn't exist.
+TEST_F(RenderWidgetHostViewAuraTest, MissingFramesDontLock) {
+  gfx::Rect view_rect(100, 100);
+  gfx::Size frame_size = view_rect.size();
+
+  view_->InitAsChild(nullptr);
+  aura::client::ParentWindowWithContext(
+      view_->GetNativeView(), parent_view_->GetNativeView()->GetRootWindow(),
+      gfx::Rect());
+
+  // The view is resized before the first frame, which should not lock the
+  // compositor as it's never received a frame to show yet.
+  view_->SetSize(view_rect.size());
+
+  EXPECT_FALSE(view_->resize_locked());
+  EXPECT_FALSE(view_->compositor_locked());
+
+  // Submit a frame of initial size to make a frame present in
+  // DelegatedFrameHost, at which point locking becomes feasible if resized.
+  view_->SubmitCompositorFrame(
+      kArbitraryLocalSurfaceId,
+      MakeDelegatedFrame(1.f, frame_size, gfx::Rect(frame_size)));
+  view_->RunOnCompositingDidCommit();
+
+  EXPECT_FALSE(view_->resize_locked());
+  EXPECT_FALSE(view_->compositor_locked());
+
+  // The view is resized and has its frame evicted, before a new frame arrives.
+  // The resize will lock the compositor, but when evicted, it should no longer
+  // be locked.
+  view_rect.SetRect(0, 0, 150, 150);
+  view_->SetSize(view_rect.size());
+  EXPECT_TRUE(view_->resize_locked());
+  EXPECT_TRUE(view_->compositor_locked());
+
+  view_->ClearCompositorFrame();
+  EXPECT_FALSE(view_->resize_locked());
+  EXPECT_FALSE(view_->compositor_locked());
+
+  // And future resizes after eviction should not lock the compositor since
+  // there is no frame present.
+  view_rect.SetRect(0, 0, 120, 120);
+  view_->SetSize(view_rect.size());
+  EXPECT_FALSE(view_->resize_locked());
+  EXPECT_FALSE(view_->compositor_locked());
+}
+
 TEST_F(RenderWidgetHostViewAuraTest, OutputSurfaceIdChange) {
   gfx::Rect view_rect(100, 100);
   gfx::Size frame_size = view_rect.size();
diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc
index 457ec14..cb7fccec 100644
--- a/content/browser/web_contents/web_contents_impl_browsertest.cc
+++ b/content/browser/web_contents/web_contents_impl_browsertest.cc
@@ -801,7 +801,7 @@
                             "window.open('" + kViewSourceURL.spec() + "');"));
   Shell* new_shell = new_shell_observer.GetShell();
   WaitForLoadStop(new_shell->web_contents());
-  EXPECT_EQ("", new_shell->web_contents()->GetURL().spec());
+  EXPECT_TRUE(new_shell->web_contents()->GetURL().spec().empty());
   // No navigation should commit.
   EXPECT_FALSE(
       new_shell->web_contents()->GetController().GetLastCommittedEntry());
@@ -848,90 +848,6 @@
                   ->IsViewSourceMode());
 }
 
-namespace {
-const char kDataUrlWarningPattern[] =
-    "Upcoming versions will block content-initiated top frame navigations*";
-
-// This class listens for console messages other than the data: URL warning. It
-// fails the test if it sees a data: URL warning.
-class NoDataURLWarningConsoleObserverDelegate : public ConsoleObserverDelegate {
- public:
-  using ConsoleObserverDelegate::ConsoleObserverDelegate;
-  // WebContentsDelegate method:
-  bool DidAddMessageToConsole(WebContents* source,
-                              int32_t level,
-                              const base::string16& message,
-                              int32_t line_no,
-                              const base::string16& source_id) override {
-    std::string ascii_message = base::UTF16ToASCII(message);
-    EXPECT_FALSE(base::MatchPattern(ascii_message, kDataUrlWarningPattern));
-    return ConsoleObserverDelegate::DidAddMessageToConsole(
-        source, level, message, line_no, source_id);
-  }
-};
-
-}  // namespace
-
-// Test that a direct navigation to a data URL doesn't show a console warning.
-IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, DataURLDirectNavigation) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-  const GURL kUrl(embedded_test_server()->GetURL("/simple_page.html"));
-
-  NoDataURLWarningConsoleObserverDelegate console_delegate(
-      shell()->web_contents(), "FINISH");
-  shell()->web_contents()->SetDelegate(&console_delegate);
-
-  NavigateToURL(
-      shell(),
-      GURL("data:text/html,<html><script>console.log('FINISH');</script>"));
-  console_delegate.Wait();
-  EXPECT_TRUE(shell()->web_contents()->GetURL().SchemeIs(url::kDataScheme));
-  EXPECT_FALSE(
-      base::MatchPattern(console_delegate.message(), kDataUrlWarningPattern));
-}
-
-// Test that window.open to a data URL shows a console warning.
-IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
-                       DataURLWindowOpen_ShouldWarn) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-  const GURL kUrl(embedded_test_server()->GetURL("/simple_page.html"));
-  NavigateToURL(shell(), kUrl);
-
-  ShellAddedObserver new_shell_observer;
-  EXPECT_TRUE(ExecuteScript(shell()->web_contents(),
-                            "window.open('data:text/plain,test');"));
-  Shell* new_shell = new_shell_observer.GetShell();
-
-  ConsoleObserverDelegate console_delegate(
-      new_shell->web_contents(),
-      "Upcoming versions will block content-initiated top frame navigations*");
-  new_shell->web_contents()->SetDelegate(&console_delegate);
-  console_delegate.Wait();
-  EXPECT_TRUE(new_shell->web_contents()->GetURL().SchemeIs(url::kDataScheme));
-}
-
-// Test that a content initiated navigation to a data URL shows a console
-// warning.
-IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, DataURLRedirect_ShouldWarn) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-  const GURL kUrl(embedded_test_server()->GetURL("/simple_page.html"));
-  NavigateToURL(shell(), kUrl);
-
-  ConsoleObserverDelegate console_delegate(
-      shell()->web_contents(),
-      "Upcoming versions will block content-initiated top frame navigations*");
-  shell()->web_contents()->SetDelegate(&console_delegate);
-  EXPECT_TRUE(ExecuteScript(shell()->web_contents(),
-                            "window.location.href = 'data:text/plain,test';"));
-  console_delegate.Wait();
-  EXPECT_TRUE(shell()
-                  ->web_contents()
-                  ->GetController()
-                  .GetLastCommittedEntry()
-                  ->GetURL()
-                  .SchemeIs(url::kDataScheme));
-}
-
 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, NewNamedWindow) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
diff --git a/content/browser/webrtc/webrtc_media_recorder_browsertest.cc b/content/browser/webrtc/webrtc_media_recorder_browsertest.cc
index cf97546..7d945a9a3 100644
--- a/content/browser/webrtc/webrtc_media_recorder_browsertest.cc
+++ b/content/browser/webrtc/webrtc_media_recorder_browsertest.cc
@@ -8,7 +8,6 @@
 #include "build/build_config.h"
 #include "content/browser/webrtc/webrtc_content_browsertest_base.h"
 #include "content/public/common/content_switches.h"
-#include "content/public/common/features.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test_utils.h"
 #include "media/base/media_switches.h"
@@ -23,9 +22,7 @@
 } const kEncodingParameters[] = {
     {true, "video/webm;codecs=VP8"},
     {true, "video/webm;codecs=VP9"},
-#if BUILDFLAG(RTC_USE_H264)
     {true, "video/x-matroska;codecs=AVC1"},
-#endif
     {false, ""},  // Instructs the platform to choose any accelerated codec.
     {false, "video/webm;codecs=VP8"},
     {false, "video/webm;codecs=VP9"},
@@ -128,14 +125,10 @@
 }
 
 IN_PROC_BROWSER_TEST_P(WebRtcMediaRecorderTest, RecordWithTransparency) {
-  // Alpha channel is only supported in "video/webm;codecs={vp8/vp9}" cases.
-  if (GetParam().mime_type.find("video/webm") == std::string::npos)
-    return;
-
   MaybeForceDisableEncodeAccelerator(GetParam().disable_accelerator);
   MakeTypicalCall(base::StringPrintf("testRecordWithTransparency(\"%s\");",
                                      GetParam().mime_type.c_str()),
-                                     kMediaRecorderHtmlFile);
+                  kMediaRecorderHtmlFile);
 }
 
 IN_PROC_BROWSER_TEST_F(WebRtcMediaRecorderTest, IllegalStopThrowsDOMError) {
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index 13d4c1f..ab89709 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -370,6 +370,11 @@
                                                 false);
   }
 
+  WebRuntimeFeatures::EnableFeatureFromString(
+      "AllowContentInitiatedDataUrlNavigations",
+      base::FeatureList::IsEnabled(
+          features::kAllowContentInitiatedDataUrlNavigations));
+
 #if defined(OS_ANDROID)
   if (base::FeatureList::IsEnabled(features::kWebNfc))
     WebRuntimeFeatures::EnableWebNfc(true);
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 85729455..01708df 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -16,6 +16,13 @@
     "AccessibilityObjectModel",
     base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Enables content-initiated, main frame navigations to data URLs.
+// TODO(meacer): Remove when the deprecation is complete.
+//               https://www.chromestatus.com/feature/5669602927312896
+const base::Feature kAllowContentInitiatedDataUrlNavigations{
+    "AllowContentInitiatedDataUrlNavigations",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables asm.js to WebAssembly V8 backend.
 // http://asmjs.org/spec/latest/
 const base::Feature kAsmJsToWebAssembly{"AsmJsToWebAssembly",
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 559d477..b2342cce 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -17,6 +17,8 @@
 // All features in alphabetical order. The features should be documented
 // alongside the definition of their values in the .cc file.
 CONTENT_EXPORT extern const base::Feature kAccessibilityObjectModel;
+CONTENT_EXPORT extern const base::Feature
+    kAllowContentInitiatedDataUrlNavigations;
 CONTENT_EXPORT extern const base::Feature kAsmJsToWebAssembly;
 CONTENT_EXPORT extern const base::Feature kBlockCredentialedSubresources;
 CONTENT_EXPORT extern const base::Feature kBrotliEncoding;
diff --git a/content/public/test/test_navigation_observer.cc b/content/public/test/test_navigation_observer.cc
index ffa280a..7166065 100644
--- a/content/public/test/test_navigation_observer.cc
+++ b/content/public/test/test_navigation_observer.cc
@@ -71,6 +71,7 @@
     : navigation_started_(false),
       navigations_completed_(0),
       number_of_navigations_(number_of_navigations),
+      last_navigation_succeeded_(false),
       message_loop_runner_(new MessageLoopRunner(quit_mode)),
       web_contents_created_callback_(
           base::Bind(&TestNavigationObserver::OnWebContentsCreated,
diff --git a/content/renderer/media_recorder/media_recorder_handler.cc b/content/renderer/media_recorder/media_recorder_handler.cc
index 544665f..16b0bb1 100644
--- a/content/renderer/media_recorder/media_recorder_handler.cc
+++ b/content/renderer/media_recorder/media_recorder_handler.cc
@@ -44,7 +44,7 @@
       return media::kCodecVP8;
     case VideoTrackRecorder::CodecId::VP9:
       return media::kCodecVP9;
-#if defined(IS_H264_SUPPORTED)
+#if BUILDFLAG(RTC_USE_H264)
     case VideoTrackRecorder::CodecId::H264:
       return media::kCodecH264;
 #endif
@@ -142,7 +142,7 @@
     codec_id_ = VideoTrackRecorder::CodecId::VP8;
   else if (codecs_str.find("vp9") != std::string::npos)
     codec_id_ = VideoTrackRecorder::CodecId::VP9;
-#if defined(IS_H264_SUPPORTED)
+#if BUILDFLAG(RTC_USE_H264)
   else if (codecs_str.find("h264") != std::string::npos)
     codec_id_ = VideoTrackRecorder::CodecId::H264;
   else if (codecs_str.find("avc1") != std::string::npos)
diff --git a/content/renderer/media_recorder/video_track_recorder.cc b/content/renderer/media_recorder/video_track_recorder.cc
index 8b747737..49def8d1 100644
--- a/content/renderer/media_recorder/video_track_recorder.cc
+++ b/content/renderer/media_recorder/video_track_recorder.cc
@@ -66,7 +66,7 @@
 } kPreferredCodecIdAndVEAProfiles[] = {
     {CodecId::VP8, media::VP8PROFILE_MIN, media::VP8PROFILE_MAX},
     {CodecId::VP9, media::VP9PROFILE_MIN, media::VP9PROFILE_MAX},
-#if defined(IS_H264_SUPPORTED)
+#if BUILDFLAG(RTC_USE_H264)
     {CodecId::H264, media::H264PROFILE_MIN, media::H264PROFILE_MAX}
 #endif
 };
@@ -109,6 +109,11 @@
   return;
 #endif
 
+#if defined(OS_ANDROID)
+  // See https://crbug.com/653864.
+  return;
+#endif
+
   content::RenderThreadImpl* const render_thread_impl =
       content::RenderThreadImpl::current();
   if (!render_thread_impl) {
diff --git a/content/renderer/media_recorder/video_track_recorder.h b/content/renderer/media_recorder/video_track_recorder.h
index 046b1ad..53a871f 100644
--- a/content/renderer/media_recorder/video_track_recorder.h
+++ b/content/renderer/media_recorder/video_track_recorder.h
@@ -18,12 +18,6 @@
 #include "third_party/WebKit/public/platform/WebMediaStreamTrack.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 
-#if BUILDFLAG(RTC_USE_H264) || defined(OS_ANDROID)
-// H264 encoding is supported on Android using the platform encode
-// accelerator, and elsewhere using OpenH264.
-#define IS_H264_SUPPORTED
-#endif
-
 namespace base {
 class Thread;
 }  // namespace base
@@ -38,13 +32,8 @@
 }  // namespace media
 
 namespace video_track_recorder {
-#if defined(OS_ANDROID)
-const int kVEAEncoderMinResolutionWidth = 176;
-const int kVEAEncoderMinResolutionHeight = 144;
-#else
 const int kVEAEncoderMinResolutionWidth = 640;
 const int kVEAEncoderMinResolutionHeight = 480;
-#endif
 }  // namespace video_track_recorder
 
 namespace content {
@@ -62,7 +51,7 @@
   enum class CodecId {
     VP8,
     VP9,
-#if defined(IS_H264_SUPPORTED)
+#if BUILDFLAG(RTC_USE_H264)
     H264,
 #endif
     LAST
diff --git a/content/renderer/media_recorder/video_track_recorder_unittest.cc b/content/renderer/media_recorder/video_track_recorder_unittest.cc
index 8cefcf4..05cd924 100644
--- a/content/renderer/media_recorder/video_track_recorder_unittest.cc
+++ b/content/renderer/media_recorder/video_track_recorder_unittest.cc
@@ -47,8 +47,6 @@
     VideoTrackRecorder::CodecId::VP8,
     VideoTrackRecorder::CodecId::VP9
 #if BUILDFLAG(RTC_USE_H264)
-    // This test case can't be enabled in Android because there is no software
-    // OpenH264 fallback nor render thread to enumerate the hardware encoders.
     , VideoTrackRecorder::CodecId::H264
 #endif
 };
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index fbf545b8..ebdb9309 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -4712,6 +4712,12 @@
   return !blocked;
 }
 
+bool RenderFrameImpl::AllowContentInitiatedDataUrlNavigations(
+    const blink::WebURL& url) {
+  // Error pages can navigate to data URLs.
+  return url.GetString() == kUnreachableWebDataURL;
+}
+
 blink::WebScreenOrientationClient*
 RenderFrameImpl::GetWebScreenOrientationClient() {
   if (!screen_orientation_dispatcher_)
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index cd89b850..e5dd469 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -650,6 +650,8 @@
   blink::WebString UserAgentOverride() override;
   blink::WebString DoNotTrackValue() override;
   bool AllowWebGL(bool default_value) override;
+  bool AllowContentInitiatedDataUrlNavigations(
+      const blink::WebURL& url) override;
   blink::WebScreenOrientationClient* GetWebScreenOrientationClient() override;
   void PostAccessibilityEvent(const blink::WebAXObject& obj,
                               blink::WebAXEvent event) override;
diff --git a/content/shell/browser/shell_web_contents_view_delegate_views.cc b/content/shell/browser/shell_web_contents_view_delegate_views.cc
index fe2ea5c..11c9750 100644
--- a/content/shell/browser/shell_web_contents_view_delegate_views.cc
+++ b/content/shell/browser/shell_web_contents_view_delegate_views.cc
@@ -87,8 +87,7 @@
 
   context_menu_model_.reset(new ContextMenuModel(web_contents_, params));
   context_menu_runner_.reset(new views::MenuRunner(
-      context_menu_model_.get(),
-      views::MenuRunner::CONTEXT_MENU | views::MenuRunner::ASYNC));
+      context_menu_model_.get(), views::MenuRunner::CONTEXT_MENU));
 
   views::Widget* widget = views::Widget::GetWidgetForNativeView(
       web_contents_->GetTopLevelNativeWindow());
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 26de07f8..fe8a813 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -608,6 +608,7 @@
     "../browser/fileapi/file_system_browsertest.cc",
     "../browser/fileapi/fileapi_browsertest.cc",
     "../browser/find_request_manager_browsertest.cc",
+    "../browser/frame_host/data_url_navigation_browsertest.cc",
     "../browser/frame_host/form_submission_throttle_browsertest.cc",
     "../browser/frame_host/frame_tree_browsertest.cc",
     "../browser/frame_host/interstitial_page_impl_browsertest.cc",
@@ -927,10 +928,7 @@
       "../browser/webrtc/webrtc_webcam_browsertest.cc",
       "../browser/webrtc/webrtc_webcam_browsertest.h",
     ]
-    deps += [
-      "//testing/perf",
-      "//content/public/common:features",
-    ]
+    deps += [ "//testing/perf" ]
   }
 
   if (enable_plugins) {
@@ -990,6 +988,10 @@
       # The cast shell media pipeline doesn't produce real video frames that we
       # can inspect.
       "../browser/media/media_color_browsertest.cc",
+
+      # The cast shell has plugin support but no pdf, causing data URL PDF tests
+      # to fail.
+      "../browser/frame_host/data_url_navigation_browsertest.cc",
     ]
   }
 }
diff --git a/content/test/data/data_url_navigations.html b/content/test/data/data_url_navigations.html
new file mode 100644
index 0000000..64d4c7f
--- /dev/null
+++ b/content/test/data/data_url_navigations.html
@@ -0,0 +1,101 @@
+<html>
+
+<h3>HTML mimetype</h3>
+
+<button id='navigate-top-frame-to-html'
+    onclick='top.location.href=`data:text/html,
+        <script>console.log(&quot;NAVIGATION_SUCCESSFUL&quot;)</script>`'>
+  Navigate top frame to data URL HTML
+</button>
+<br>
+<button id='window-open-html'
+    onclick='window.open(`data:text/html,
+        <script>console.log(&quot;NAVIGATION_SUCCESSFUL&quot;)</script>`);'>
+  Open new window with a data URL HTML
+</button>
+<br>
+<form method="post" action="data:text/html,
+             <script>console.log('NAVIGATION_SUCCESSFUL')</script>">
+  <input type=submit id='form-post-to-html'
+      value="Submit form to data URL HTML">
+</form>
+
+<h3>octet-stream mimetype</h3>
+
+<button id='navigate-top-frame-to-octetstream'
+    onclick='top.location.href=`data:application/octet-stream,test`'>
+  Navigate top frame to data URL octet-stream
+</button>
+<br>
+<button id='window-open-octetstream'
+    onclick='window.open(`data:application/octet-stream,test`)'>
+  Open new window with a data URL octet-stream
+</button>
+<form method="post" action="data:application/octet-stream,test">
+  <input type=submit id='form-post-to-octetstream'
+      value="Submit form to data URL octet-stream">
+</form>
+<h3>PDF mimetype</h3>
+
+<button id='navigate-top-frame-to-pdf'
+    onclick='top.location.href=`data:application/pdf;base64,
+    JVBERi0xLjcKMSAwIG9iaiA8PCAvVHlwZSAvUGFnZSAvUG
+    FyZW50IDMgMCBSIC9SZXNvdXJjZXMgNSAwIFIgL0NvbnRlbnRzIDIgMCBSID4+CmVuZG9iagoy
+    IDAgb2JqIDw8IC9MZW5ndGggNTEgPj4KIHN0cmVhbSBCVAogL0YxIDEyIFRmCiAxIDAgMCAxID
+    EwMCAyMCBUbQogKEhlbGxvIFdvcmxkKVRqCiBFVAogZW5kc3RyZWFtCmVuZG9iagozIDAgb2Jq
+    IDw8IC9UeXBlIC9QYWdlcyAvS2lkcyBbIDEgMCBSIF0gL0NvdW50IDEgL01lZGlhQm94IFsgMC
+    AwIDMwMCA1MF0gPj4KZW5kb2JqCjQgMCBvYmogPDwgL1R5cGUgL0ZvbnQgL1N1YnR5cGUgL1R5
+    cGUxIC9OYW1lIC9GMSAvQmFzZUZvbnQvQXJpYWwgPj4KZW5kb2JqCjUgMCBvYmogPDwgL1Byb2
+    NTZXRbL1BERi9UZXh0XSAvRm9udCA8PC9GMSA0IDAgUiA+PiA+PgplbmRvYmoKNiAwIG9iaiA8
+    PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMyAwIFIgPj4KZW5kb2JqCnRyYWlsZXIgPDwgL1Jvb3
+    QgNiAwIFIgPj4K`'>
+  Navigate top frame to data URL PDF
+</button>
+<br>
+<button id='window-open-pdf'
+    onclick='window.open(`data:application/pdf;base64,
+    JVBERi0xLjcKMSAwIG9iaiA8PCAvVHlwZSAvUGFnZSAvUG
+    FyZW50IDMgMCBSIC9SZXNvdXJjZXMgNSAwIFIgL0NvbnRlbnRzIDIgMCBSID4+CmVuZG9iagoy
+    IDAgb2JqIDw8IC9MZW5ndGggNTEgPj4KIHN0cmVhbSBCVAogL0YxIDEyIFRmCiAxIDAgMCAxID
+    EwMCAyMCBUbQogKEhlbGxvIFdvcmxkKVRqCiBFVAogZW5kc3RyZWFtCmVuZG9iagozIDAgb2Jq
+    IDw8IC9UeXBlIC9QYWdlcyAvS2lkcyBbIDEgMCBSIF0gL0NvdW50IDEgL01lZGlhQm94IFsgMC
+    AwIDMwMCA1MF0gPj4KZW5kb2JqCjQgMCBvYmogPDwgL1R5cGUgL0ZvbnQgL1N1YnR5cGUgL1R5
+    cGUxIC9OYW1lIC9GMSAvQmFzZUZvbnQvQXJpYWwgPj4KZW5kb2JqCjUgMCBvYmogPDwgL1Byb2
+    NTZXRbL1BERi9UZXh0XSAvRm9udCA8PC9GMSA0IDAgUiA+PiA+PgplbmRvYmoKNiAwIG9iaiA8
+    PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMyAwIFIgPj4KZW5kb2JqCnRyYWlsZXIgPDwgL1Jvb3
+    QgNiAwIFIgPj4K`)'>
+  Open new window with a data URL PDF
+</button>
+<br>
+<form method="post" action='data:application/pdf;base64,
+    JVBERi0xLjcKMSAwIG9iaiA8PCAvVHlwZSAvUGFnZSAvUG
+    FyZW50IDMgMCBSIC9SZXNvdXJjZXMgNSAwIFIgL0NvbnRlbnRzIDIgMCBSID4+CmVuZG9iagoy
+    IDAgb2JqIDw8IC9MZW5ndGggNTEgPj4KIHN0cmVhbSBCVAogL0YxIDEyIFRmCiAxIDAgMCAxID
+    EwMCAyMCBUbQogKEhlbGxvIFdvcmxkKVRqCiBFVAogZW5kc3RyZWFtCmVuZG9iagozIDAgb2Jq
+    IDw8IC9UeXBlIC9QYWdlcyAvS2lkcyBbIDEgMCBSIF0gL0NvdW50IDEgL01lZGlhQm94IFsgMC
+    AwIDMwMCA1MF0gPj4KZW5kb2JqCjQgMCBvYmogPDwgL1R5cGUgL0ZvbnQgL1N1YnR5cGUgL1R5
+    cGUxIC9OYW1lIC9GMSAvQmFzZUZvbnQvQXJpYWwgPj4KZW5kb2JqCjUgMCBvYmogPDwgL1Byb2
+    NTZXRbL1BERi9UZXh0XSAvRm9udCA8PC9GMSA0IDAgUiA+PiA+PgplbmRvYmoKNiAwIG9iaiA8
+    PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMyAwIFIgPj4KZW5kb2JqCnRyYWlsZXIgPDwgL1Jvb3
+    QgNiAwIFIgPj4K'>
+  <input type=submit id='form-post-to-pdf'
+      value="Submit form to data URL PDF">
+</form>
+
+<h3>Unknown mimetype</h3>
+
+<button id='navigate-top-frame-to-unknown-mimetype'
+    onclick='top.location.href=`data:unknown/mimetype,test`'>
+  Navigate top frame to data URL unknown mimetype
+</button>
+<br>
+<button id='window-open-unknown-mimetype'
+    onclick='window.open(`data:unknown/mimetype,test`)'>
+  Open new window with a data URL unknown mimetype
+</button>
+<form method="post" action="data:unknown/mimetype,test">
+  <input type=submit id='form-post-to-unknown-mimetype'
+         value='Submit form to data URL unkown mimetype'>
+</form>
+
+</html>
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
index 3d80440..179d0ed 100644
--- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -218,8 +218,10 @@
     # Mac only.
 
     # Fails on all GPU types.
+    self.Flaky('conformance/context/context-release-upon-reload.html',
+        ['mac'], bug=713829)
     self.Fail('conformance2/glsl3/vector-dynamic-indexing-swizzled-lvalue.html',
-              ['mac'], bug=709351)
+        ['mac'], bug=709351)
     self.Fail('conformance2/rendering/' +
         'blitframebuffer-resolve-to-back-buffer.html',
         ['mac'], bug=699566)
@@ -553,8 +555,6 @@
         ['mac', 'amd', 'intel'], bug=679691)
 
     # Mac Intel
-    self.Flaky('conformance/context/context-release-upon-reload.html',
-      ['sierra', 'intel'], bug=713829)
     self.Fail(
       'conformance2/textures/canvas/tex-2d-rgb9_e5-rgb-float.html',
       ['sierra', 'intel'], bug=663188)
diff --git a/device/vr/BUILD.gn b/device/vr/BUILD.gn
index 6afccbc..3aa3c8d 100644
--- a/device/vr/BUILD.gn
+++ b/device/vr/BUILD.gn
@@ -28,8 +28,10 @@
   defines = [ "DEVICE_VR_IMPLEMENTATION" ]
   deps = [
     ":features",
-    ":mojo_bindings",
   ]
+  if (!is_ios) {
+    deps += [ ":mojo_bindings" ]
+  }
 
   if (!enable_vr) {
     sources += [
@@ -121,17 +123,19 @@
   }
 }
 
-mojom("mojo_bindings") {
-  sources = [
-    "vr_service.mojom",
-  ]
+if (!is_ios) {
+  mojom("mojo_bindings") {
+    sources = [
+      "vr_service.mojom",
+    ]
 
-  public_deps = [
-    "//gpu/ipc/common:interfaces",
-    "//mojo/common:common_custom_types",
-  ]
+    public_deps = [
+      "//gpu/ipc/common:interfaces",
+      "//mojo/common:common_custom_types",
+    ]
 
-  export_class_attribute = "DEVICE_VR_EXPORT"
-  export_define = "DEVICE_VR_IMPLEMENTATION=1"
-  export_header = "device/vr/vr_export.h"
+    export_class_attribute = "DEVICE_VR_EXPORT"
+    export_define = "DEVICE_VR_IMPLEMENTATION=1"
+    export_header = "device/vr/vr_export.h"
+  }
 }
diff --git a/extensions/browser/guest_view/web_view/web_view_apitest.cc b/extensions/browser/guest_view/web_view/web_view_apitest.cc
index 745edfe..bce35f7 100644
--- a/extensions/browser/guest_view/web_view/web_view_apitest.cc
+++ b/extensions/browser/guest_view/web_view/web_view_apitest.cc
@@ -618,6 +618,11 @@
 }
 
 IN_PROC_BROWSER_TEST_F(WebViewAPITest,
+                       TestContentInitiatedNavigationToDataUrlBlocked) {
+  RunTest("testContentInitiatedNavigationToDataUrlBlocked", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest,
                        TestNavOnConsecutiveSrcAttributeChanges) {
   RunTest("testNavOnConsecutiveSrcAttributeChanges", "web_view/apitest");
 }
diff --git a/extensions/renderer/api_request_handler_unittest.cc b/extensions/renderer/api_request_handler_unittest.cc
index f40527b..d29ba5f 100644
--- a/extensions/renderer/api_request_handler_unittest.cc
+++ b/extensions/renderer/api_request_handler_unittest.cc
@@ -358,4 +358,80 @@
   thread.reset();
 }
 
+TEST_F(APIRequestHandlerTest, SettingLastError) {
+  v8::HandleScope handle_scope(isolate());
+  v8::Local<v8::Context> context = MainContext();
+
+  base::Optional<std::string> logged_error;
+  auto get_parent = [](v8::Local<v8::Context> context) {
+    return context->Global();
+  };
+
+  auto log_error = [](base::Optional<std::string>* logged_error,
+                      v8::Local<v8::Context> context,
+                      const std::string& error) { *logged_error = error; };
+
+  APIRequestHandler request_handler(
+      base::Bind(&DoNothingWithRequest),
+      base::Bind(&APIRequestHandlerTest::RunJS, base::Unretained(this)),
+      APILastError(base::Bind(get_parent),
+                   base::Bind(log_error, &logged_error)));
+
+  const char kReportExposedLastError[] =
+      "(function() {\n"
+      "  if (this.lastError)\n"
+      "    this.seenLastError = this.lastError.message;\n"
+      "})";
+  auto get_exposed_error = [context]() {
+    return GetStringPropertyFromObject(context->Global(), context,
+                                       "seenLastError");
+  };
+
+  {
+    // Test a successful function call. No last error should be emitted to the
+    // console or exposed to the callback.
+    v8::Local<v8::Function> callback =
+        FunctionFromString(context, kReportExposedLastError);
+    int request_id = request_handler.StartRequest(
+        context, kMethod, base::MakeUnique<base::ListValue>(), callback,
+        v8::Local<v8::Function>(), binding::RequestThread::UI);
+    request_handler.CompleteRequest(request_id, base::ListValue(),
+                                    std::string());
+    EXPECT_FALSE(logged_error);
+    EXPECT_EQ("undefined", get_exposed_error());
+    logged_error.reset();
+  }
+
+  {
+    // Test a function call resulting in an error. Since the callback checks the
+    // last error, no error should be logged to the console (but it should be
+    // exposed to the callback).
+    v8::Local<v8::Function> callback =
+        FunctionFromString(context, kReportExposedLastError);
+    int request_id = request_handler.StartRequest(
+        context, kMethod, base::MakeUnique<base::ListValue>(), callback,
+        v8::Local<v8::Function>(), binding::RequestThread::UI);
+    request_handler.CompleteRequest(request_id, base::ListValue(),
+                                    "some error");
+    EXPECT_FALSE(logged_error);
+    EXPECT_EQ("\"some error\"", get_exposed_error());
+    logged_error.reset();
+  }
+
+  {
+    // Test a function call resulting in an error that goes unchecked by the
+    // callback. The error should be logged.
+    v8::Local<v8::Function> callback =
+        FunctionFromString(context, "(function() {})");
+    int request_id = request_handler.StartRequest(
+        context, kMethod, base::MakeUnique<base::ListValue>(), callback,
+        v8::Local<v8::Function>(), binding::RequestThread::UI);
+    request_handler.CompleteRequest(request_id, base::ListValue(),
+                                    "some error");
+    ASSERT_TRUE(logged_error);
+    EXPECT_EQ("Unchecked runtime.lastError: some error", *logged_error);
+    logged_error.reset();
+  }
+}
+
 }  // namespace extensions
diff --git a/extensions/renderer/native_extension_bindings_system.cc b/extensions/renderer/native_extension_bindings_system.cc
index 3e84d28..60968b0 100644
--- a/extensions/renderer/native_extension_bindings_system.cc
+++ b/extensions/renderer/native_extension_bindings_system.cc
@@ -476,7 +476,12 @@
     bool success,
     const base::ListValue& response,
     const std::string& error) {
-  api_system_.CompleteRequest(request_id, response, error);
+  // Some API calls result in failure, but don't set an error. Use a generic and
+  // unhelpful error string.
+  // TODO(devlin): Track these down and fix them. See crbug.com/648275.
+  api_system_.CompleteRequest(
+      request_id, response,
+      !success && error.empty() ? "Unknown error." : error);
 }
 
 RequestSender* NativeExtensionBindingsSystem::GetRequestSender() {
diff --git a/extensions/renderer/native_extension_bindings_system_unittest.cc b/extensions/renderer/native_extension_bindings_system_unittest.cc
index b853306..f977f31 100644
--- a/extensions/renderer/native_extension_bindings_system_unittest.cc
+++ b/extensions/renderer/native_extension_bindings_system_unittest.cc
@@ -694,33 +694,40 @@
 
   bindings_system()->UpdateBindingsForContext(script_context);
 
-  {
-    // Try calling the function with an invalid invocation - an error should be
-    // thrown.
-    const char kCallFunction[] =
-        "(function() {\n"
-        "  chrome.idle.queryState(30, function(state) {\n"
-        "    if (chrome.runtime.lastError)\n"
-        "      this.lastErrorMessage = chrome.runtime.lastError.message;\n"
-        "  });\n"
-        "});";
-    v8::Local<v8::Function> function =
-        FunctionFromString(context, kCallFunction);
-    ASSERT_FALSE(function.IsEmpty());
-    RunFunctionOnGlobal(function, context, 0, nullptr);
-  }
+  const char kCallFunction[] =
+      "(function() {\n"
+      "  chrome.idle.queryState(30, function(state) {\n"
+      "    if (chrome.runtime.lastError)\n"
+      "      this.lastErrorMessage = chrome.runtime.lastError.message;\n"
+      "  });\n"
+      "});";
+  v8::Local<v8::Function> function = FunctionFromString(context, kCallFunction);
+  ASSERT_FALSE(function.IsEmpty());
+  RunFunctionOnGlobal(function, context, 0, nullptr);
 
   // Validate the params that would be sent to the browser.
   EXPECT_EQ(extension->id(), last_params().extension_id);
   EXPECT_EQ("idle.queryState", last_params().name);
 
-  // Respond and validate.
-  bindings_system()->HandleResponse(last_params().request_id, true,
+  int first_request_id = last_params().request_id;
+  // Respond with an error.
+  bindings_system()->HandleResponse(last_params().request_id, false,
                                     base::ListValue(), "Some API Error");
-
   EXPECT_EQ("\"Some API Error\"",
             GetStringPropertyFromObject(context->Global(), context,
                                         "lastErrorMessage"));
+
+  // Test responding with a failure, but no set error.
+  RunFunctionOnGlobal(function, context, 0, nullptr);
+  EXPECT_EQ(extension->id(), last_params().extension_id);
+  EXPECT_EQ("idle.queryState", last_params().name);
+  EXPECT_NE(first_request_id, last_params().request_id);
+
+  bindings_system()->HandleResponse(last_params().request_id, false,
+                                    base::ListValue(), std::string());
+  EXPECT_EQ("\"Unknown error.\"",
+            GetStringPropertyFromObject(context->Global(), context,
+                                        "lastErrorMessage"));
 }
 
 TEST_F(NativeExtensionBindingsSystemUnittest, TestCustomProperties) {
diff --git a/extensions/test/data/web_view/apitest/main.js b/extensions/test/data/web_view/apitest/main.js
index b6c734f..71ba033 100644
--- a/extensions/test/data/web_view/apitest/main.js
+++ b/extensions/test/data/web_view/apitest/main.js
@@ -465,6 +465,28 @@
   document.body.appendChild(webview);
 }
 
+// This test verifies that guests are blocked from navigating the webview to a
+// data URL.
+function testContentInitiatedNavigationToDataUrlBlocked() {
+  var navUrl = "data:text/html,foo";
+  var webview = document.createElement('webview');
+  webview.addEventListener('consolemessage', function(e) {
+    if (e.message.startsWith(
+        'Not allowed to navigate top frame to data URL:')) {
+      embedder.test.succeed();
+    }
+  });
+  webview.addEventListener('loadstop', function(e) {
+    if (webview.getAttribute('src') == navUrl) {
+      embedder.test.fail();
+    }
+  });
+  webview.setAttribute('src',
+      'data:text/html,<script>window.location.href = "' + navUrl +
+      '";</scr' + 'ipt>');
+  document.body.appendChild(webview);
+}
+
 // This test verifies that the load event fires when the a new page is
 // loaded.
 // TODO(fsamuel): Add a test to verify that subframe loads within a guest
@@ -1229,6 +1251,12 @@
 }
 
 // This test verifies that new window attachment functions as expected.
+//
+// TODO(crbug.com/594215) Test that opening a new window with a data URL is
+// blocked. There is currently no way to test this, as the block message is
+// printed on the new window which never gets created, so the message is lost.
+// Also test that opening a new window with a data URL when the webview is
+// already on a data URL is allowed.
 function testNewWindow() {
   var webview = document.createElement('webview');
   webview.addEventListener('newwindow', function(e) {
@@ -1753,6 +1781,8 @@
   'testCannotMutateEventName': testCannotMutateEventName,
   'testChromeExtensionRelativePath': testChromeExtensionRelativePath,
   'testChromeExtensionURL': testChromeExtensionURL,
+  'testContentInitiatedNavigationToDataUrlBlocked':
+      testContentInitiatedNavigationToDataUrlBlocked,
   'testContentLoadEvent': testContentLoadEvent,
   'testDeclarativeWebRequestAPI': testDeclarativeWebRequestAPI,
   'testDeclarativeWebRequestAPISendMessage':
diff --git a/headless/BUILD.gn b/headless/BUILD.gn
index de78c61..b3407de0 100644
--- a/headless/BUILD.gn
+++ b/headless/BUILD.gn
@@ -146,7 +146,6 @@
   "network",
   "page",
   "profiler",
-  "rendering",
   "runtime",
   "security",
   "service_worker",
diff --git a/headless/lib/browser/headless_devtools_client_impl.cc b/headless/lib/browser/headless_devtools_client_impl.cc
index 9cb9de77..0b6cc969 100644
--- a/headless/lib/browser/headless_devtools_client_impl.cc
+++ b/headless/lib/browser/headless_devtools_client_impl.cc
@@ -59,7 +59,6 @@
       network_domain_(this),
       page_domain_(this),
       profiler_domain_(this),
-      rendering_domain_(this),
       runtime_domain_(this),
       security_domain_(this),
       service_worker_domain_(this),
@@ -329,10 +328,6 @@
   return &profiler_domain_;
 }
 
-rendering::Domain* HeadlessDevToolsClientImpl::GetRendering() {
-  return &rendering_domain_;
-}
-
 runtime::Domain* HeadlessDevToolsClientImpl::GetRuntime() {
   return &runtime_domain_;
 }
diff --git a/headless/lib/browser/headless_devtools_client_impl.h b/headless/lib/browser/headless_devtools_client_impl.h
index 1b17967..3accb43 100644
--- a/headless/lib/browser/headless_devtools_client_impl.h
+++ b/headless/lib/browser/headless_devtools_client_impl.h
@@ -34,7 +34,6 @@
 #include "headless/public/devtools/domains/network.h"
 #include "headless/public/devtools/domains/page.h"
 #include "headless/public/devtools/domains/profiler.h"
-#include "headless/public/devtools/domains/rendering.h"
 #include "headless/public/devtools/domains/runtime.h"
 #include "headless/public/devtools/domains/security.h"
 #include "headless/public/devtools/domains/service_worker.h"
@@ -87,7 +86,6 @@
   network::Domain* GetNetwork() override;
   page::Domain* GetPage() override;
   profiler::Domain* GetProfiler() override;
-  rendering::Domain* GetRendering() override;
   runtime::Domain* GetRuntime() override;
   security::Domain* GetSecurity() override;
   service_worker::Domain* GetServiceWorker() override;
@@ -190,7 +188,6 @@
   network::ExperimentalDomain network_domain_;
   page::ExperimentalDomain page_domain_;
   profiler::ExperimentalDomain profiler_domain_;
-  rendering::ExperimentalDomain rendering_domain_;
   runtime::ExperimentalDomain runtime_domain_;
   security::ExperimentalDomain security_domain_;
   service_worker::ExperimentalDomain service_worker_domain_;
diff --git a/headless/public/headless_devtools_client.h b/headless/public/headless_devtools_client.h
index 7e7d979..b3cea9482 100644
--- a/headless/public/headless_devtools_client.h
+++ b/headless/public/headless_devtools_client.h
@@ -84,9 +84,6 @@
 namespace profiler {
 class Domain;
 }
-namespace rendering {
-class Domain;
-}
 namespace runtime {
 class Domain;
 }
@@ -139,7 +136,6 @@
   virtual network::Domain* GetNetwork() = 0;
   virtual page::Domain* GetPage() = 0;
   virtual profiler::Domain* GetProfiler() = 0;
-  virtual rendering::Domain* GetRendering() = 0;
   virtual runtime::Domain* GetRuntime() = 0;
   virtual security::Domain* GetSecurity() = 0;
   virtual service_worker::Domain* GetServiceWorker() = 0;
diff --git a/ios/chrome/browser/ui/fullscreen_egtest.mm b/ios/chrome/browser/ui/fullscreen_egtest.mm
index 550cc49..62adad1 100644
--- a/ios/chrome/browser/ui/fullscreen_egtest.mm
+++ b/ios/chrome/browser/ui/fullscreen_egtest.mm
@@ -139,6 +139,11 @@
 // Verifies that the toolbar properly appears/disappears when scrolling up/down
 // on a PDF that is long in length and wide in width.
 - (void)testLongPDFScroll {
+// TODO(crbug.com/714329): Re-enable this test on devices.
+#if !TARGET_IPHONE_SIMULATOR
+  EARL_GREY_TEST_DISABLED(@"Test disabled on device.");
+#endif
+
   web::test::SetUpFileBasedHttpServer();
   GURL URL = web::test::HttpServer::MakeUrl(
       "http://ios/testing/data/http_server_files/two_pages.pdf");
diff --git a/ios/chrome/browser/web/browsing_egtest.mm b/ios/chrome/browser/web/browsing_egtest.mm
index 7e4817a..dbf7d60 100644
--- a/ios/chrome/browser/web/browsing_egtest.mm
+++ b/ios/chrome/browser/web/browsing_egtest.mm
@@ -441,6 +441,11 @@
 // does not change the page and that the back button works as expected
 // afterwards.
 - (void)testBrowsingPostToSamePage {
+// TODO(crbug.com/714303): Re-enable this test on devices.
+#if !TARGET_IPHONE_SIMULATOR
+  EARL_GREY_TEST_DISABLED(@"Test disabled on device.");
+#endif
+
   // Create map of canned responses and set up the test HTML server.
   std::map<GURL, std::string> responses;
   const GURL firstURL = web::test::HttpServer::MakeUrl("http://first");
@@ -606,6 +611,11 @@
 // afterwards.
 // TODO(crbug.com/711108): Move test to forms_egtest.mm.
 - (void)testBrowsingPostEntryWithKeyboard {
+// TODO(crbug.com/704618): Re-enable this test on devices.
+#if !TARGET_IPHONE_SIMULATOR
+  EARL_GREY_TEST_DISABLED(@"Test disabled on device.");
+#endif
+
   // Create map of canned responses and set up the test HTML server.
   std::map<GURL, std::string> responses;
   const GURL URL =
diff --git a/mash/browser/browser.cc b/mash/browser/browser.cc
index a88fbd2..ecc5f9f2f 100644
--- a/mash/browser/browser.cc
+++ b/mash/browser/browser.cc
@@ -438,12 +438,11 @@
         model_.get(),
         base::Bind(&NavButton::OnMenuClosed, base::Unretained(this))));
     menu_model_adapter_->set_triggerable_event_flags(triggerable_event_flags());
-    menu_runner_.reset(new views::MenuRunner(
-        menu_model_adapter_->CreateMenu(),
-        views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::ASYNC));
-    ignore_result(menu_runner_->RunMenuAt(
-        GetWidget(), nullptr, gfx::Rect(menu_position, gfx::Size(0, 0)),
-        views::MENU_ANCHOR_TOPLEFT, source_type));
+    menu_runner_.reset(new views::MenuRunner(menu_model_adapter_->CreateMenu(),
+                                             views::MenuRunner::HAS_MNEMONICS));
+    menu_runner_->RunMenuAt(GetWidget(), nullptr,
+                            gfx::Rect(menu_position, gfx::Size(0, 0)),
+                            views::MENU_ANCHOR_TOPLEFT, source_type);
   }
 
   void OnMenuClosed() {
diff --git a/mash/example/window_type_launcher/window_type_launcher.cc b/mash/example/window_type_launcher/window_type_launcher.cc
index a33422e..847b625 100644
--- a/mash/example/window_type_launcher/window_type_launcher.cc
+++ b/mash/example/window_type_launcher/window_type_launcher.cc
@@ -404,8 +404,7 @@
                          MenuItemView::NORMAL);
     // MenuRunner takes ownership of root.
     menu_runner_.reset(new MenuRunner(
-        root, MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU |
-                  views::MenuRunner::ASYNC));
+        root, MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU));
     menu_runner_->RunMenuAt(GetWidget(), NULL, gfx::Rect(point, gfx::Size()),
                             views::MENU_ANCHOR_TOPLEFT, source_type);
   }
diff --git a/media/base/content_decryption_module.h b/media/base/content_decryption_module.h
index e8b3665..df06c712 100644
--- a/media/base/content_decryption_module.h
+++ b/media/base/content_decryption_module.h
@@ -189,6 +189,9 @@
 
 // Called when the CDM changes the expiration time of a session.
 // See http://w3c.github.io/encrypted-media/#update-expiration
+// A null base::Time() will be translated to NaN in Javascript, which means "no
+// such time exists or if the license explicitly never expires, as determined
+// by the CDM", according to the EME spec.
 typedef base::Callback<void(const std::string& session_id,
                             base::Time new_expiry_time)>
     SessionExpirationUpdateCB;
diff --git a/media/filters/video_cadence_estimator.cc b/media/filters/video_cadence_estimator.cc
index 7548b82..ee8916e 100644
--- a/media/filters/video_cadence_estimator.cc
+++ b/media/filters/video_cadence_estimator.cc
@@ -189,6 +189,8 @@
   // within minimum_time_until_max_drift.
   if (max_acceptable_drift >= minimum_time_until_max_drift_) {
     int cadence_value = round(perfect_cadence);
+    if (cadence_value < 0)
+      return Cadence();
     if (cadence_value == 0)
       cadence_value = 1;
     Cadence result = ConstructCadence(cadence_value, 1);
diff --git a/net/base/network_delegate_impl.h b/net/base/network_delegate_impl.h
index 8acdd72..a46942a 100644
--- a/net/base/network_delegate_impl.h
+++ b/net/base/network_delegate_impl.h
@@ -33,65 +33,22 @@
   ~NetworkDelegateImpl() override {}
 
  private:
-  // This is the interface for subclasses of NetworkDelegate to implement. These
-  // member functions will be called by the respective public notification
-  // member function, which will perform basic sanity checking.
-
-  // Called before a request is sent. Allows the delegate to rewrite the URL
-  // being fetched by modifying |new_url|. If set, the URL must be valid. The
-  // reference fragment from the original URL is not automatically appended to
-  // |new_url|; callers are responsible for copying the reference fragment if
-  // desired.
-  // |callback| and |new_url| are valid only until OnURLRequestDestroyed is
-  // called for this request. Returns a net status code, generally either OK to
-  // continue with the request or ERR_IO_PENDING if the result is not ready yet.
-  // A status code other than OK and ERR_IO_PENDING will cancel the request and
-  // report the status code as the reason.
-  //
-  // The default implementation returns OK (continue with request).
   int OnBeforeURLRequest(URLRequest* request,
                          const CompletionCallback& callback,
                          GURL* new_url) override;
 
-  // Called right before the network transaction starts. Allows the delegate to
-  // read/write |headers| before they get sent out. |callback| and |headers| are
-  // valid only until OnCompleted or OnURLRequestDestroyed is called for this
-  // request.
-  // See OnBeforeURLRequest for return value description. Returns OK by default.
   int OnBeforeStartTransaction(URLRequest* request,
                                const CompletionCallback& callback,
                                HttpRequestHeaders* headers) override;
 
-  // Called after a connection is established , and just before headers are sent
-  // to the destination server (i.e., not called for HTTP CONNECT requests). For
-  // non-tunneled requests using HTTP proxies, |headers| will include any
-  // proxy-specific headers as well. Allows the delegate to read/write |headers|
-  // before they get sent out. |headers| is valid only until OnCompleted or
-  // OnURLRequestDestroyed is called for this request.
   void OnBeforeSendHeaders(URLRequest* request,
                            const ProxyInfo& proxy_info,
                            const ProxyRetryInfoMap& proxy_retry_info,
                            HttpRequestHeaders* headers) override;
 
-  // Called right before the HTTP request(s) are being sent to the network.
-  // |headers| is only valid until OnCompleted or OnURLRequestDestroyed is
-  // called for this request.
   void OnStartTransaction(URLRequest* request,
                           const HttpRequestHeaders& headers) override;
 
-  // Called for HTTP requests when the headers have been received.
-  // |original_response_headers| contains the headers as received over the
-  // network, these must not be modified. |override_response_headers| can be set
-  // to new values, that should be considered as overriding
-  // |original_response_headers|.
-  // If the response is a redirect, and the Location response header value is
-  // identical to |allowed_unsafe_redirect_url|, then the redirect is never
-  // blocked and the reference fragment is not copied from the original URL
-  // to the redirection target.
-  //
-  // |callback|, |original_response_headers|, and |override_response_headers|
-  // are only valid until OnURLRequestDestroyed is called for this request.
-  // See OnBeforeURLRequest for return value description. Returns OK by default.
   int OnHeadersReceived(
       URLRequest* request,
       const CompletionCallback& callback,
@@ -99,109 +56,44 @@
       scoped_refptr<HttpResponseHeaders>* override_response_headers,
       GURL* allowed_unsafe_redirect_url) override;
 
-  // Called right after a redirect response code was received.
-  // |new_location| is only valid until OnURLRequestDestroyed is called for this
-  // request.
   void OnBeforeRedirect(URLRequest* request, const GURL& new_location) override;
 
-  // This corresponds to URLRequestDelegate::OnResponseStarted.
   void OnResponseStarted(URLRequest* request, int net_error) override;
-  // Deprecated.
-  // TODO(maksims): Remove this;
   void OnResponseStarted(URLRequest* request) override;
 
-  // Called when bytes are received from the network, such as after receiving
-  // headers or reading raw response bytes. This includes localhost requests.
-  // |bytes_received| is the number of bytes measured at the application layer
-  // that have been received over the network for this request since the last
-  // time OnNetworkBytesReceived was called. |bytes_received| will always be
-  // greater than 0.
-  // Currently, this is only implemented for HTTP transactions, and
-  // |bytes_received| does not include TLS overhead or TCP retransmits.
   void OnNetworkBytesReceived(URLRequest* request,
                               int64_t bytes_received) override;
 
-  // Called when bytes are sent over the network, such as when sending request
-  // headers or uploading request body bytes. This includes localhost requests.
-  // |bytes_sent| is the number of bytes measured at the application layer that
-  // have been sent over the network for this request since the last time
-  // OnNetworkBytesSent was called. |bytes_sent| will always be greater than 0.
-  // Currently, this is only implemented for HTTP transactions, and |bytes_sent|
-  // does not include TLS overhead or TCP retransmits.
   void OnNetworkBytesSent(URLRequest* request, int64_t bytes_sent) override;
 
-  // Indicates that the URL request has been completed or failed.
-  // |started| indicates whether the request has been started. If false,
-  // some information like the socket address is not available.
   void OnCompleted(URLRequest* request, bool started, int net_error) override;
-  // Deprecated.
-  // TODO(maksims): Remove this;
   void OnCompleted(URLRequest* request, bool started) override;
 
-  // Called when an URLRequest is being destroyed. Note that the request is
-  // being deleted, so it's not safe to call any methods that may result in
-  // a virtual method call.
   void OnURLRequestDestroyed(URLRequest* request) override;
 
-  // Corresponds to ProxyResolverJSBindings::OnError.
   void OnPACScriptError(int line_number, const base::string16& error) override;
 
-  // Called when a request receives an authentication challenge
-  // specified by |auth_info|, and is unable to respond using cached
-  // credentials. |callback| and |credentials| must be non-NULL, and must
-  // be valid until OnURLRequestDestroyed is called for |request|.
-  //
-  // The following return values are allowed:
-  //  - AUTH_REQUIRED_RESPONSE_NO_ACTION: |auth_info| is observed, but
-  //    no action is being taken on it.
-  //  - AUTH_REQUIRED_RESPONSE_SET_AUTH: |credentials| is filled in with
-  //    a username and password, which should be used in a response to
-  //    |auth_info|.
-  //  - AUTH_REQUIRED_RESPONSE_CANCEL_AUTH: The authentication challenge
-  //    should not be attempted.
-  //  - AUTH_REQUIRED_RESPONSE_IO_PENDING: The action will be decided
-  //    asynchronously. |callback| will be invoked when the decision is made,
-  //    and one of the other AuthRequiredResponse values will be passed in with
-  //    the same semantics as described above.
   AuthRequiredResponse OnAuthRequired(URLRequest* request,
                                       const AuthChallengeInfo& auth_info,
                                       const AuthCallback& callback,
                                       AuthCredentials* credentials) override;
 
-  // Called when reading cookies to allow the network delegate to block access
-  // to the cookie. This method will never be invoked when
-  // LOAD_DO_NOT_SEND_COOKIES is specified.
   bool OnCanGetCookies(const URLRequest& request,
                        const CookieList& cookie_list) override;
 
-  // Called when a cookie is set to allow the network delegate to block access
-  // to the cookie. This method will never be invoked when
-  // LOAD_DO_NOT_SAVE_COOKIES is specified.
   bool OnCanSetCookie(const URLRequest& request,
                       const std::string& cookie_line,
                       CookieOptions* options) override;
 
-  // Called when a file access is attempted to allow the network delegate to
-  // allow or block access to the given file path.  Returns true if access is
-  // allowed.
   bool OnCanAccessFile(const URLRequest& request,
                        const base::FilePath& path) const override;
 
-  // Returns true if the given |url| has to be requested over connection that
-  // is not tracked by the server. Usually is false, unless user privacy
-  // settings block cookies from being get or set.
   bool OnCanEnablePrivacyMode(
       const GURL& url,
       const GURL& first_party_for_cookies) const override;
 
-  // Returns true if the embedder has enabled experimental cookie features.
   bool OnAreExperimentalCookieFeaturesEnabled() const override;
 
-  // Called when the |referrer_url| for requesting |target_url| during handling
-  // of the |request| is does not comply with the referrer policy (e.g. a
-  // secure referrer for an insecure initial target).
-  // Returns true if the request should be cancelled. Otherwise, the referrer
-  // header is stripped from the request.
   bool OnCancelURLRequestWithPolicyViolatingReferrerHeader(
       const URLRequest& request,
       const GURL& target_url,
diff --git a/net/cert_net/nss_ocsp.cc b/net/cert_net/nss_ocsp.cc
index 19fb32d..6d78a604 100644
--- a/net/cert_net/nss_ocsp.cc
+++ b/net/cert_net/nss_ocsp.cc
@@ -728,87 +728,9 @@
     return SECFailure;
   }
 
-  const base::Time start_time = base::Time::Now();
-  bool request_ok = true;
   req->Start();
   if (!req->Wait() || req->http_response_code() == static_cast<PRUint16>(-1)) {
     // If the response code is -1, the request failed and there is no response.
-    request_ok = false;
-  }
-  const base::TimeDelta duration = base::Time::Now() - start_time;
-
-  // For metrics, we want to know if the request was 'successful' or not.
-  // |request_ok| determines if we'll pass the response back to NSS and |ok|
-  // keep track of if we think the response was good.
-  bool ok = true;
-  if (!request_ok ||
-      (req->http_response_code() >= 400 && req->http_response_code() < 600) ||
-      req->http_response_data().size() == 0 ||
-      // 0x30 is the ASN.1 DER encoding of a SEQUENCE. All valid OCSP/CRL/CRT
-      // responses must start with this. If we didn't check for this then a
-      // captive portal could provide an HTML reply that we would count as a
-      // 'success' (although it wouldn't count in NSS, of course).
-      req->http_response_data().data()[0] != 0x30) {
-    ok = false;
-  }
-
-  // We want to know if this was:
-  //   1) An OCSP request
-  //   2) A CRL request
-  //   3) A request for a missing intermediate certificate
-  // There's no sure way to do this, so we use heuristics like MIME type and
-  // URL.
-  const char* mime_type = "";
-  if (ok)
-    mime_type = req->http_response_content_type().c_str();
-  bool is_ocsp =
-      strcasecmp(mime_type, "application/ocsp-response") == 0;
-  bool is_crl = strcasecmp(mime_type, "application/x-pkcs7-crl") == 0 ||
-                strcasecmp(mime_type, "application/x-x509-crl") == 0 ||
-                strcasecmp(mime_type, "application/pkix-crl") == 0;
-  bool is_cert =
-      strcasecmp(mime_type, "application/x-x509-ca-cert") == 0 ||
-      strcasecmp(mime_type, "application/x-x509-server-cert") == 0 ||
-      strcasecmp(mime_type, "application/pkix-cert") == 0 ||
-      strcasecmp(mime_type, "application/pkcs7-mime") == 0;
-
-  if (!is_cert && !is_crl && !is_ocsp) {
-    // We didn't get a hint from the MIME type, so do the best that we can.
-    const std::string path = req->url().path();
-    const std::string host = req->url().host();
-    is_crl = strcasestr(path.c_str(), ".crl") != NULL;
-    is_cert = strcasestr(path.c_str(), ".crt") != NULL ||
-              strcasestr(path.c_str(), ".p7c") != NULL ||
-              strcasestr(path.c_str(), ".cer") != NULL;
-    is_ocsp = strcasestr(host.c_str(), "ocsp") != NULL ||
-              req->http_request_method() == "POST";
-  }
-
-  if (is_ocsp) {
-    if (ok) {
-      UMA_HISTOGRAM_TIMES("Net.OCSPRequestTimeMs", duration);
-      UMA_HISTOGRAM_BOOLEAN("Net.OCSPRequestSuccess", true);
-    } else {
-      UMA_HISTOGRAM_TIMES("Net.OCSPRequestFailedTimeMs", duration);
-      UMA_HISTOGRAM_BOOLEAN("Net.OCSPRequestSuccess", false);
-    }
-  } else if (is_crl) {
-    if (ok) {
-      UMA_HISTOGRAM_TIMES("Net.CRLRequestTimeMs", duration);
-      UMA_HISTOGRAM_BOOLEAN("Net.CRLRequestSuccess", true);
-    } else {
-      UMA_HISTOGRAM_TIMES("Net.CRLRequestFailedTimeMs", duration);
-      UMA_HISTOGRAM_BOOLEAN("Net.CRLRequestSuccess", false);
-    }
-  } else if (is_cert) {
-    if (ok)
-      UMA_HISTOGRAM_TIMES("Net.CRTRequestTimeMs", duration);
-  } else {
-    if (ok)
-      UMA_HISTOGRAM_TIMES("Net.UnknownTypeRequestTimeMs", duration);
-  }
-
-  if (!request_ok) {
     PORT_SetError(SEC_ERROR_BAD_HTTP_RESPONSE);  // Simple approximation.
     return SECFailure;
   }
diff --git a/remoting/client/ios/session/BUILD.gn b/remoting/client/ios/session/BUILD.gn
index 843a023b..e927ffb 100644
--- a/remoting/client/ios/session/BUILD.gn
+++ b/remoting/client/ios/session/BUILD.gn
@@ -17,7 +17,9 @@
   deps = [
     "//base",
     "//remoting/client",
+    "//remoting/client/ios/display",
     "//remoting/client/ios/domain",
+    "//remoting/protocol",
   ]
 
   configs += [ "//build/config/compiler:enable_arc" ]
diff --git a/remoting/client/ios/session/remoting_client.h b/remoting/client/ios/session/remoting_client.h
index cbaa425d..c6bed380 100644
--- a/remoting/client/ios/session/remoting_client.h
+++ b/remoting/client/ios/session/remoting_client.h
@@ -5,14 +5,25 @@
 #ifndef REMOTING_CLIENT_IOS_SESSION_REMOTING_CLIENT_H_
 #define REMOTING_CLIENT_IOS_SESSION_REMOTING_CLIENT_H_
 
+#import <Foundation/Foundation.h>
+
+#import "remoting/client/ios/display/gl_display_handler.h"
+
+#include "remoting/protocol/connection_to_host.h"
+#include "remoting/protocol/session.h"
+
+namespace remoting {
+struct ConnectToHostInfo;
+}
+
 @interface RemotingClient : NSObject
 
 - (void)connectToHost:(const remoting::ConnectToHostInfo&)info;
 
 // Mirrors the native client session delegate interface:
 
-- (void)onConnectionState:(protocol::ConnectionToHost::State)state
-                    error:(protocol::ErrorCode)error;
+- (void)onConnectionState:(remoting::protocol::ConnectionToHost::State)state
+                    error:(remoting::protocol::ErrorCode)error;
 
 - (void)commitPairingCredentialsForHost:(NSString*)host
                                      id:(NSString*)id
@@ -26,6 +37,8 @@
 
 - (void)handleExtensionMessageOfType:(NSString*)type message:(NSString*)message;
 
+@property(nonatomic, strong) GlDisplayHandler* displayHandler;
+
 @end
 
 #endif  // REMOTING_CLIENT_IOS_SESSION_REMOTING_CLIENT_H_
diff --git a/remoting/client/ios/session/remoting_client.mm b/remoting/client/ios/session/remoting_client.mm
index 44b3b015..be20f85b 100644
--- a/remoting/client/ios/session/remoting_client.mm
+++ b/remoting/client/ios/session/remoting_client.mm
@@ -6,15 +6,17 @@
 #error "This file requires ARC support."
 #endif
 
-#import "remoting/client/ios/session/client.h"
+#import "remoting/client/ios/session/remoting_client.h"
 
 #import "base/mac/bind_objc_block.h"
 
 #include "remoting/client/chromoting_client_runtime.h"
+#include "remoting/client/chromoting_session.h"
 #include "remoting/client/connect_to_host_info.h"
+#include "remoting/client/ios/session/remoting_client_session_delegate.h"
+#include "remoting/protocol/video_renderer.h"
 
 @interface RemotingClient () {
-  GlDisplayHandler* _displayHandler;
   remoting::ChromotingClientRuntime* _runtime;
   std::unique_ptr<remoting::ChromotingSession> _session;
   remoting::RemotingClientSessonDelegate* _sessonDelegate;
@@ -23,64 +25,76 @@
 
 @implementation RemotingClient
 
+@synthesize displayHandler = _displayHandler;
+
 - (instancetype)init {
   self = [super init];
   if (self) {
-    _runtime = ChromotingClientRuntime::GetInstance();
+    _runtime = remoting::ChromotingClientRuntime::GetInstance();
     _sessonDelegate = new remoting::RemotingClientSessonDelegate(self);
   }
   return self;
 }
 
 - (void)connectToHost:(const remoting::ConnectToHostInfo&)info {
-  _displayHandler = [[GlDisplayHandler alloc] initWithRuntime:_runtime];
+  remoting::ConnectToHostInfo hostInfo(info);
 
-  protocol::ClientAuthenticationConfig client_auth_config;
+  remoting::protocol::ClientAuthenticationConfig client_auth_config;
   client_auth_config.host_id = info.host_id;
   client_auth_config.pairing_client_id = info.pairing_id;
   client_auth_config.pairing_secret = info.pairing_secret;
   client_auth_config.fetch_secret_callback = base::BindBlockArc(
-      ^(bool pairing_supported,
-        const SecretFetchedCallback& secret_fetched_callback) {
+      ^(bool pairing_supported, const remoting::protocol::SecretFetchedCallback&
+                                    secret_fetched_callback) {
         NSLog(@"TODO(nicholss): Implement the FetchSecretCallback.");
+        // TODO(nicholss): For now we pass back a junk number.
+        secret_fetched_callback.Run("000000");
       });
 
   // TODO(nicholss): Add audio support to iOS.
-  base::WeakPtr<protocol::AudioStub> audioPlayer = nullptr;
+  base::WeakPtr<remoting::protocol::AudioStub> audioPlayer = nullptr;
 
-  _session.reset(new remoting::ChromotingSession(
-      _sessonDelegate->GetWeakPtr(), [_displayHandler CreateCursorShapeStub],
-      [_displayHandler CreateVideoRenderer], audioPlayer, info,
-      client_auth_config));
-  _session->Connect();
+  _displayHandler = [[GlDisplayHandler alloc] init];
+
+  _runtime->ui_task_runner()->PostTask(
+      FROM_HERE, base::BindBlockArc(^{
+        _session.reset(new remoting::ChromotingSession(
+            _sessonDelegate->GetWeakPtr(),
+            [_displayHandler CreateCursorShapeStub],
+            [_displayHandler CreateVideoRenderer], audioPlayer, hostInfo,
+            client_auth_config));
+        _session->Connect();
+      }));
 }
 
 #pragma mark - ChromotingSession::Delegate
 
-- (void)onConnectionState:(protocol::ConnectionToHost::State)state
-                    error:(protocol::ErrorCode)error {
-  NSLog(@"TODO(nicholss): implement this.");
+- (void)onConnectionState:(remoting::protocol::ConnectionToHost::State)state
+                    error:(remoting::protocol::ErrorCode)error {
+  NSLog(@"TODO(nicholss): implement this, onConnectionState: %d %d.", state,
+        error);
 }
 
 - (void)commitPairingCredentialsForHost:(NSString*)host
                                      id:(NSString*)id
                                  secret:(NSString*)secret {
-  NSLog(@"TODO(nicholss): implement this.");
+  NSLog(@"TODO(nicholss): implement this, commitPairingCredentialsForHost.");
 }
 
 - (void)fetchThirdPartyTokenForUrl:(NSString*)tokenUrl
                           clientId:(NSString*)clientId
                              scope:(NSString*)scope {
-  NSLog(@"TODO(nicholss): implement this.");
+  NSLog(@"TODO(nicholss): implement this, fetchThirdPartyTokenForUrl.");
 }
 
 - (void)setCapabilities:(NSString*)capabilities {
-  NSLog(@"TODO(nicholss): implement this.");
+  NSLog(@"TODO(nicholss): implement this, setCapabilities.");
 }
 
 - (void)handleExtensionMessageOfType:(NSString*)type
                              message:(NSString*)message {
-  NSLog(@"TODO(nicholss): implement this.");
+  NSLog(@"TODO(nicholss): implement this, handleExtensionMessageOfType %@:%@.",
+        type, message);
 }
 
 @end
diff --git a/remoting/client/ios/session/remoting_client_session_delegate.h b/remoting/client/ios/session/remoting_client_session_delegate.h
index f9e8200c..ab0b9ef 100644
--- a/remoting/client/ios/session/remoting_client_session_delegate.h
+++ b/remoting/client/ios/session/remoting_client_session_delegate.h
@@ -10,13 +10,15 @@
 #include "remoting/client/chromoting_session.h"
 #include "remoting/protocol/connection_to_host.h"
 
+@class RemotingClient;
+
 namespace remoting {
 
 class ChromotingClientRuntime;
 
 class RemotingClientSessonDelegate : public ChromotingSession::Delegate {
  public:
-  RemotingClientSessonDelegate();
+  RemotingClientSessonDelegate(RemotingClient* client);
   ~RemotingClientSessonDelegate() override;
 
   // ChromotingSession::Delegate implementation
@@ -42,7 +44,7 @@
   base::WeakPtrFactory<RemotingClientSessonDelegate> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(RemotingClientSessonDelegate);
-}
+};
 
 }  // namespace remoting
 
diff --git a/remoting/client/ios/session/remoting_client_session_delegate.mm b/remoting/client/ios/session/remoting_client_session_delegate.mm
index 97401ba..ffcf1329 100644
--- a/remoting/client/ios/session/remoting_client_session_delegate.mm
+++ b/remoting/client/ios/session/remoting_client_session_delegate.mm
@@ -6,7 +6,9 @@
 #error "This file requires ARC support."
 #endif
 
-#import "remoting/client/ios/session/remoting_client_session_delegate.h"
+#include "remoting/client/ios/session/remoting_client_session_delegate.h"
+
+#import "remoting/client/ios/session/remoting_client.h"
 
 #include "base/strings/sys_string_conversions.h"
 #include "remoting/client/chromoting_client_runtime.h"
diff --git a/third_party/WebKit/LayoutTests/fast/dom/HTMLAnchorElement/anchor-no-multiple-windows.html b/third_party/WebKit/LayoutTests/fast/dom/HTMLAnchorElement/anchor-no-multiple-windows.html
index 6e89d69..b380d36 100644
--- a/third_party/WebKit/LayoutTests/fast/dom/HTMLAnchorElement/anchor-no-multiple-windows.html
+++ b/third_party/WebKit/LayoutTests/fast/dom/HTMLAnchorElement/anchor-no-multiple-windows.html
@@ -36,6 +36,6 @@
 </head>
 <body>
 You need popups to be enabled to run this test.
-<a id="link" target="_blank" href="data:text/html;charset=utf-8,<html><body>The test passes if this page opens in the same window</body></html>">Click me!</a>
+<a id="link" target="_blank" href="resources/popup.html">Click me!</a>
 </body>
 </html>
diff --git a/third_party/WebKit/LayoutTests/fast/dom/HTMLAnchorElement/resources/popup.html b/third_party/WebKit/LayoutTests/fast/dom/HTMLAnchorElement/resources/popup.html
new file mode 100644
index 0000000..f84f0a2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/dom/HTMLAnchorElement/resources/popup.html
@@ -0,0 +1 @@
+<html><body>The test passes if this page opens in the same window</body></html>
diff --git a/third_party/WebKit/LayoutTests/fast/dom/location-new-window-no-crash-expected.txt b/third_party/WebKit/LayoutTests/fast/dom/location-new-window-no-crash-expected.txt
index 0eeb73207..11b9aad 100644
--- a/third_party/WebKit/LayoutTests/fast/dom/location-new-window-no-crash-expected.txt
+++ b/third_party/WebKit/LayoutTests/fast/dom/location-new-window-no-crash-expected.txt
@@ -1,3 +1,4 @@
+CONSOLE ERROR: line 15: Not allowed to navigate top frame to data URL: data:text/plain,a
 Tests that manipulating location properties in a just-created window object does not crash. Note: Turn off pop-up blocking to run this in-browser.
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
diff --git a/third_party/WebKit/LayoutTests/fast/forms/date-multiple-fields/date-multiple-fields-clearbutton-visibility-after-restore.html b/third_party/WebKit/LayoutTests/fast/forms/date-multiple-fields/date-multiple-fields-clearbutton-visibility-after-restore.html
index 406d4c64..349786e5 100644
--- a/third_party/WebKit/LayoutTests/fast/forms/date-multiple-fields/date-multiple-fields-clearbutton-visibility-after-restore.html
+++ b/third_party/WebKit/LayoutTests/fast/forms/date-multiple-fields/date-multiple-fields-clearbutton-visibility-after-restore.html
@@ -2,7 +2,7 @@
 <html>
 <body>
 <input id="emptyOnFirstVisit">
-<form action="data:text/html,&lt;script>history.back()&lt;/script>" id=form1>
+<form action="../../../resources/back.html" id=form1>
     <input type=date id=input1>
     <input type=date id=input2>
     <input type=date id=input3 value="2012-01-03">
diff --git a/third_party/WebKit/LayoutTests/fast/forms/datetimelocal-multiple-fields/datetimelocal-multiple-fields-clearbutton-visibility-after-restore.html b/third_party/WebKit/LayoutTests/fast/forms/datetimelocal-multiple-fields/datetimelocal-multiple-fields-clearbutton-visibility-after-restore.html
index 3bae7d0..8d0d421 100644
--- a/third_party/WebKit/LayoutTests/fast/forms/datetimelocal-multiple-fields/datetimelocal-multiple-fields-clearbutton-visibility-after-restore.html
+++ b/third_party/WebKit/LayoutTests/fast/forms/datetimelocal-multiple-fields/datetimelocal-multiple-fields-clearbutton-visibility-after-restore.html
@@ -2,7 +2,7 @@
 <html>
 <body>
 <input id="emptyOnFirstVisit">
-<form action="data:text/html,&lt;script>history.back()&lt;/script>" id=form1>
+<form action="../../../resources/back.html" id=form1>
     <input type="datetime-local" id=input1>
     <input type="datetime-local" id=input2>
     <input type="datetime-local" id=input3 value="2001-01-01T01:01:01.001">
diff --git a/third_party/WebKit/LayoutTests/fast/forms/month-multiple-fields/month-multiple-fields-clearbutton-visibility-after-restore.html b/third_party/WebKit/LayoutTests/fast/forms/month-multiple-fields/month-multiple-fields-clearbutton-visibility-after-restore.html
index 9e6f513..d566c65 100644
--- a/third_party/WebKit/LayoutTests/fast/forms/month-multiple-fields/month-multiple-fields-clearbutton-visibility-after-restore.html
+++ b/third_party/WebKit/LayoutTests/fast/forms/month-multiple-fields/month-multiple-fields-clearbutton-visibility-after-restore.html
@@ -2,7 +2,7 @@
 <html>
 <body>
 <input id="emptyOnFirstVisit">
-<form action="data:text/html,&lt;script>history.back()&lt;/script>" id=form1>
+<form action="../../../resources/back.html" id=form1>
     <input type=month id=input1>
     <input type=month id=input2>
     <input type=month id=input3 value="2012-01">
diff --git a/third_party/WebKit/LayoutTests/fast/forms/time-multiple-fields/time-multiple-fields-clearbutton-visibility-after-restore.html b/third_party/WebKit/LayoutTests/fast/forms/time-multiple-fields/time-multiple-fields-clearbutton-visibility-after-restore.html
index 4b3c4ee..c224b594 100644
--- a/third_party/WebKit/LayoutTests/fast/forms/time-multiple-fields/time-multiple-fields-clearbutton-visibility-after-restore.html
+++ b/third_party/WebKit/LayoutTests/fast/forms/time-multiple-fields/time-multiple-fields-clearbutton-visibility-after-restore.html
@@ -2,7 +2,7 @@
 <html>
 <body>
 <input id="emptyOnFirstVisit">
-<form action="data:text/html,&lt;script>history.back()&lt;/script>" id=form1>
+<form action="../../../resources/back.html" id=form1>
     <input type=time id=input1>
     <input type=time id=input2>
     <input type=time id=input3 value="01:01:01.001">
diff --git a/third_party/WebKit/LayoutTests/fast/forms/week-multiple-fields/week-multiple-fields-clearbutton-visibility-after-restore.html b/third_party/WebKit/LayoutTests/fast/forms/week-multiple-fields/week-multiple-fields-clearbutton-visibility-after-restore.html
index b147c9a..6df4ff5f 100644
--- a/third_party/WebKit/LayoutTests/fast/forms/week-multiple-fields/week-multiple-fields-clearbutton-visibility-after-restore.html
+++ b/third_party/WebKit/LayoutTests/fast/forms/week-multiple-fields/week-multiple-fields-clearbutton-visibility-after-restore.html
@@ -2,7 +2,7 @@
 <html>
 <body>
 <input id="emptyOnFirstVisit">
-<form action="data:text/html,&lt;script>history.back()&lt;/script>" id=form1>
+<form action="../../../resources/back.html" id=form1>
     <input type=week id=input1>
     <input type=week id=input2>
     <input type=week id=input3 value="2012-W01">
diff --git a/third_party/WebKit/LayoutTests/fast/history/scroll-restoration/scroll-restoration-scale-not-impacted.html b/third_party/WebKit/LayoutTests/fast/history/scroll-restoration/scroll-restoration-scale-not-impacted.html
index f854189..0f91a3bc 100644
--- a/third_party/WebKit/LayoutTests/fast/history/scroll-restoration/scroll-restoration-scale-not-impacted.html
+++ b/third_party/WebKit/LayoutTests/fast/history/scroll-restoration/scroll-restoration-scale-not-impacted.html
@@ -29,7 +29,7 @@
 
         setTimeout(function() {
           window.name = 'verification';
-          window.location = "data:text/html,<script>history.back();</scr" + "ipt>";
+          window.location = "../../../resources/back.html";
         }, 0);
       } else {
         assert_array_equals([internals.visualViewportWidth(), internals.visualViewportHeight()], [400, 300], 'page scale is restored');
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector/debugger-test.js b/third_party/WebKit/LayoutTests/http/tests/inspector/debugger-test.js
index e3f5b2e..4dd6334f 100644
--- a/third_party/WebKit/LayoutTests/http/tests/inspector/debugger-test.js
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector/debugger-test.js
@@ -731,4 +731,17 @@
     }
 }
 
+InspectorTest.setEventListenerBreakpoint = function(id, enabled, targetName)
+{
+    var pane = self.runtime.sharedInstance(Sources.EventListenerBreakpointsSidebarPane);
+    var auxData = {'eventName': id};
+    if (targetName)
+        auxData.targetName = targetName;
+    var breakpoint = SDK.domDebuggerManager.resolveEventListenerBreakpoint(auxData);
+    if (breakpoint.enabled() !== enabled) {
+        pane._breakpoints.get(breakpoint).checkbox.checked = enabled;
+        pane._breakpointCheckboxClicked(breakpoint);
+    }
+}
+
 };
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector/debugger/fetch-breakpoints.html b/third_party/WebKit/LayoutTests/http/tests/inspector/debugger/fetch-breakpoints.html
index 0be4949..51d0388 100644
--- a/third_party/WebKit/LayoutTests/http/tests/inspector/debugger/fetch-breakpoints.html
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector/debugger/fetch-breakpoints.html
@@ -14,7 +14,7 @@
     InspectorTest.runDebuggerTestSuite([
         function testFetchBreakpoint(next)
         {
-            SDK.xhrBreakpointManager.addBreakpoint("foo", true);
+            SDK.domDebuggerManager.addXHRBreakpoint("foo", true);
             InspectorTest.waitUntilPaused(step1);
             InspectorTest.evaluateInPageWithTimeout("sendRequest('/foo?a=b')");
 
@@ -31,14 +31,14 @@
 
             function step3()
             {
-                SDK.xhrBreakpointManager.removeBreakpoint("foo");
+                SDK.domDebuggerManager.removeXHRBreakpoint("foo");
                 InspectorTest.evaluateInPage("sendRequest('/foo?a=b')", next);
             }
         },
 
         function testPauseOnAnyFetch(next)
         {
-            SDK.xhrBreakpointManager.addBreakpoint("", true);
+            SDK.domDebuggerManager.addXHRBreakpoint("", true);
             InspectorTest.waitUntilPaused(pausedFoo);
             InspectorTest.evaluateInPageWithTimeout("sendRequest('/foo?a=b')");
 
@@ -56,7 +56,7 @@
             {
                 function resumed()
                 {
-                    SDK.xhrBreakpointManager.removeBreakpoint("");
+                    SDK.domDebuggerManager.removeXHRBreakpoint("");
                     InspectorTest.evaluateInPage("sendRequest('/baz?a=b')", next);
                 }
                 InspectorTest.resumeExecution(resumed);
@@ -65,7 +65,7 @@
 
         function testDisableBreakpoint(next)
         {
-            SDK.xhrBreakpointManager.addBreakpoint("", true);
+            SDK.domDebuggerManager.addXHRBreakpoint("", true);
             InspectorTest.waitUntilPaused(paused);
             InspectorTest.evaluateInPage("sendRequest('/foo')");
 
@@ -73,7 +73,7 @@
             {
                 function resumed()
                 {
-                    SDK.xhrBreakpointManager.toggleBreakpoint("", false);
+                    SDK.domDebuggerManager.toggleXHRBreakpoint("", false);
                     InspectorTest.waitUntilPaused(pausedAgain);
                     InspectorTest.evaluateInPage("sendRequest('/foo')", next);
                 }
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector/elements-test.js b/third_party/WebKit/LayoutTests/http/tests/inspector/elements-test.js
index 495b94a..d965cc1 100644
--- a/third_party/WebKit/LayoutTests/http/tests/inspector/elements-test.js
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector/elements-test.js
@@ -967,7 +967,7 @@
 
     function nodeResolved(node)
     {
-        InspectorTest.DOMAgent.getHighlightObjectForTest(node.id, report);
+        InspectorTest.OverlayAgent.getHighlightObjectForTest(node.id, report);
     }
 
     function report(error, result)
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector/inspector-test.js b/third_party/WebKit/LayoutTests/http/tests/inspector/inspector-test.js
index 27539da..bee51ac1 100644
--- a/third_party/WebKit/LayoutTests/http/tests/inspector/inspector-test.js
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector/inspector-test.js
@@ -978,6 +978,7 @@
         InspectorTest.HeapProfilerAgent = target.heapProfilerAgent();
         InspectorTest.InspectorAgent = target.inspectorAgent();
         InspectorTest.NetworkAgent = target.networkAgent();
+        InspectorTest.OverlayAgent = target.overlayAgent();
         InspectorTest.PageAgent = target.pageAgent();
         InspectorTest.ProfilerAgent = target.profilerAgent();
         InspectorTest.RuntimeAgent = target.runtimeAgent();
@@ -992,6 +993,7 @@
         InspectorTest.domDebuggerModel = target.model(SDK.DOMDebuggerModel);
         InspectorTest.cssModel = target.model(SDK.CSSModel);
         InspectorTest.cpuProfilerModel = target.model(SDK.CPUProfilerModel);
+        InspectorTest.overlayModel = target.model(SDK.OverlayModel);
         InspectorTest.serviceWorkerManager = target.model(SDK.ServiceWorkerManager);
         InspectorTest.tracingManager = target.model(SDK.TracingManager);
         InspectorTest.mainTarget = target;
diff --git a/third_party/WebKit/LayoutTests/http/tests/loading/doc-write-sync-third-party-script-reload-expected.txt b/third_party/WebKit/LayoutTests/http/tests/loading/doc-write-sync-third-party-script-reload-expected.txt
index 1d06d6e5..055f2cd 100644
--- a/third_party/WebKit/LayoutTests/http/tests/loading/doc-write-sync-third-party-script-reload-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/loading/doc-write-sync-third-party-script-reload-expected.txt
@@ -4,12 +4,12 @@
 CONSOLE WARNING: line 47: A Parser-blocking, cross site (i.e. different eTLD+1) script, http://localhost:8000/loading/resources/js-loaded.js?reload, is invoked via document.write. The network request for this script MAY be blocked by the browser in this or a future page load due to poor network connectivity. If blocked in this page load, it will be confirmed in a subsequent console message.See https://www.chromestatus.com/feature/5718547946799104 for more details.
 CONSOLE ERROR: Network request for the parser-blocking, cross site (i.e. different eTLD+1) script, http://localhost:8000/loading/resources/js-loaded.js?reload, invoked via document.write was BLOCKED by the browser due to poor network connectivity. 
 main frame - didStartProvisionalLoadForFrame
-main frame - didFailLoadWithError
+main frame - didFinishLoadForFrame
 main frame - didCommitLoadForFrame
 CONSOLE WARNING: line 47: A Parser-blocking, cross site (i.e. different eTLD+1) script, http://localhost:8000/loading/resources/js-loaded.js?reload, is invoked via document.write. The network request for this script MAY be blocked by the browser in this or a future page load due to poor network connectivity. If blocked in this page load, it will be confirmed in a subsequent console message.See https://www.chromestatus.com/feature/5718547946799104 for more details.
 CONSOLE WARNING: line 47: A Parser-blocking, cross site (i.e. different eTLD+1) script, http://localhost:8000/loading/resources/js-loaded.js?reload, is invoked via document.write. The network request for this script MAY be blocked by the browser in this or a future page load due to poor network connectivity. If blocked in this page load, it will be confirmed in a subsequent console message.See https://www.chromestatus.com/feature/5718547946799104 for more details.
 main frame - didStartProvisionalLoadForFrame
-main frame - didFailLoadWithError
+main frame - didFinishLoadForFrame
 main frame - didCommitLoadForFrame
 CONSOLE WARNING: line 47: A Parser-blocking, cross site (i.e. different eTLD+1) script, http://localhost:8000/loading/resources/js-loaded.js?reload, is invoked via document.write. The network request for this script MAY be blocked by the browser in this or a future page load due to poor network connectivity. If blocked in this page load, it will be confirmed in a subsequent console message.See https://www.chromestatus.com/feature/5718547946799104 for more details.
 CONSOLE WARNING: line 47: A Parser-blocking, cross site (i.e. different eTLD+1) script, http://localhost:8000/loading/resources/js-loaded.js?reload, is invoked via document.write. The network request for this script MAY be blocked by the browser in this or a future page load due to poor network connectivity. If blocked in this page load, it will be confirmed in a subsequent console message.See https://www.chromestatus.com/feature/5718547946799104 for more details.
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/dataURL/xss-DENIED-from-javascript-url-window-open-expected.txt b/third_party/WebKit/LayoutTests/http/tests/security/dataURL/xss-DENIED-from-javascript-url-window-open-expected.txt
deleted file mode 100644
index 797a829b..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/security/dataURL/xss-DENIED-from-javascript-url-window-open-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-CONSOLE WARNING: Upcoming versions will block content-initiated top frame navigations to data: URLs. For more information, see https://goo.gl/BaZAea.
-Opener Frame
-
-PASS: Access from a window opened with a data: URL was denied.
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/dataURL/xss-DENIED-from-javascript-url-window-open.html b/third_party/WebKit/LayoutTests/http/tests/security/dataURL/xss-DENIED-from-javascript-url-window-open.html
deleted file mode 100644
index b4a2ff67..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/security/dataURL/xss-DENIED-from-javascript-url-window-open.html
+++ /dev/null
@@ -1,39 +0,0 @@
-<html>
-<head>
-    <script src="../resources/cross-frame-access.js"></script>
-    <script>
-        if (window.testRunner) {
-            testRunner.dumpAsText();
-            testRunner.setCanOpenWindows();
-            testRunner.waitUntilDone();
-        }
-        function loaded() {
-            var url = "data:text/html,<html>"
-                + "<head>"
-                +     "<scr" + "ipt>"
-                +         "function test() {"
-                +             "try {"
-                +                 "opener.document.getElementById(\"accessMe\").innerHTML = \"FAIL: Access from a window opened with a data: URL was allowed!\";"
-                +             "} catch (e) {"
-                +             "}"
-                +             "window.opener.postMessage('done', '*');"
-                +         "}"
-                +     "</scri" + "pt>"
-                + "</head>"
-                + "<body onload=\"test();\">"
-                +     "<p>Opened Frame.</p>"
-                + "</body>"
-                + "</html>";
-
-            window.addEventListener('message', function () {
-                closeWindowAndNotifyDone(openedWindow);
-            });
-            var openedWindow = window.open(url);
-        }
-    </script>
-</head>
-<body onload="loaded();">
-    <p>Opener Frame</p>
-    <p id='accessMe'>PASS: Access from a window opened with a data: URL was denied.</p>
-</body>
-</html>
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/dataURL/xss-DENIED-to-data-url-window-open-expected.txt b/third_party/WebKit/LayoutTests/http/tests/security/dataURL/xss-DENIED-to-data-url-window-open-expected.txt
deleted file mode 100644
index 3a5f5c1..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/security/dataURL/xss-DENIED-to-data-url-window-open-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-CONSOLE WARNING: Upcoming versions will block content-initiated top frame navigations to data: URLs. For more information, see https://goo.gl/BaZAea.
-Opener Frame
-
-
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/dataURL/xss-DENIED-to-data-url-window-open.html b/third_party/WebKit/LayoutTests/http/tests/security/dataURL/xss-DENIED-to-data-url-window-open.html
deleted file mode 100644
index ded1406..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/security/dataURL/xss-DENIED-to-data-url-window-open.html
+++ /dev/null
@@ -1,47 +0,0 @@
-<html>
-<head>
-    <script src="../resources/cross-frame-access.js"></script>
-    <script>
-        if (window.testRunner) {
-            testRunner.dumpAsText();
-            testRunner.waitUntilDone();
-            testRunner.setCanOpenWindows();
-        }
-
-        var openedWindow;
-
-        function loaded() {
-            var url = "data:text/html,<html>"
-                + "<head>"
-                +     "<scr" + "ipt>"
-                +         "function fireSentinel() {"
-                +             "window.opener.postMessage('done', '*');"
-                +         "}"
-                +     "</scr" + "ipt>"
-                + "</head>"
-                + "<body onload=\"fireSentinel();\">"
-                +     "<p>Opened Frame</p>"
-                +     "<p id='accessMe'>PASS: Cross frame access from an opener frame was denied</p>"
-                + "</body>"
-                + "</html>";
-    
-            window.addEventListener('message', performTest);
-            openedWindow = window.open(url);
-        }
-    
-        function performTest() {
-            try {
-                openedWindow.document.getElementById('accessMe').innerHTML = 'FAIL: Access to a window opened with a data: URL was allowed.';
-            } catch (e) {
-            }
-
-            if (window.testRunner)
-                closeWindowAndNotifyDone(openedWindow);
-        }    
-    </script>
-</head>
-<body onload="loaded();">
-    <p>Opener Frame</p>
-    <pre id="console"></pre>
-</body>
-</html>
diff --git a/third_party/WebKit/LayoutTests/inspector-protocol/dom/dom-setInspectModeEnabled.html b/third_party/WebKit/LayoutTests/inspector-protocol/dom/dom-setInspectModeEnabled.html
index 24432f9..f10e98e 100644
--- a/third_party/WebKit/LayoutTests/inspector-protocol/dom/dom-setInspectModeEnabled.html
+++ b/third_party/WebKit/LayoutTests/inspector-protocol/dom/dom-setInspectModeEnabled.html
@@ -7,9 +7,10 @@
 {
     var nodeInfo = {};
     InspectorTest.eventHandler["DOM.setChildNodes"] = setChildNodes;
-    InspectorTest.eventHandler["DOM.inspectNodeRequested"] = inspectNodeRequested;
+    InspectorTest.eventHandler["Overlay.inspectNodeRequested"] = inspectNodeRequested;
     InspectorTest.sendCommand("DOM.enable", {});
-    InspectorTest.sendCommand("DOM.setInspectMode", { "mode": "searchForNode", highlightConfig: {} }, onSetModeEnabled);
+    InspectorTest.sendCommand("Overlay.enable", {});
+    InspectorTest.sendCommand("Overlay.setInspectMode", { "mode": "searchForNode", highlightConfig: {} }, onSetModeEnabled);
 
     function onSetModeEnabled(message)
     {
diff --git a/third_party/WebKit/LayoutTests/inspector/agents-enable-disable-expected.txt b/third_party/WebKit/LayoutTests/inspector/agents-enable-disable-expected.txt
index e9cbe18..c9fe4a65 100644
--- a/third_party/WebKit/LayoutTests/inspector/agents-enable-disable-expected.txt
+++ b/third_party/WebKit/LayoutTests/inspector/agents-enable-disable-expected.txt
@@ -11,6 +11,7 @@
 LayerTree.disable finished successfully
 Log.disable finished successfully
 Network.disable finished successfully
+Overlay.disable finished successfully
 Page.disable finished successfully
 Profiler.disable finished successfully
 Runtime.disable finished successfully
@@ -48,6 +49,9 @@
 Network.enable finished successfully
 Network.disable finished successfully
 
+Overlay.enable finished with error DOM should be enabled first
+Overlay.disable finished successfully
+
 Page.enable finished successfully
 Page.disable finished successfully
 
diff --git a/third_party/WebKit/LayoutTests/inspector/elements/inspect-mode-after-profiling.html b/third_party/WebKit/LayoutTests/inspector/elements/inspect-mode-after-profiling.html
index 8d40576..cba03b6 100644
--- a/third_party/WebKit/LayoutTests/inspector/elements/inspect-mode-after-profiling.html
+++ b/third_party/WebKit/LayoutTests/inspector/elements/inspect-mode-after-profiling.html
@@ -18,7 +18,7 @@
 {
     InspectorTest.cpuProfilerModel.startRecording();
     InspectorTest.cpuProfilerModel.stopRecording();
-    InspectorTest.domModel.setInspectMode(Protocol.DOM.InspectMode.SearchForNode, clickAtInspected);
+    InspectorTest.overlayModel.setInspectMode(Protocol.Overlay.InspectMode.SearchForNode).then(clickAtInspected);
 
     function clickAtInspected()
     {
diff --git a/third_party/WebKit/LayoutTests/inspector/elements/inspect-mode-shadow-text.html b/third_party/WebKit/LayoutTests/inspector/elements/inspect-mode-shadow-text.html
index 694383e..fb1e8d04 100644
--- a/third_party/WebKit/LayoutTests/inspector/elements/inspect-mode-shadow-text.html
+++ b/third_party/WebKit/LayoutTests/inspector/elements/inspect-mode-shadow-text.html
@@ -19,7 +19,7 @@
 
 function test()
 {
-    InspectorTest.domModel.setInspectMode(Protocol.DOM.InspectMode.SearchForNode, step2);
+    InspectorTest.overlayModel.setInspectMode(Protocol.Overlay.InspectMode.SearchForNode).then(step2);
 
     function step2()
     {
diff --git a/third_party/WebKit/LayoutTests/inspector/elements/inspect-pointer-events-none.html b/third_party/WebKit/LayoutTests/inspector/elements/inspect-pointer-events-none.html
index 71505d93f..ab0ee74 100644
--- a/third_party/WebKit/LayoutTests/inspector/elements/inspect-pointer-events-none.html
+++ b/third_party/WebKit/LayoutTests/inspector/elements/inspect-pointer-events-none.html
@@ -59,7 +59,7 @@
 
     function step1()
     {
-        InspectorTest.domModel.setInspectMode(Protocol.DOM.InspectMode.SearchForNode, step2);
+        InspectorTest.overlayModel.setInspectMode(Protocol.Overlay.InspectMode.SearchForNode).then(step2);
     }
 
     function step2()
@@ -72,7 +72,7 @@
     {
         InspectorTest.firstElementsTreeOutline().removeEventListener(Elements.ElementsTreeOutline.Events.SelectedNodeChanged, step3);
         expectSelectedNode("inner");
-        InspectorTest.domModel.setInspectMode(Protocol.DOM.InspectMode.SearchForNode, step4);
+        InspectorTest.overlayModel.setInspectMode(Protocol.Overlay.InspectMode.SearchForNode).then(step4);
     }
 
     function step4()
diff --git a/third_party/WebKit/LayoutTests/inspector/elements/inspect-pseudo-element.html b/third_party/WebKit/LayoutTests/inspector/elements/inspect-pseudo-element.html
index 5f34553..2598996 100644
--- a/third_party/WebKit/LayoutTests/inspector/elements/inspect-pseudo-element.html
+++ b/third_party/WebKit/LayoutTests/inspector/elements/inspect-pseudo-element.html
@@ -16,7 +16,7 @@
 
 function test()
 {
-    InspectorTest.domModel.setInspectMode(Protocol.DOM.InspectMode.SearchForNode, inspectModeEnabled);
+    InspectorTest.overlayModel.setInspectMode(Protocol.Overlay.InspectMode.SearchForNode).then(inspectModeEnabled);
 
     function inspectModeEnabled()
     {
diff --git a/third_party/WebKit/LayoutTests/inspector/layers/no-overlay-layers.html b/third_party/WebKit/LayoutTests/inspector/layers/no-overlay-layers.html
index 0a82d716..8300cdb 100644
--- a/third_party/WebKit/LayoutTests/inspector/layers/no-overlay-layers.html
+++ b/third_party/WebKit/LayoutTests/inspector/layers/no-overlay-layers.html
@@ -25,7 +25,7 @@
     {
         // Assure layer objects are not re-created during updates.
         InspectorTest.layerTreeModel().layerTree().forEachLayer(function(layer) { layersBeforeHighlight.push(layer.id()); });
-        InspectorTest.DOMAgent.highlightRect(0, 0, 200, 200, {r:255, g:0, b:0});
+        InspectorTest.OverlayAgent.highlightRect(0, 0, 200, 200, {r:255, g:0, b:0});
         InspectorTest.evaluateAndRunWhenTreeChanges("requestAnimationFrame(updateGeometry)", step2);
     }
 
diff --git a/third_party/WebKit/LayoutTests/inspector/profiler/agents-disabled-check-expected.txt b/third_party/WebKit/LayoutTests/inspector/profiler/agents-disabled-check-expected.txt
index 0d62404..accaa67 100644
--- a/third_party/WebKit/LayoutTests/inspector/profiler/agents-disabled-check-expected.txt
+++ b/third_party/WebKit/LayoutTests/inspector/profiler/agents-disabled-check-expected.txt
@@ -1,22 +1,22 @@
 Test that if a profiler is working all the agents are disabled.
 
 --> SDK.targetManager.suspendAllTargets();
-frontend: {"id":<number>,"method":"Page.configureOverlay","params":{"suspended":true}}
 frontend: {"id":<number>,"method":"Target.setAutoAttach","params":{"autoAttach":true,"waitForDebuggerOnStart":false}}
 frontend: {"id":<number>,"method":"Debugger.disable"}
 frontend: {"id":<number>,"method":"Debugger.setAsyncCallStackDepth","params":{"maxDepth":0}}
-frontend: {"id":<number>,"method":"Page.configureOverlay","params":{"suspended":true}}
+frontend: {"id":<number>,"method":"Overlay.setPausedInDebuggerMessage"}
 frontend: {"id":<number>,"method":"DOM.disable"}
 frontend: {"id":<number>,"method":"CSS.disable"}
+frontend: {"id":<number>,"method":"Overlay.setSuspended","params":{"suspended":true}}
 
 --> SDK.targetManager.resumeAllTargets();
-frontend: {"id":<number>,"method":"Page.configureOverlay","params":{"suspended":false}}
 frontend: {"id":<number>,"method":"Target.setAutoAttach","params":{"autoAttach":true,"waitForDebuggerOnStart":true}}
 frontend: {"id":<number>,"method":"Debugger.enable"}
 frontend: {"id":<number>,"method":"Debugger.setPauseOnExceptions","params":{"state":"none"}}
 frontend: {"id":<number>,"method":"Debugger.setAsyncCallStackDepth","params":{"maxDepth":8}}
 frontend: {"id":<number>,"method":"DOM.enable"}
 frontend: {"id":<number>,"method":"CSS.enable"}
+frontend: {"id":<number>,"method":"Overlay.setSuspended","params":{"suspended":false}}
 
 --> done
 
diff --git a/third_party/WebKit/LayoutTests/inspector/screen-orientation-override.html b/third_party/WebKit/LayoutTests/inspector/screen-orientation-override.html
index f8bc5de2..5c0e1b6 100644
--- a/third_party/WebKit/LayoutTests/inspector/screen-orientation-override.html
+++ b/third_party/WebKit/LayoutTests/inspector/screen-orientation-override.html
@@ -45,12 +45,13 @@
     function testOverride(angle, orientation, next)
     {
         InspectorTest.addConsoleSniffer(addDumpResult.bind(null, next));
-        InspectorTest.EmulationAgent.invoke_setDeviceMetricsOverride({width: 0, height: 0, deviceScaleFactor: 0, mobile: true, fitWindow: false, screenOrientation: {type: orientation, angle: angle}}, function() {});
+        InspectorTest.EmulationAgent.invoke_setDeviceMetricsOverride({width: 0, height: 0, deviceScaleFactor: 0, mobile: true, fitWindow: false, screenOrientation: {type: orientation, angle: angle}});
     }
 
-    function testError(angle, orientation, next)
+    async function testError(angle, orientation, next)
     {
-        InspectorTest.EmulationAgent.invoke_setDeviceMetricsOverride({width: 0, height: 0, deviceScaleFactor: 0, mobile: true, fitWindow: false, screenOrientation: {type: orientation, angle: angle}}, next);
+        await InspectorTest.EmulationAgent.invoke_setDeviceMetricsOverride({width: 0, height: 0, deviceScaleFactor: 0, mobile: true, fitWindow: false, screenOrientation: {type: orientation, angle: angle}});
+        next();
     }
 
     var original;
diff --git a/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/debugger-set-breakpoint-regex.html b/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/debugger-set-breakpoint-regex.html
index 6e570d7..1f71f94 100644
--- a/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/debugger-set-breakpoint-regex.html
+++ b/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/debugger-set-breakpoint-regex.html
@@ -16,15 +16,11 @@
 function test()
 {
     InspectorTest.runDebuggerTestSuite([
-        function testSetNoneOfURLAndRegex(next)
+        async function testSetNoneOfURLAndRegex(next)
         {
-            InspectorTest.DebuggerAgent.invoke_setBreakpointByUrl({lineNumber: 1}, step2);
-
-            function step2(error)
-            {
-                InspectorTest.addResult(error);
-                next();
-            }
+            var response = await InspectorTest.DebuggerAgent.invoke_setBreakpointByUrl({lineNumber: 1});
+            InspectorTest.addResult(response[Protocol.Error]);
+            next();
         },
 
         function testSetBothURLAndRegex(next)
@@ -40,20 +36,13 @@
             }
         },
 
-        function testSetByRegex(next)
+        async function testSetByRegex(next)
         {
-            InspectorTest.DebuggerAgent.invoke_setBreakpointByUrl({urlRegex: "debugger-set-breakpoint.*", lineNumber:8}, step2);
-
-            function step2(result)
-            {
-                InspectorTest.runTestFunctionAndWaitUntilPaused(step3);
-            }
-        
-            function step3(callFrames)
-            {
+            await InspectorTest.DebuggerAgent.invoke_setBreakpointByUrl({urlRegex: "debugger-set-breakpoint.*", lineNumber:8});
+            InspectorTest.runTestFunctionAndWaitUntilPaused(callFrames => {
                 InspectorTest.captureStackTrace(callFrames);
                 next();
-            }
+            });
         }
     ]);
 }
diff --git a/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/event-listener-breakpoints-after-suspension.html b/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/event-listener-breakpoints-after-suspension.html
index 36b526f..a70dfd46 100644
--- a/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/event-listener-breakpoints-after-suspension.html
+++ b/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/event-listener-breakpoints-after-suspension.html
@@ -22,8 +22,7 @@
 
     function start()
     {
-        var pane = self.runtime.sharedInstance(Sources.EventListenerBreakpointsSidebarPane);
-        pane._setBreakpoint("listener:click");
+        InspectorTest.setEventListenerBreakpoint("listener:click", true);
         InspectorTest.waitUntilPaused(paused);
         InspectorTest.evaluateInPageWithTimeout("addListenerAndClick()");
     }
diff --git a/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/event-listener-breakpoints-script-first-stmt.html b/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/event-listener-breakpoints-script-first-stmt.html
index 53f91eb..82122fa 100644
--- a/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/event-listener-breakpoints-script-first-stmt.html
+++ b/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/event-listener-breakpoints-script-first-stmt.html
@@ -29,14 +29,13 @@
 
 function test()
 {
-    var pane = self.runtime.sharedInstance(Sources.EventListenerBreakpointsSidebarPane);
     var numberOfPauses = 2;
 
     InspectorTest.startDebuggerTest(step1, true);
 
     function step1()
     {
-        pane._setBreakpoint("instrumentation:scriptFirstStatement");
+        InspectorTest.setEventListenerBreakpoint("instrumentation:scriptFirstStatement", true);
         InspectorTest.runTestFunctionAndWaitUntilPaused(didPause);
     }
 
@@ -54,7 +53,7 @@
 
     function step2()
     {
-        pane._removeBreakpoint("instrumentation:scriptFirstStatement");
+        InspectorTest.setEventListenerBreakpoint("instrumentation:scriptFirstStatement", false);
         InspectorTest.completeDebuggerTest();
     }
 }
diff --git a/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/event-listener-breakpoints-xhr.html b/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/event-listener-breakpoints-xhr.html
index 8b4f527..a045496 100644
--- a/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/event-listener-breakpoints-xhr.html
+++ b/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/event-listener-breakpoints-xhr.html
@@ -49,19 +49,16 @@
 
 function test()
 {
-    var pane = self.runtime.sharedInstance(Sources.EventListenerBreakpointsSidebarPane);
-    var xhrTargetNames = ["XMLHttpRequest", "XMLHttpRequestUpload"];
-
     InspectorTest.setQuiet(true);
     InspectorTest.startDebuggerTest(step1);
 
     function step1()
     {
-        pane._setBreakpoint("listener:load");
-        pane._setBreakpoint("listener:error");
-        pane._setBreakpoint("listener:loadend", xhrTargetNames);
-        pane._setBreakpoint("listener:progress", xhrTargetNames);
-        pane._setBreakpoint("listener:readystatechange", xhrTargetNames);
+        InspectorTest.setEventListenerBreakpoint("listener:load", true, "xmlhttprequest");
+        InspectorTest.setEventListenerBreakpoint("listener:error", true, "xmlhttprequest");
+        InspectorTest.setEventListenerBreakpoint("listener:loadend", true, "xmlhttprequest");
+        InspectorTest.setEventListenerBreakpoint("listener:progress", true, "xmlhttprequest");
+        InspectorTest.setEventListenerBreakpoint("listener:readystatechange", true, "xmlhttprequest");
         InspectorTest.runTestFunctionAndWaitUntilPaused(didPause);
     }
 
@@ -87,11 +84,11 @@
 
     function completeTest()
     {
-        pane._removeBreakpoint("listener:load");
-        pane._removeBreakpoint("listener:error");
-        pane._removeBreakpoint("listener:loadend", xhrTargetNames);
-        pane._removeBreakpoint("listener:progress", xhrTargetNames);
-        pane._removeBreakpoint("listener:readystatechange", xhrTargetNames);
+        InspectorTest.setEventListenerBreakpoint("listener:load", false, "xmlhttprequest");
+        InspectorTest.setEventListenerBreakpoint("listener:error", false, "xmlhttprequest");
+        InspectorTest.setEventListenerBreakpoint("listener:loadend", false, "xmlhttprequest");
+        InspectorTest.setEventListenerBreakpoint("listener:progress", false, "xmlhttprequest");
+        InspectorTest.setEventListenerBreakpoint("listener:readystatechange", false, "xmlhttprequest");
         InspectorTest.deprecatedRunAfterPendingDispatches(InspectorTest.completeDebuggerTest.bind(InspectorTest));
     }
 }
diff --git a/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/event-listener-breakpoints.html b/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/event-listener-breakpoints.html
index 4d2e597..d0c439a9 100644
--- a/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/event-listener-breakpoints.html
+++ b/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/event-listener-breakpoints.html
@@ -72,11 +72,10 @@
 
 function test()
 {
-    var pane = self.runtime.sharedInstance(Sources.EventListenerBreakpointsSidebarPane);
     var testFunctions = [
         function testClickBreakpoint(next)
         {
-            pane._setBreakpoint("listener:click");
+            InspectorTest.setEventListenerBreakpoint("listener:click", true);
             InspectorTest.waitUntilPaused(paused);
             InspectorTest.evaluateInPageWithTimeout("addListenerAndClick()");
 
@@ -84,7 +83,7 @@
             {
                 InspectorTest.captureStackTrace(callFrames);
                 printEventTargetName(auxData);
-                pane._removeBreakpoint("listener:click");
+                InspectorTest.setEventListenerBreakpoint("listener:click", false);
                 InspectorTest.resumeExecution(resumed);
             }
 
@@ -96,7 +95,7 @@
 
         function testAuxclickBreakpoint(next)
         {
-            pane._setBreakpoint("listener:auxclick");
+            InspectorTest.setEventListenerBreakpoint("listener:auxclick", true);
             InspectorTest.waitUntilPaused(paused);
             InspectorTest.evaluateInPageWithTimeout("addListenerAndAuxclick()");
 
@@ -104,7 +103,7 @@
             {
                 InspectorTest.captureStackTrace(callFrames);
                 printEventTargetName(auxData);
-                pane._removeBreakpoint("listener:auxclick");
+                InspectorTest.setEventListenerBreakpoint("listener:auxclick", false);
                 InspectorTest.resumeExecution(resumed);
             }
 
@@ -116,22 +115,22 @@
 
         function testTimerFiredBreakpoint(next)
         {
-            pane._setBreakpoint("instrumentation:setTimeout.callback");
+            InspectorTest.setEventListenerBreakpoint("instrumentation:setTimeout.callback", true);
             InspectorTest.waitUntilPaused(paused);
             InspectorTest.evaluateInPage("setTimeout(timerFired, 10)");
 
             function paused(callFrames)
             {
                 InspectorTest.captureStackTrace(callFrames);
-                pane._removeBreakpoint("instrumentation:setTimeout.callback");
+                InspectorTest.setEventListenerBreakpoint("instrumentation:setTimeout.callback", false);
                 InspectorTest.resumeExecution(next);
             }
         },
 
         function testLoadBreakpointOnXHR(next)
         {
-            pane._setBreakpoint("listener:load", ["xmlHTTPrequest"]); // test case-insensitive match
-            pane._setBreakpoint("listener:error", ["XMLHttpRequest"]);
+            InspectorTest.setEventListenerBreakpoint("listener:load", true, "xmlhttprequest");
+            InspectorTest.setEventListenerBreakpoint("listener:error", true, "xmlhttprequest");
             InspectorTest.waitUntilPaused(paused);
             InspectorTest.evaluateInPageWithTimeout("addLoadListeners()");
 
@@ -139,8 +138,8 @@
             {
                 InspectorTest.captureStackTrace(callFrames);
                 printEventTargetName(auxData);
-                pane._removeBreakpoint("listener:load", ["XMLHttpRequest"]);
-                pane._removeBreakpoint("listener:error", ["xmlHTTPrequest"]);
+                InspectorTest.setEventListenerBreakpoint("listener:load", false, "xmlhttprequest");
+                InspectorTest.setEventListenerBreakpoint("listener:error", false, "xmlhttprequest");
                 InspectorTest.resumeExecution(resumed);
             }
 
@@ -152,7 +151,7 @@
 
         function testMediaEventBreakpoint(next)
         {
-            pane._setBreakpoint("listener:play", ["audio", "video"]);
+            InspectorTest.setEventListenerBreakpoint("listener:play", true, "audio");
             InspectorTest.waitUntilPaused(paused);
             InspectorTest.evaluateInPageWithTimeout("playVideo()");
 
@@ -160,7 +159,7 @@
             {
                 InspectorTest.captureStackTrace(callFrames);
                 printEventTargetName(auxData);
-                pane._removeBreakpoint("listener:play", ["audio", "video"]);
+                InspectorTest.setEventListenerBreakpoint("listener:play", false, "audio");
                 InspectorTest.resumeExecution(next);
             }
         }
@@ -170,7 +169,7 @@
         testFunctions.push(
             function testPointerEventBreakpoint(next)
             {
-                pane._setBreakpoint("listener:pointerdown");
+                InspectorTest.setEventListenerBreakpoint("listener:pointerdown", true);
                 InspectorTest.waitUntilPaused(paused);
                 InspectorTest.evaluateInPageWithTimeout("addListenerAndPointerDown()");
 
@@ -178,7 +177,7 @@
                 {
                     InspectorTest.captureStackTrace(callFrames);
                     printEventTargetName(auxData);
-                    pane._removeBreakpoint("listener:pointerdown");
+                    InspectorTest.setEventListenerBreakpoint("listener:pointerdown", false);
                     InspectorTest.resumeExecution(resumed);
                 }
 
diff --git a/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/xhr-breakpoints.html b/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/xhr-breakpoints.html
index 3690196b..035a9c8 100644
--- a/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/xhr-breakpoints.html
+++ b/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/xhr-breakpoints.html
@@ -16,7 +16,7 @@
     InspectorTest.runDebuggerTestSuite([
         function testXHRBreakpoint(next)
         {
-            SDK.xhrBreakpointManager.addBreakpoint("foo", true);
+            SDK.domDebuggerManager.addXHRBreakpoint("foo", true);
             InspectorTest.waitUntilPaused(step1);
             InspectorTest.evaluateInPageWithTimeout("sendRequest('/foo?a=b')");
 
@@ -33,14 +33,14 @@
 
             function step3()
             {
-                SDK.xhrBreakpointManager.removeBreakpoint("foo");
+                SDK.domDebuggerManager.removeXHRBreakpoint("foo");
                 InspectorTest.evaluateInPage("sendRequest('/foo?a=b')", next);
             }
         },
 
         function testPauseOnAnyXHR(next)
         {
-            SDK.xhrBreakpointManager.addBreakpoint("", true);
+            SDK.domDebuggerManager.addXHRBreakpoint("", true);
             InspectorTest.waitUntilPaused(pausedFoo);
             InspectorTest.evaluateInPageWithTimeout("sendRequest('/foo?a=b')");
 
@@ -58,7 +58,7 @@
             {
                 function resumed()
                 {
-                    SDK.xhrBreakpointManager.removeBreakpoint("");
+                    SDK.domDebuggerManager.removeXHRBreakpoint("");
                     InspectorTest.evaluateInPage("sendRequest('/baz?a=b')", next);
                 }
                 InspectorTest.resumeExecution(resumed);
@@ -67,7 +67,7 @@
 
         function testDisableBreakpoint(next)
         {
-            SDK.xhrBreakpointManager.addBreakpoint("", true);
+            SDK.domDebuggerManager.addXHRBreakpoint("", true);
             InspectorTest.waitUntilPaused(paused);
             InspectorTest.evaluateInPage("sendRequest('/foo')");
 
@@ -75,7 +75,7 @@
             {
                 function resumed()
                 {
-                    SDK.xhrBreakpointManager.toggleBreakpoint("", false);
+                    SDK.domDebuggerManager.toggleXHRBreakpoint("", false);
                     InspectorTest.waitUntilPaused(pausedAgain);
                     InspectorTest.evaluateInPage("sendRequest('/foo')", next);
                 }
diff --git a/third_party/WebKit/LayoutTests/inspector/sources/debugger-frameworks/frameworks-skip-break-program.html b/third_party/WebKit/LayoutTests/inspector/sources/debugger-frameworks/frameworks-skip-break-program.html
index cca9f1e..375a4d5 100644
--- a/third_party/WebKit/LayoutTests/inspector/sources/debugger-frameworks/frameworks-skip-break-program.html
+++ b/third_party/WebKit/LayoutTests/inspector/sources/debugger-frameworks/frameworks-skip-break-program.html
@@ -27,8 +27,6 @@
     var frameworkRegexString = "/framework\\.js$";
     Common.settingForTest("skipStackFramesPattern").set(frameworkRegexString);
 
-    var pane = self.runtime.sharedInstance(Sources.EventListenerBreakpointsSidebarPane);
-
     InspectorTest.startDebuggerTest(step1, true);
 
     function step1()
@@ -39,7 +37,7 @@
     function step2()
     {
         InspectorTest.DebuggerAgent.setPauseOnExceptions(SDK.DebuggerModel.PauseOnExceptionsState.PauseOnAllExceptions);
-        pane._setBreakpoint("instrumentation:setTimeout");
+        InspectorTest.setEventListenerBreakpoint("instrumentation:setTimeout", true);
         InspectorTest.resumeExecution(InspectorTest.waitUntilPaused.bind(InspectorTest, didPause));
     }
 
@@ -52,7 +50,7 @@
     function completeTest()
     {
         InspectorTest.DebuggerAgent.setPauseOnExceptions(SDK.DebuggerModel.PauseOnExceptionsState.DontPauseOnExceptions);
-        pane._removeBreakpoint("instrumentation:setTimeout");
+        InspectorTest.setEventListenerBreakpoint("instrumentation:setTimeout", false);
         InspectorTest.completeDebuggerTest();
     }
 }
diff --git a/third_party/WebKit/LayoutTests/inspector/sources/debugger-pause/skip-pauses-until-reload.html b/third_party/WebKit/LayoutTests/inspector/sources/debugger-pause/skip-pauses-until-reload.html
index 5e2bc98..280db8d1 100644
--- a/third_party/WebKit/LayoutTests/inspector/sources/debugger-pause/skip-pauses-until-reload.html
+++ b/third_party/WebKit/LayoutTests/inspector/sources/debugger-pause/skip-pauses-until-reload.html
@@ -82,9 +82,8 @@
     function setUpEventBreakpoints()
     {
         testRunner.logToStderr("setUpEventBreakpoints");
-        var pane = self.runtime.sharedInstance(Sources.EventListenerBreakpointsSidebarPane);
         InspectorTest.addResult("Set up Event breakpoints.");
-        pane._setBreakpoint("listener:click");
+        InspectorTest.setEventListenerBreakpoint("listener:click", true);
         InspectorTest.deprecatedRunAfterPendingDispatches(didSetUp);
     }
 
@@ -136,8 +135,7 @@
     function completeTest()
     {
         testRunner.logToStderr("completeTest");
-        var pane = self.runtime.sharedInstance(Sources.EventListenerBreakpointsSidebarPane);
-        pane._removeBreakpoint("listener:click");
+        InspectorTest.setEventListenerBreakpoint("listener:click", false);
         InspectorTest.completeDebuggerTest();
     }
 }; 
diff --git a/third_party/WebKit/LayoutTests/inspector/sources/debugger-step/step-through-event-listeners.html b/third_party/WebKit/LayoutTests/inspector/sources/debugger-step/step-through-event-listeners.html
index 54afaa43..4016b12 100644
--- a/third_party/WebKit/LayoutTests/inspector/sources/debugger-step/step-through-event-listeners.html
+++ b/third_party/WebKit/LayoutTests/inspector/sources/debugger-step/step-through-event-listeners.html
@@ -28,11 +28,10 @@
 
 function test()
 {
-    var pane = self.runtime.sharedInstance(Sources.EventListenerBreakpointsSidebarPane);
     InspectorTest.runDebuggerTestSuite([
         function testClickBreakpoint(next)
         {
-            pane._setBreakpoint("listener:click");
+            InspectorTest.setEventListenerBreakpoint("listener:click", true);
             InspectorTest.waitUntilPaused(paused1);
             InspectorTest.evaluateInPageWithTimeout("addListenerAndClick()");
 
@@ -72,7 +71,7 @@
             function paused4(callFrames)
             {
                 InspectorTest.captureStackTrace(callFrames);
-                pane._removeBreakpoint("listener:click");
+                InspectorTest.setEventListenerBreakpoint("listener:click", false);
                 InspectorTest.resumeExecution(next);
             }
         }
diff --git a/third_party/WebKit/LayoutTests/inspector/sources/debugger-ui/break-on-empty-event-listener.html b/third_party/WebKit/LayoutTests/inspector/sources/debugger-ui/break-on-empty-event-listener.html
index 1e425a8..6d1405b 100644
--- a/third_party/WebKit/LayoutTests/inspector/sources/debugger-ui/break-on-empty-event-listener.html
+++ b/third_party/WebKit/LayoutTests/inspector/sources/debugger-ui/break-on-empty-event-listener.html
@@ -21,20 +21,19 @@
 
 var test = function()
 {
-    var pane = self.runtime.sharedInstance(Sources.EventListenerBreakpointsSidebarPane);
     InspectorTest.startDebuggerTest(step1, true);
 
     function step1()
     {
         var actions = [ "Print" ];
         InspectorTest.waitUntilPausedAndPerformSteppingActions(actions, step2);
-        pane._setBreakpoint("listener:click");
+        InspectorTest.setEventListenerBreakpoint("listener:click", true);
         InspectorTest.evaluateInPageWithTimeout("addEmptyEventListenerAndClick()");
     }
 
     function step2()
     {
-        pane._removeBreakpoint("listener:click");
+        InspectorTest.setEventListenerBreakpoint("listener:click", false);
         InspectorTest.completeDebuggerTest();
     }
 }
diff --git a/third_party/WebKit/LayoutTests/inspector/sources/debugger-ui/break-on-set-timeout-with-syntax-error.html b/third_party/WebKit/LayoutTests/inspector/sources/debugger-ui/break-on-set-timeout-with-syntax-error.html
index 42ed9f3..494836ef 100644
--- a/third_party/WebKit/LayoutTests/inspector/sources/debugger-ui/break-on-set-timeout-with-syntax-error.html
+++ b/third_party/WebKit/LayoutTests/inspector/sources/debugger-ui/break-on-set-timeout-with-syntax-error.html
@@ -21,7 +21,7 @@
 
     function step1()
     {
-        self.runtime.sharedInstance(Sources.EventListenerBreakpointsSidebarPane)._setBreakpoint("instrumentation:timerFired");
+        InspectorTest.setEventListenerBreakpoint("instrumentation:setTimeout.callback", true);
         InspectorTest.evaluateInPage("runSetTimeoutWithSyntaxError()", InspectorTest.waitUntilMessageReceived.bind(this, step2));
     }
 
@@ -34,7 +34,7 @@
 
     function step3()
     {
-        self.runtime.sharedInstance(Sources.EventListenerBreakpointsSidebarPane)._removeBreakpoint("instrumentation:timerFired");
+        InspectorTest.setEventListenerBreakpoint("instrumentation:setTimeout.callback", false);
         InspectorTest.completeDebuggerTest();
     }
 }
diff --git a/third_party/WebKit/LayoutTests/printing/print-close-crash.html b/third_party/WebKit/LayoutTests/printing/print-close-crash.html
index f588cca..2af8367 100644
--- a/third_party/WebKit/LayoutTests/printing/print-close-crash.html
+++ b/third_party/WebKit/LayoutTests/printing/print-close-crash.html
@@ -6,7 +6,7 @@
     testRunner.setCanOpenWindows();
     testRunner.setCloseRemainingWindowsWhenComplete(true);
 }
-var w = window.open("data:text/html,foo");
+var w = window.open("about:blank");
 w.print();
 w.close();
 
diff --git a/third_party/WebKit/LayoutTests/svg/as-object/history-navigation.html b/third_party/WebKit/LayoutTests/svg/as-object/history-navigation.html
index c04c90b..d56429e 100644
--- a/third_party/WebKit/LayoutTests/svg/as-object/history-navigation.html
+++ b/third_party/WebKit/LayoutTests/svg/as-object/history-navigation.html
@@ -13,7 +13,7 @@
     } else {
         // Navigate a timeout to make sure we generate a history entry that we
         // can go back to.
-        setTimeout(function() {location.href = 'data:text/html,<script>history.back();</' + 'script>';}, 0);
+        setTimeout(function() {location.href = '../../resources/back.html';}, 0);
         sessionStorage.didNav = true;
     }
 }
diff --git a/third_party/WebKit/LayoutTests/webaudio/Oscillator/oscillator-late-start.html b/third_party/WebKit/LayoutTests/webaudio/Oscillator/oscillator-late-start.html
index 6d757bca..f73424b 100644
--- a/third_party/WebKit/LayoutTests/webaudio/Oscillator/oscillator-late-start.html
+++ b/third_party/WebKit/LayoutTests/webaudio/Oscillator/oscillator-late-start.html
@@ -6,23 +6,79 @@
   <script src="../../resources/testharnessreport.js"></script> 
   <script src="../resources/audit-util.js"></script>
   <script src="../resources/audit.js"></script>
-  <script src="../resources/late-start-testing.js"></script>
 </head>
 
 <body>
   <script>
-    var audit = Audit.createTaskRunner();
+    let audit = Audit.createTaskRunner();
 
-    var sampleRate = 44100;
+    let sampleRate = 44100;
+    let renderLength = 1;
 
-    var renderLength = 1;
-    
-    var context = new OfflineAudioContext(1, sampleRate * renderLength, sampleRate);
-    var osc = context.createOscillator();
+    let context;
+    let node;
 
-    // Test the oscillator node is rendered correctly when the start time of start() 
-    // call is in the past in terms of the context time.
-    runLateStartTest(audit, context, osc);
+    // Test the oscillator node is rendered correctly when the start time of
+    // start() call is in the past in terms of the context time.
+    audit.define('initialize', (task, should) => {
+      should(
+          () => context =
+              new OfflineAudioContext(1, sampleRate * renderLength, sampleRate),
+          'Creating offline context for testing')
+          .notThrow();
+      should(() => {
+        // Set up a dummy signal path to keep the audio context running and
+        // spend processing time before calling start(0).
+        let osc = context.createOscillator();
+        let silent = context.createGain();
+
+        osc.connect(silent);
+        silent.connect(context.destination);
+        silent.gain.setValueAtTime(0.0, 0);
+        osc.start();
+
+        node = context.createOscillator();
+        node.connect(context.destination);
+      }, 'Creating graph for testing').notThrow();
+      task.done();
+    });
+
+    audit.define('test-late-start', (task, should) => {
+      // The node's start time will be clamped to the render quantum boundary
+      // >0.1 sec. Thus the rendered buffer will have non-zero frames.
+      // See issue: crbug.com/462167
+      let suspendTime = 0.1;
+      let suspendFrame = 128 * Math.floor(0.1 * context.sampleRate / 128);
+
+      context.suspend(suspendTime).then(() => {
+        node.start(0);
+        context.resume();
+      });
+
+      // Start rendering and verify result: this verifies if 1) the rendered
+      // buffer contains at least one non-zero value and 2) the non-zero value
+      // is found later than the first output sample.
+      context.startRendering()
+          .then(buffer => {
+            let channelData = buffer.getChannelData(0);
+            let nonZeroValueIndex = channelData.findIndex(x => x != 0);
+
+            should(
+                nonZeroValueIndex,
+                'The index (' + nonZeroValueIndex +
+                    ') of first non-zero output value')
+                .beGreaterThanOrEqualTo(suspendFrame);
+
+            should(
+                channelData.slice(0, suspendFrame),
+                'Output[0:' + (suspendFrame - 1) + ']')
+                .beConstantValueOf(0);
+          })
+          .then(() => task.done());
+      ;
+    });
+
+    audit.run();
   </script>
 </body>
 
diff --git a/third_party/WebKit/LayoutTests/webaudio/constructor/audiobuffer.html b/third_party/WebKit/LayoutTests/webaudio/constructor/audiobuffer.html
index 1979c04..3274d97 100644
--- a/third_party/WebKit/LayoutTests/webaudio/constructor/audiobuffer.html
+++ b/third_party/WebKit/LayoutTests/webaudio/constructor/audiobuffer.html
@@ -5,252 +5,197 @@
     <script src="../../resources/testharness.js"></script>
     <script src="../../resources/testharnessreport.js"></script>
     <script src="../resources/audit-util.js"></script>
-    <script src="../resources/audio-testing.js"></script>
+    <script src="../resources/audit.js"></script>
+    <script src="new-audionodeoptions.js"></script>
   </head>
 
   <body>
     <script>
-      var context;
+      let context;
 
-      var audit = Audit.createTaskRunner();
+      let audit = Audit.createTaskRunner();
 
-      audit.defineTask("initialize", function (taskDone) {
-        Should("context = new OfflineAudioContext(...)", function () {
-          context = new OfflineAudioContext(1, 1, 48000);
-        }).notThrow();
-
-        taskDone();
+      audit.define('initialize', (task, should) => {
+        context = initializeContext(should);
+        task.done();
       });
 
-      audit.defineTask("invalid constructor", function (taskDone) {
-        var node;
-        var args;
-        var success = true;
+      audit.define('invalid constructor', (task, should) => {
+        should(() => {
+          new AudioBuffer();
+        }, 'new AudioBuffer()').throw('TypeError');
+        should(() => {
+          new AudioBuffer(1);
+        }, 'new AudioBuffer(1)').throw('TypeError');
+        should(() => {
+          new AudioBuffer(Date, 42);
+        }, 'new AudioBuffer(Date, 42)').throw('TypeError');
 
-        args = [];
-        success = Should("new AudioBuffer()", function () {
-          node = new AudioBuffer();
-        }).throw("TypeError");
-        success = Should("new AudioBuffer(1)", function () {
-          node = new AudioBuffer(1) && success;
-        }).throw("TypeError");
-        success = Should("new AudioBuffer(Date, 42)", function () {
-          node = new AudioBuffer(Date, 42) && success;
-        }).throw("TypeError");
-
-        Should("Invalid constructors", success)
-          .summarize(
-            "correctly threw errors",
-            "did not throw errors in all cases");
-
-        taskDone();
+        task.done();
       });
 
-      audit.defineTask("required options", function (taskDone) {
-        var success = true;
-
-        var buffer;
+      audit.define('required options', (task, should) => {
+        let buffer;
 
         // The length and sampleRate attributes are required; all others are
         // optional.
-        success = Should("buffer = new AudioBuffer({})", function () {
-          var buffer = new AudioBuffer({});
-        }).throw("TypeError");
+        should(() => {
+          new AudioBuffer({});
+        }, 'buffer = new AudioBuffer({})').throw('TypeError');
 
-        success = Should("buffer = new AudioBuffer({length: 1})", function () {
-          new AudioBuffer({
-            length: 1
-          });
-        }).throw("TypeError") && success;
+        should(() => {
+          new AudioBuffer({length: 1});
+        }, 'buffer = new AudioBuffer({length: 1})').throw('TypeError');
 
-        success = Should("buffer = new AudioBuffer({sampleRate: 48000})",
-          function () {
-            new AudioBuffer({
-              sampleRate: 48000
-            });
-          }).throw("TypeError") && success;
+        should(() => {
+          new AudioBuffer({sampleRate: 48000});
+        }, 'buffer = new AudioBuffer({sampleRate: 48000})').throw('TypeError');
 
-        success = Should("buffer = new AudioBuffer({numberOfChannels: 1}",
-          function () {
-            buffer = new AudioBuffer({
-              numberOfChannels: 1
-            });
-          }).throw("TypeError") && success;
+        should(() => {
+          buffer = new AudioBuffer({numberOfChannels: 1});
+        }, 'buffer = new AudioBuffer({numberOfChannels: 1}').throw('TypeError');
 
         // Length and sampleRate are required, but others are optional.
-        success = Should(
-          "buffer = new AudioBuffer({length: 21, sampleRate: 48000}",
-          function () {
-            buffer = new AudioBuffer({
-              length: 21,
-              sampleRate: context.sampleRate
-            });
-          }).notThrow() && success;
+        should(
+            () => {
+              buffer =
+                  new AudioBuffer({length: 21, sampleRate: context.sampleRate});
+            },
+            'buffer0 = new AudioBuffer({length: 21, sampleRate: ' +
+                context.sampleRate + '}')
+            .notThrow();
         // Verify the buffer has the correct values.
-        success = Should("buffer.numberOfChannels", buffer.numberOfChannels)
-          .beEqualTo(1) && success;
-        success = Should("buffer.length", buffer.length)
-          .beEqualTo(21) && success;
-        success = Should("buffer.sampleRate", buffer.sampleRate)
-          .beEqualTo(context.sampleRate) && success;
+        should(buffer.numberOfChannels, 'buffer0.numberOfChannels')
+            .beEqualTo(1);
+        should(buffer.length, 'buffer0.length').beEqualTo(21);
+        should(buffer.sampleRate, 'buffer0.sampleRate')
+            .beEqualTo(context.sampleRate);
 
-        success = Should(
-          "buffer = new AudioBuffer({numberOfChannels: 1, length: 1, sampleRate: 48000}",
-          function () {
-            buffer = new AudioBuffer({
-              numberOfChannels: 1,
-              length: 1,
-              sampleRate: 48000
-            });
-          }).notThrow() && success;
+        should(
+            () => {
+              buffer = new AudioBuffer(
+                  {numberOfChannels: 3, length: 1, sampleRate: 48000});
+            },
+            'buffer1 = new AudioBuffer(' +
+                '{numberOfChannels: 3, length: 1, sampleRate: 48000})')
+            .notThrow();
+        // Verify the buffer has the correct values.
+        should(buffer.numberOfChannels, 'buffer1.numberOfChannels')
+            .beEqualTo(3);
+        should(buffer.length, 'buffer1.length').beEqualTo(1);
+        should(buffer.sampleRate, 'buffer1.sampleRate').beEqualTo(48000);
 
-        Should("Missing option values handled", success)
-          .summarize("correctly", "incorrectly");
-
-        taskDone();
+        task.done();
       });
 
-      audit.defineTask("invalid option values", function (taskDone) {
-        var success = true;
+      audit.define('invalid option values', (task, should) => {
+        let options = {numberOfChannels: 0, length: 1, sampleRate: 16000};
+        should(
+            () => {
+              let buffer = new AudioBuffer(options);
+            },
+            'new AudioBuffer(' + JSON.stringify(options) + ')')
+            .throw('NotSupportedError');
 
-        var options = {
-          numberOfChannels: 0,
-          length: 1,
-          sampleRate: 16000
-        };
-        success = Should("new AudioBuffer(" + JSON.stringify(options) + ")", function () {
-          var buffer = new AudioBuffer(options);
-        }).throw("NotSupportedError");
+        options = {numberOfChannels: 99, length: 0, sampleRate: 16000};
+        should(
+            () => {
+              let buffer = new AudioBuffer(options);
+            },
+            'new AudioBuffer(' + JSON.stringify(options) + ')')
+            .throw('NotSupportedError');
 
-        options = {
-          numberOfChannels: 99,
-          length: 0,
-          sampleRate: 16000
-        };
-        success = Should("new AudioBuffer(" + JSON.stringify(options) + ")", function () {
-          var buffer = new AudioBuffer(options);
-        }).throw("NotSupportedError") && success;
+        options = {numberOfChannels: 1, length: 0, sampleRate: 16000};
+        should(
+            () => {
+              let buffer = new AudioBuffer(options);
+            },
+            'new AudioBuffer(' + JSON.stringify(options) + ')')
+            .throw('NotSupportedError');
 
-        options = {
-          numberOfChannels: 1,
-          length: 0,
-          sampleRate: 16000
-        };
-        success = Should("new AudioBuffer(" + JSON.stringify(options) + ")", function () {
-          var buffer = new AudioBuffer(options);
-        }).throw("NotSupportedError") && success;
+        options = {numberOfChannels: 1, length: 1, sampleRate: 100};
+        should(
+            () => {
+              let buffer = new AudioBuffer(options);
+            },
+            'new AudioBuffer(' + JSON.stringify(options) + ')')
+            .throw('NotSupportedError');
 
-        options = {
-          numberOfChannels: 1,
-          length: 1,
-          sampleRate: 100
-        };
-        success = Should("new AudioBuffer(" + JSON.stringify(options) + ")", function () {
-          var buffer = new AudioBuffer(options);
-        }).throw("NotSupportedError") && success;
-
-        Should("Invalid option values handled", success)
-          .summarize("correctly", "incorrectly");
-
-        taskDone();
+        task.done();
       });
 
-      audit.defineTask("default constructor", function (taskDone) {
-        var buffer;
-        var success = true;
+      audit.define('default constructor', (task, should) => {
+        let buffer;
 
-        var options = {
-          numberOfChannels: 5,
-          length: 17,
-          sampleRate: 16000
-        };
-        success = Should(
-          "buffer = new AudioBuffer(" + JSON.stringify(options) + ")",
-          function () {
-            buffer = new AudioBuffer(options);
-          }).notThrow();
+        let options = {numberOfChannels: 5, length: 17, sampleRate: 16000};
+        should(
+            () => {
+              buffer = new AudioBuffer(options);
+            },
+            'buffer = new AudioBuffer(' + JSON.stringify(options) + ')')
+            .notThrow();
 
-        success = Should("buffer.numberOfChannels", buffer.numberOfChannels)
-          .beEqualTo(options.numberOfChannels) && success;
-        success = Should("buffer.length", buffer.length)
-          .beEqualTo(options.length) && success;
-        success = Should("buffer.sampleRate", buffer.sampleRate)
-          .beEqualTo(16000) && success;
+        should(buffer.numberOfChannels, 'buffer.numberOfChannels')
+            .beEqualTo(options.numberOfChannels);
+        should(buffer.length, 'buffer.length').beEqualTo(options.length);
+        should(buffer.sampleRate, 'buffer.sampleRate').beEqualTo(16000);
 
-        Should("Default constructor values set", success)
-          .summarize("correctly", "incorrectly");
-
-        taskDone();
+        task.done();
       });
 
-      audit.defineTask("valid constructor", function (taskDone) {
-        var buffer;
-        var success = true;
+      audit.define('valid constructor', (task, should) => {
+        let buffer;
 
-        var options = {
-          numberOfChannels: 3,
-          length: 42,
-          sampleRate: 54321
-        };
+        let options = {numberOfChannels: 3, length: 42, sampleRate: 54321};
 
-        var message = "new AudioBuffer(" + JSON.stringify(options) + ")";
-        success = Should(message, function () {
+        let message = 'new AudioBuffer(' + JSON.stringify(options) + ')';
+        should(() => {
           buffer = new AudioBuffer(options);
-        }).notThrow();
+        }, message).notThrow();
 
-        success = Should("buffer.numberOfChannels", buffer.numberOfChannels)
-          .beEqualTo(options.numberOfChannels) && success;
+        should(buffer.numberOfChannels, 'buffer.numberOfChannels')
+            .beEqualTo(options.numberOfChannels);
 
-        success = Should("buffer.length", buffer.length)
-          .beEqualTo(options.length) && success;
+        should(buffer.length, 'buffer.length').beEqualTo(options.length);
 
-        success = Should("buffer.sampleRate", buffer.sampleRate)
-          .beEqualTo(options.sampleRate) && success;
+        should(buffer.sampleRate, 'buffer.sampleRate')
+            .beEqualTo(options.sampleRate);
 
         // Verify that we actually got the right number of channels
-        for (var k = 0; k < options.numberOfChannels; ++k) {
-          var data;
-          var message = "buffer.getChannelData(" + k + ")";
-          success = Should(message, function () {
+        for (let k = 0; k < options.numberOfChannels; ++k) {
+          let data;
+          let message = 'buffer.getChannelData(' + k + ')';
+          should(() => {
             data = buffer.getChannelData(k);
-          }).notThrow() && success;
+          }, message).notThrow();
 
-          success = Should(message + " length", data.length)
-            .beEqualTo(options.length) && success;
+          should(data.length, message + ' length').beEqualTo(options.length);
         }
 
-        Should("buffer.getChannelData(" + options.numberOfChannels + ")",
-          function () {
-            buffer.getChannelData(options.numberOfChannels);
-          }).throw("IndexSizeError") && success;
+        should(
+            () => {
+              buffer.getChannelData(options.numberOfChannels);
+            },
+            'buffer.getChannelData(' + options.numberOfChannels + ')')
+            .throw('IndexSizeError');
 
-        Should("AudioBuffer constructed", success)
-          .summarize("correctly", "incorrectly");
-
-        taskDone();
+        task.done();
       });
 
-      audit.defineTask("multiple contexts", function (taskDone) {
+      audit.define('multiple contexts', (task, should) => {
         // Test that an AudioBuffer can be used for different contexts.
-        var buffer = new AudioBuffer({
-          length: 128,
-          sampleRate: context.sampleRate
-        });
+        let buffer =
+            new AudioBuffer({length: 128, sampleRate: context.sampleRate});
 
-        var data = buffer.getChannelData(0);
-        for (var k = 0; k < data.length; ++k)
+        let data = buffer.getChannelData(0);
+        for (let k = 0; k < data.length; ++k)
           data[k] = 1 + k;
 
-        var c1 = new OfflineAudioContext(1, 128, context.sampleRate);
-        var c2 = new OfflineAudioContext(1, 128, context.sampleRate);
+        let c1 = new OfflineAudioContext(1, 128, context.sampleRate);
+        let c2 = new OfflineAudioContext(1, 128, context.sampleRate);
 
-
-        var s1 = new AudioBufferSourceNode(c1, {
-          buffer: buffer
-        });
-        var s2 = new AudioBufferSourceNode(c2, {
-          buffer: buffer
-        });
+        let s1 = new AudioBufferSourceNode(c1, {buffer: buffer});
+        let s2 = new AudioBufferSourceNode(c2, {buffer: buffer});
 
         s1.connect(c1.destination);
         s2.connect(c2.destination);
@@ -258,28 +203,27 @@
         s1.start();
         s2.start();
 
-        var c1Success = false;
-        var c2Success = false;
-
-        Promise.all([
-            c1.startRendering().then(function (resultBuffer) {
-              c1Success = Should("c1 result", resultBuffer.getChannelData(0))
-                .beEqualToArray(data);
-            }),
-            c2.startRendering().then(function (resultBuffer) {
-              c2Success = Should("c2 result", resultBuffer.getChannelData(0))
-                .beEqualToArray(data);
-            }),
-          ])
-          .then(function () {
-            Should("AudioBuffer shared between two different contexts",
-                c1Success && c2Success)
-              .summarize("correctly", "incorrectly");
-            taskDone();
-          });
+        Promise
+            .all([
+              c1.startRendering().then(function(resultBuffer) {
+                return should(resultBuffer.getChannelData(0), 'c1 result')
+                    .beEqualToArray(data);
+              }),
+              c2.startRendering().then(function(resultBuffer) {
+                return should(resultBuffer.getChannelData(0), 'c2 result')
+                    .beEqualToArray(data);
+              }),
+            ])
+            .then(returnValues => {
+              should(
+                  returnValues[0] && returnValues[1],
+                  'AudioBuffer shared between two different contexts')
+                  .message('correctly', 'incorrectly');
+              task.done();
+            });
       });
 
-      audit.runTasks();
+      audit.run();
     </script>
   </body>
 </html>
diff --git a/third_party/WebKit/LayoutTests/webaudio/constructor/audiobuffersource.html b/third_party/WebKit/LayoutTests/webaudio/constructor/audiobuffersource.html
index ce52578c..1311dd52 100644
--- a/third_party/WebKit/LayoutTests/webaudio/constructor/audiobuffersource.html
+++ b/third_party/WebKit/LayoutTests/webaudio/constructor/audiobuffersource.html
@@ -5,113 +5,71 @@
     <script src="../../resources/testharness.js"></script>
     <script src="../../resources/testharnessreport.js"></script>
     <script src="../resources/audit-util.js"></script>
-    <script src="../resources/audio-testing.js"></script>
-    <script src="audionodeoptions.js"></script>
+    <script src="../resources/audit.js"></script>
+    <script src="new-audionodeoptions.js"></script>
   </head>
 
   <body>
     <script>
-      var context;
+      let context;
 
-      var audit = Audit.createTaskRunner();
+      let audit = Audit.createTaskRunner();
 
-      audit.defineTask("initialize", function (taskDone) {
-        Should("context = new OfflineAudioContext(...)", function () {
-          context = new OfflineAudioContext(1, 1, 48000);
-        }).notThrow();
-
-        taskDone();
+      audit.define('initialize', (task, should) => {
+        context = initializeContext(should);
+        task.done();
       });
 
-      audit.defineTask("invalid constructor", function (taskDone) {
-        var node;
-        var success = true;
-
-        success = Should("new AudioBufferSourceNode()", function () {
-          node = new AudioBufferSourceNode();
-        }).throw("TypeError");
-        success = Should("new AudioBufferSourceNode(1)", function () {
-          node = new AudioBufferSourceNode(1) && success;
-        }).throw("TypeError");
-        success = Should("new AudioBufferSourceNode(c, 42)", function () {
-          node = new AudioBufferSourceNode(context, 42) && success;
-        }).throw("TypeError");
-
-        Should("Invalid constructors", success)
-          .summarize(
-            "correctly threw errors",
-            "did not throw errors in all cases");
-
-        taskDone();
+      audit.define('invalid constructor', (task, should) => {
+        testInvalidConstructor(should, 'AudioBufferSourceNode', context);
+        task.done();
       });
 
-      audit.defineTask("default constructor", function (taskDone) {
-        var node;
-        var success = true;
+      audit.define('default constructor', (task, should) => {
+        let prefix = 'node0';
+        let node =
+            testDefaultConstructor(should, 'AudioBufferSourceNode', context, {
+              prefix: prefix,
+              numberOfInputs: 0,
+              numberOfOutputs: 1,
+              channelCount: 2,
+              channelCountMode: 'max',
+              channelInterpretation: 'speakers'
+            });
 
-        success = Should("node = new AudioBufferSourceNode(c)", function () {
-          node = new AudioBufferSourceNode(context);
-        }).notThrow() && success;
+        testDefaultAttributes(should, node, prefix, [
+          {name: 'buffer', value: null},
+          {name: 'detune', value: 0},
+          {name: 'loop', value: false},
+          {name: 'loopEnd', value: 0.0},
+          {name: 'loopStart', value: 0.0},
+          {name: 'playbackRate', value: 1.0},
+        ]);
 
-        success = Should("node instanceof AudioBufferSourceNode",
-          node instanceof AudioBufferSourceNode).beEqualTo(true) && success;
-
-        success = Should("node0.buffer === null", node.buffer === null)
-          .beEqualTo(true) && success;
-
-        // This node using the factory method is used as a reference for the
-        // defautl values.
-        var factoryNode = context.createBufferSource();
-
-        var testAttributes = ["buffer", "detune", "loop", "loopEnd", "loopStart",
-        "playbackRate"];
-
-        for (var index in testAttributes) {
-          var name = testAttributes[index];
-
-          if (node[name] instanceof AudioParam) {
-            success = Should("node0." + name + ".value", node[name].value)
-              .beEqualTo(factoryNode[name].value) && success;
-          } else {
-          success = Should("node0." + name, node[name])
-          .beEqualTo(factoryNode[name]) && success;
-          }
-        }
-
-        Should("AudioBufferSourceNode constructed", success)
-          .summarize("correctly", "incorrectly");
-
-        taskDone();
+        task.done();
       });
 
-      audit.defineTask("nullable buffer", function (taskDone) {
-        var node;
-        var success = true;
+      audit.define('nullable buffer', (task, should) => {
+        let node;
+        let options = {buffer: null};
 
-        var options = { buffer: null };
-      
-        success = Should("node1 = new AudioBufferSourceNode(c, " + JSON.stringify(options), function () {
-          node = new AudioBufferSourceNode(context, options);
-        }).notThrow();
+        should(
+            () => {
+              node = new AudioBufferSourceNode(context, options);
+            },
+            'node1 = new AudioBufferSourceNode(c, ' + JSON.stringify(options))
+            .notThrow();
 
-        success = Should("node1.buffer", node.buffer)
-          .beEqualTo(null);
+        should(node.buffer, 'node1.buffer').beEqualTo(null);
 
-        Should("Null buffer in constructor handled", success)
-          .summarize(
-            "correctly",
-            "incorrectly");
-
-        taskDone();
+        task.done();
       });
 
-      audit.defineTask("constructor options", function (taskDone) {
-        var node;
-        var success = true;
+      audit.define('constructor options', (task, should) => {
+        let node;
+        let buffer = context.createBuffer(2, 1000, context.sampleRate);
 
-        var buffer = context.createBuffer(2, 1000, context.sampleRate);
-
-        var options = {
+        let options = {
           buffer: buffer,
           detune: .5,
           loop: true,
@@ -120,15 +78,16 @@
           playbackRate: .75
         };
 
-        message = "node = new AudioBufferSourceNode(c, " + JSON.stringify(options) + ")";
+        let message = 'node = new AudioBufferSourceNode(c, ' +
+            JSON.stringify(options) + ')';
 
-        success = Should(message, function () {
+        should(() => {
           node = new AudioBufferSourceNode(context, options);
-        }).notThrow();
+        }, message).notThrow();
 
         // Use the factory method to create an equivalent node and compare the
         // results from the constructor against this node.
-        var factoryNode = context.createBufferSource();
+        let factoryNode = context.createBufferSource();
         factoryNode.buffer = options.buffer;
         factoryNode.detune.value = options.detune;
         factoryNode.loop = options.loop;
@@ -136,26 +95,21 @@
         factoryNode.loopStart = options.loopStart;
         factoryNode.playbackRate.value = options.playbackRate;
 
-        success = Should("node2.buffer === buffer", node.buffer === buffer)
-          .beEqualTo(true) && success;
-        success = Should("node2.detune.value", node.detune.value)
-          .beEqualTo(factoryNode.detune.value) && success;
-        success = Should("node2.loop", node.loop)
-          .beEqualTo(factoryNode.loop) && success;
-        success = Should("node2.loopEnd", node.loopEnd)
-          .beEqualTo(factoryNode.loopEnd) && success;
-        success = Should("node2.loopStart", node.loopStart)
-          .beEqualTo(factoryNode.loopStart) && success;
-        success = Should("node2.playbackRate.value", node.playbackRate.value)
-          .beEqualTo(factoryNode.playbackRate.value) && success;
+        should(node.buffer === buffer, 'node2.buffer === buffer')
+            .beEqualTo(true);
+        should(node.detune.value, 'node2.detune.value')
+            .beEqualTo(factoryNode.detune.value);
+        should(node.loop, 'node2.loop').beEqualTo(factoryNode.loop);
+        should(node.loopEnd, 'node2.loopEnd').beEqualTo(factoryNode.loopEnd);
+        should(node.loopStart, 'node2.loopStart')
+            .beEqualTo(factoryNode.loopStart);
+        should(node.playbackRate.value, 'node2.playbackRate.value')
+            .beEqualTo(factoryNode.playbackRate.value);
 
-        Should("AudioBufferSource with options cosntructed", success)
-          .summarize("correctly", "incorrectly");
-        
-        taskDone();
+        task.done();
       });
 
-      audit.runTasks();
+      audit.run();
     </script>
   </body>
 </html>
diff --git a/third_party/WebKit/LayoutTests/webaudio/constructor/biquadfilter.html b/third_party/WebKit/LayoutTests/webaudio/constructor/biquadfilter.html
index 2518bcb2..37e96fa 100644
--- a/third_party/WebKit/LayoutTests/webaudio/constructor/biquadfilter.html
+++ b/third_party/WebKit/LayoutTests/webaudio/constructor/biquadfilter.html
@@ -5,8 +5,8 @@
     <script src="../../resources/testharness.js"></script>
     <script src="../../resources/testharnessreport.js"></script>
     <script src="../resources/audit-util.js"></script>
-    <script src="../resources/audio-testing.js"></script>
-    <script src="audionodeoptions.js"></script>
+    <script src="../resources/audit.js"></script>
+    <script src="new-audionodeoptions.js"></script>
   </head>
 
   <body>
@@ -15,110 +15,71 @@
 
       var audit = Audit.createTaskRunner();
 
-      audit.defineTask("initialize", function (taskDone) {
-        Should("context = new OfflineAudioContext(...)", function () {
-          context = new OfflineAudioContext(1, 1, 48000);
-        }).notThrow();
-
-        taskDone();
+      audit.define('initialize', (task, should) => {
+        context = initializeContext(should);
+        task.done();
       });
 
-      audit.defineTask("invalid constructor", function (taskDone) {
+      audit.define('invalid constructor', (task, should) => {
+        testInvalidConstructor(should, 'BiquadFilterNode', context);
+        task.done();
+      });
+
+      audit.define('default constructor', (task, should) => {
+        let prefix = 'node0';
+        let node = testDefaultConstructor(should, 'BiquadFilterNode', context, {
+          prefix: prefix,
+          numberOfInputs: 1,
+          numberOfOutputs: 1,
+          channelCount: 2,
+          channelCountMode: 'max',
+          channelInterpretation: 'speakers'
+        });
+
+        testDefaultAttributes(should, node, prefix, [
+          {name: 'type', value: 'lowpass'}, {name: 'Q', value: 1},
+          {name: 'detune', value: 0}, {name: 'frequency', value: 350},
+          {name: 'gain', value: 0.0}
+        ]);
+
+        task.done();
+      });
+
+      audit.define('test AudioNodeOptions', (task, should) => {
+        testAudioNodeOptions(should, context, 'BiquadFilterNode');
+        task.done();
+      });
+
+      audit.define('construct with options', (task, should) => {
         var node;
-        var success = true;
-
-        success = Should("new BiquadFilterNode()", function () {
-          node = new BiquadFilterNode();
-        }).throw("TypeError");
-        success = Should("new BiquadFilterNode(1)", function () {
-          node = new BiquadFilterNode(1) && success;
-        }).throw("TypeError");
-        success = Should("new BiquadFilterNode(context, 42)", function () {
-          node = new BiquadFilterNode(context, 42) && success;
-        }).throw("TypeError");
-
-        Should("Invalid constructors", success)
-          .summarize(
-            "correctly threw errors",
-            "did not throw errors in all cases");
-
-        taskDone();
-      });
-
-      audit.defineTask("default constructor", function (taskDone) {
-        var node;
-        var success = true;
-
-        success = Should("node = new BiquadFilterNode(context)", function () {
-          node = new BiquadFilterNode(context);
-        }).notThrow();
-        success = Should("node instanceof BiquadFilterNode", node instanceof BiquadFilterNode)
-          .beEqualTo(true) && success;
-
-        // Test if attributes are set correctly to the defaults
-        success = Should("node.type", node.type)
-          .beEqualTo("lowpass") && success;
-        success = Should("node.Q.value", node.Q.value)
-          .beEqualTo(1) && success;
-        success = Should("node.detune.value", node.detune.value)
-          .beEqualTo(0) && success;
-        success = Should("node.frequency.value", node.frequency.value)
-          .beEqualTo(350) &&
-          success;
-        success = Should("node.gain.value", node.gain.value)
-          .beEqualTo(0) && success;
-
-        Should("new BiquadFilterNode(context)", success)
-          .summarize(
-            "constructed node with correct attributes",
-            "did not construct correct node correctly")
-
-        taskDone();
-      });
-
-      audit.defineTask("test AudioNodeOptions", function (taskDone) {
-        testAudioNodeOptions(context, "BiquadFilterNode");
-        taskDone();
-      });
-
-      audit.defineTask("construct with options", function (taskDone) {
-        var node;
-        var success = true;
         var options = {
-          type: "highpass",
+          type: 'highpass',
           frequency: 512,
           detune: 1,
           Q: 5,
           gain: 3,
         };
 
-        success = Should("node = new BiquadFilterNode(..., " + JSON.stringify(options) + ")", function () {
-          node = new BiquadFilterNode(context, options);
-        }).notThrow();
+        should(
+            () => {
+              node = new BiquadFilterNode(context, options);
+            },
+            'node = new BiquadFilterNode(..., ' + JSON.stringify(options) + ')')
+            .notThrow();
 
         // Test that attributes are set according to the option values.
-        success = Should("node.type", node.type)
-          .beEqualTo(options.type) && success;
-        success = Should("node.frequency.value", node.frequency.value)
-          .beEqualTo(options.frequency) &&
-          success;
-        success = Should("node.detuen.value", node.detune.value)
-          .beEqualTo(options.detune) &&
-          success;
-        success = Should("node.Q.value", node.Q.value)
-          .beEqualTo(options.Q) && success;
-        success = Should("node.gain.value", node.gain.value)
-          .beEqualTo(options.gain) && success;
+        should(node.type, 'node.type').beEqualTo(options.type);
+        should(node.frequency.value, 'node.frequency.value')
+            .beEqualTo(options.frequency);
+        should(node.detune.value, 'node.detuen.value')
+            .beEqualTo(options.detune);
+        should(node.Q.value, 'node.Q.value').beEqualTo(options.Q);
+        should(node.gain.value, 'node.gain.value').beEqualTo(options.gain);
 
-        Should("new BiquadFilterNode() with options", success)
-          .summarize(
-            "constructed with correct attributes",
-            "was not constructed correctly");
-
-        taskDone();
+        task.done();
       });
 
-      audit.runTasks();
+      audit.run();
     </script>
   </body>
 </html>
diff --git a/third_party/WebKit/LayoutTests/webaudio/resources/late-start-testing.js b/third_party/WebKit/LayoutTests/webaudio/resources/late-start-testing.js
deleted file mode 100644
index 2d6dc0b0..0000000
--- a/third_party/WebKit/LayoutTests/webaudio/resources/late-start-testing.js
+++ /dev/null
@@ -1,47 +0,0 @@
-function runLateStartTest(audit, context, node) {
-
-  // Set up a dummy signal path to keep the audio context running and spend
-  // processing time before calling start(0).
-  var osc = context.createOscillator();
-  var silent = context.createGain();
-
-  osc.connect(silent);
-  silent.connect(context.destination);
-  silent.gain.setValueAtTime(0.0, 0);
-  osc.start();
-
-  node.connect(context.destination);
-
-  // Task: schedule a suspend and start rendering.
-  audit.define('test-late-start', (task, should) => {
-    // The node's start time will be clamped to the render quantum boundary
-    // >0.1 sec. Thus the rendered buffer will have non-zero frames.
-    // See issue: crbug.com/462167
-    context.suspend(0.1).then(() => {
-      node.start(0);
-      context.resume();
-    });
-
-    // Start rendering and verify result: this verifies if 1) the rendered
-    // buffer contains at least one non-zero value and 2) the non-zero value is
-    // found later than the first output sample.
-    context.startRendering().then(function (buffer) {
-      var nonZeroValueIndex = -1;
-      var channelData = buffer.getChannelData(0);
-      for (var i = 0; i < channelData.length; i++) {
-        if (channelData[i] !== 0) {
-          nonZeroValueIndex = i;
-          break;
-        }
-      }
-
-      should(nonZeroValueIndex, 'The index of first non-zero value')
-              .notBeEqualTo(-1);
-      should(channelData[0], 'The first sample value')
-          .beEqualTo(0);
-      task.done();
-    });
-  });
-
-  audit.run();
-}
diff --git a/third_party/WebKit/Source/bindings/core/v8/V8Binding.h b/third_party/WebKit/Source/bindings/core/v8/V8Binding.h
index be777490..cbd147f1 100644
--- a/third_party/WebKit/Source/bindings/core/v8/V8Binding.h
+++ b/third_party/WebKit/Source/bindings/core/v8/V8Binding.h
@@ -195,12 +195,6 @@
 }
 
 template <typename CallbackInfo, typename T>
-inline void V8SetReturnValue(const CallbackInfo& callback_info,
-                             PassRefPtr<T> impl) {
-  V8SetReturnValue(callback_info, impl.Get());
-}
-
-template <typename CallbackInfo, typename T>
 inline void V8SetReturnValue(const CallbackInfo& callbackInfo,
                              NotShared<T> notShared) {
   V8SetReturnValue(callbackInfo, notShared.View());
@@ -222,12 +216,6 @@
   V8SetReturnValue(callback_info, wrapper);
 }
 
-template <typename CallbackInfo, typename T>
-inline void V8SetReturnValueForMainWorld(const CallbackInfo& callback_info,
-                                         PassRefPtr<T> impl) {
-  V8SetReturnValueForMainWorld(callback_info, impl.Get());
-}
-
 template <typename CallbackInfo>
 inline void V8SetReturnValueFast(const CallbackInfo& callback_info,
                                  ScriptWrappable* impl,
@@ -252,13 +240,6 @@
                        wrappable);
 }
 
-template <typename CallbackInfo, typename T, typename Wrappable>
-inline void V8SetReturnValueFast(const CallbackInfo& callback_info,
-                                 PassRefPtr<T> impl,
-                                 const Wrappable* wrappable) {
-  V8SetReturnValueFast(callback_info, impl.Get(), wrappable);
-}
-
 template <typename CallbackInfo, typename T>
 inline void V8SetReturnValueFast(const CallbackInfo& callback_info,
                                  const v8::Local<T> handle,
diff --git a/third_party/WebKit/Source/core/dom/Document.cpp b/third_party/WebKit/Source/core/dom/Document.cpp
index 4b21ebd..829e3b3f 100644
--- a/third_party/WebKit/Source/core/dom/Document.cpp
+++ b/third_party/WebKit/Source/core/dom/Document.cpp
@@ -488,7 +488,7 @@
           this,
           &Document::UpdateFocusAppearanceTimerFired),
       css_target_(nullptr),
-      load_event_progress_(kLoadEventNotRun),
+      load_event_progress_(kLoadEventCompleted),
       start_time_(CurrentTime()),
       script_runner_(ScriptRunner::Create(this)),
       xml_version_("1.0"),
@@ -2770,9 +2770,6 @@
 
   if (frame_)
     frame_->Loader().DidExplicitOpen();
-  if (load_event_progress_ != kLoadEventInProgress &&
-      PageDismissalEventBeingDispatched() == kNoDismissal)
-    load_event_progress_ = kLoadEventNotRun;
 }
 
 void Document::DetachParser() {
@@ -2787,12 +2784,11 @@
   DetachParser();
   SetParsingState(kFinishedParsing);
   SetReadyState(kComplete);
+  SuppressLoadEvent();
 }
 
 DocumentParser* Document::ImplicitOpen(
     ParserSynchronizationPolicy parser_sync_policy) {
-  DetachParser();
-
   RemoveChildren();
   DCHECK(!focused_element_);
 
@@ -2806,11 +2802,16 @@
     parser_sync_policy = kForceSynchronousParsing;
   }
 
+  DetachParser();
   parser_sync_policy_ = parser_sync_policy;
   parser_ = CreateParser();
   DocumentParserTiming::From(*this).MarkParserStart();
   SetParsingState(kParsing);
   SetReadyState(kLoading);
+  if (load_event_progress_ != kLoadEventInProgress &&
+      PageDismissalEventBeingDispatched() == kNoDismissal) {
+    load_event_progress_ = kLoadEventNotRun;
+  }
 
   return parser_;
 }
@@ -2952,30 +2953,15 @@
       !GetScriptableDocumentParser()->IsParsing())
     return;
 
-  if (DocumentParser* parser = parser_)
-    parser->Finish();
-
-  if (!frame_) {
-    // Because we have no frame, we don't know if all loading has completed,
-    // so we just call implicitClose() immediately. FIXME: This might fire
-    // the load event prematurely
-    // <http://bugs.webkit.org/show_bug.cgi?id=14568>.
-    ImplicitClose();
-    return;
-  }
-
-  frame_->Loader().CheckCompleted();
+  parser_->Finish();
+  if (!parser_ || !parser_->IsParsing())
+    SetReadyState(kComplete);
+  CheckCompleted();
 }
 
 void Document::ImplicitClose() {
   DCHECK(!InStyleRecalc());
-  if (ProcessingLoadEvent() || !parser_)
-    return;
-  if (GetFrame() &&
-      GetFrame()->GetNavigationScheduler().LocationChangePending()) {
-    SuppressLoadEvent();
-    return;
-  }
+  DCHECK(parser_);
 
   load_event_progress_ = kLoadEventInProgress;
 
@@ -3057,6 +3043,69 @@
     AccessSVGExtensions().StartAnimations();
 }
 
+static bool AllDescendantsAreComplete(Frame* frame) {
+  if (!frame)
+    return true;
+  for (Frame* child = frame->Tree().FirstChild(); child;
+       child = child->Tree().TraverseNext(frame)) {
+    if (child->IsLoading())
+      return false;
+  }
+  return true;
+}
+
+bool Document::ShouldComplete() {
+  return parsing_state_ == kFinishedParsing && HaveImportsLoaded() &&
+         !fetcher_->BlockingRequestCount() && !IsDelayingLoadEvent() &&
+         load_event_progress_ != kLoadEventInProgress &&
+         AllDescendantsAreComplete(frame_);
+}
+
+void Document::CheckCompleted() {
+  if (!ShouldComplete())
+    return;
+
+  if (frame_) {
+    frame_->Client()->RunScriptsAtDocumentIdle();
+
+    // Injected scripts may have disconnected this frame.
+    if (!frame_)
+      return;
+
+    // Check again, because runScriptsAtDocumentIdle() may have delayed the load
+    // event.
+    if (!ShouldComplete())
+      return;
+  }
+
+  // OK, completed. Fire load completion events as needed.
+  SetReadyState(kComplete);
+  if (LoadEventStillNeeded())
+    ImplicitClose();
+
+  // The readystatechanged or load event may have disconnected this frame.
+  if (!frame_ || !frame_->IsAttached())
+    return;
+  frame_->GetNavigationScheduler().StartTimer();
+  View()->HandleLoadCompleted();
+  // The document itself is complete, but if a child frame was restarted due to
+  // an event, this document is still considered to be in progress.
+  if (!AllDescendantsAreComplete(frame_))
+    return;
+
+  // No need to repeat if we've already notified this load as finished.
+  if (!Loader()->SentDidFinishLoad()) {
+    if (frame_->IsMainFrame())
+      ViewportDescription().ReportMobilePageStats(frame_);
+    Loader()->SetSentDidFinishLoad();
+    frame_->Client()->DispatchDidFinishLoad();
+    if (!frame_)
+      return;
+  }
+
+  frame_->Loader().DidFinishNavigation();
+}
+
 bool Document::DispatchBeforeUnloadEvent(ChromeClient& chrome_client,
                                          bool is_reload,
                                          bool& did_allow_navigation) {
@@ -5988,8 +6037,8 @@
   DCHECK(load_event_delay_count_);
   --load_event_delay_count_;
 
-  if (!load_event_delay_count_ && GetFrame())
-    GetFrame()->Loader().CheckCompleted();
+  if (!load_event_delay_count_)
+    CheckCompleted();
 }
 
 void Document::CheckLoadEventSoon() {
@@ -6011,8 +6060,7 @@
 }
 
 void Document::LoadEventDelayTimerFired(TimerBase*) {
-  if (GetFrame())
-    GetFrame()->Loader().CheckCompleted();
+  CheckCompleted();
 }
 
 void Document::LoadPluginsSoon() {
diff --git a/third_party/WebKit/Source/core/dom/Document.h b/third_party/WebKit/Source/core/dom/Document.h
index 9da3667..9b0ab6e 100644
--- a/third_party/WebKit/Source/core/dom/Document.h
+++ b/third_party/WebKit/Source/core/dom/Document.h
@@ -566,8 +566,8 @@
   void close(ExceptionState&);
   // This is used internally and does not handle exceptions.
   void close();
-  // implicitClose() actually does the work of closing the input stream.
-  void ImplicitClose();
+
+  void CheckCompleted();
 
   bool DispatchBeforeUnloadEvent(ChromeClient&,
                                  bool is_reload,
@@ -680,9 +680,6 @@
   enum ParsingState { kParsing, kInDOMContentLoaded, kFinishedParsing };
   void SetParsingState(ParsingState);
   bool Parsing() const { return parsing_state_ == kParsing; }
-  bool IsInDOMContentLoaded() const {
-    return parsing_state_ == kInDOMContentLoaded;
-  }
   bool HasFinishedParsing() const { return parsing_state_ == kFinishedParsing; }
 
   bool ShouldScheduleLayout() const;
@@ -1050,9 +1047,6 @@
   bool LoadEventStillNeeded() const {
     return load_event_progress_ == kLoadEventNotRun;
   }
-  bool ProcessingLoadEvent() const {
-    return load_event_progress_ == kLoadEventInProgress;
-  }
   bool LoadEventFinished() const {
     return load_event_progress_ >= kLoadEventCompleted;
   }
@@ -1163,7 +1157,6 @@
   }
   HTMLImportLoader* ImportLoader() const;
 
-  bool HaveImportsLoaded() const;
   void DidLoadAllImports();
 
   void AdjustFloatQuadsForScrollAndAbsoluteZoom(Vector<FloatQuad>&,
@@ -1373,6 +1366,10 @@
   void UpdateStyle();
   void NotifyLayoutTreeOfSubtreeChanges();
 
+  // ImplicitClose() actually does the work of closing the input stream.
+  void ImplicitClose();
+  bool ShouldComplete();
+
   void DetachParser();
 
   void BeginLifecycleUpdatesIfRenderingReady();
@@ -1438,6 +1435,8 @@
   void RunExecutionContextTask(std::unique_ptr<ExecutionContextTask>,
                                bool instrumenting);
 
+  bool HaveImportsLoaded() const;
+
   DocumentLifecycle lifecycle_;
 
   bool has_nodes_with_placeholder_style_;
diff --git a/third_party/WebKit/Source/core/dom/DocumentTest.cpp b/third_party/WebKit/Source/core/dom/DocumentTest.cpp
index 6002eb4..2c81584 100644
--- a/third_party/WebKit/Source/core/dom/DocumentTest.cpp
+++ b/third_party/WebKit/Source/core/dom/DocumentTest.cpp
@@ -727,10 +727,10 @@
   MockValidationMessageClient* mock_client = new MockValidationMessageClient();
   GetDocument().GetSettings()->SetScriptEnabled(true);
   GetPage().SetValidationMessageClient(mock_client);
-  // implicitOpen()-implicitClose() makes Document.loadEventFinished()
+  // ImplicitOpen()-CancelParsing() makes Document.loadEventFinished()
   // true. It's necessary to kick unload process.
   GetDocument().ImplicitOpen(kForceSynchronousParsing);
-  GetDocument().ImplicitClose();
+  GetDocument().CancelParsing();
   GetDocument().AppendChild(GetDocument().createElement("html"));
   SetHtmlInnerHTML("<body><input required></body>");
   Element* script = GetDocument().createElement("script");
diff --git a/third_party/WebKit/Source/core/frame/LocalFrameClient.h b/third_party/WebKit/Source/core/frame/LocalFrameClient.h
index 44bae22..3d24e8b 100644
--- a/third_party/WebKit/Source/core/frame/LocalFrameClient.h
+++ b/third_party/WebKit/Source/core/frame/LocalFrameClient.h
@@ -236,6 +236,12 @@
   virtual void DidChangeScrollOffset() {}
   virtual void DidUpdateCurrentHistoryItem() {}
 
+  // Called when a content-initiated, main frame navigation to a data URL is
+  // about to occur.
+  virtual bool AllowContentInitiatedDataUrlNavigations(const KURL&) {
+    return false;
+  }
+
   virtual WebCookieJar* CookieJar() const = 0;
 
   virtual void DidChangeName(const String&) {}
diff --git a/third_party/WebKit/Source/core/html/imports/HTMLImportTreeRoot.cpp b/third_party/WebKit/Source/core/html/imports/HTMLImportTreeRoot.cpp
index ed535ae..e04c8af5b 100644
--- a/third_party/WebKit/Source/core/html/imports/HTMLImportTreeRoot.cpp
+++ b/third_party/WebKit/Source/core/html/imports/HTMLImportTreeRoot.cpp
@@ -51,10 +51,8 @@
 void HTMLImportTreeRoot::StateDidChange() {
   HTMLImport::StateDidChange();
 
-  if (!GetState().IsReady())
-    return;
-  if (LocalFrame* frame = document_->GetFrame())
-    frame->Loader().CheckCompleted();
+  if (GetState().IsReady())
+    document_->CheckCompleted();
 }
 
 void HTMLImportTreeRoot::ScheduleRecalcState() {
diff --git a/third_party/WebKit/Source/core/inspector/BUILD.gn b/third_party/WebKit/Source/core/inspector/BUILD.gn
index 417c1c2..66150a0 100644
--- a/third_party/WebKit/Source/core/inspector/BUILD.gn
+++ b/third_party/WebKit/Source/core/inspector/BUILD.gn
@@ -148,12 +148,12 @@
     "inspector/protocol/Memory.h",
     "inspector/protocol/Network.cpp",
     "inspector/protocol/Network.h",
+    "inspector/protocol/Overlay.cpp",
+    "inspector/protocol/Overlay.h",
     "inspector/protocol/Page.cpp",
     "inspector/protocol/Page.h",
     "inspector/protocol/Protocol.cpp",
     "inspector/protocol/Protocol.h",
-    "inspector/protocol/Rendering.cpp",
-    "inspector/protocol/Rendering.h",
     "inspector/protocol/Runtime.h",
     "inspector/protocol/Security.cpp",
     "inspector/protocol/Security.h",
diff --git a/third_party/WebKit/Source/core/inspector/InspectorDOMAgent.cpp b/third_party/WebKit/Source/core/inspector/InspectorDOMAgent.cpp
index a9a2e62..9a66426 100644
--- a/third_party/WebKit/Source/core/inspector/InspectorDOMAgent.cpp
+++ b/third_party/WebKit/Source/core/inspector/InspectorDOMAgent.cpp
@@ -77,6 +77,7 @@
 #include "core/page/Page.h"
 #include "core/xml/DocumentXPathEvaluator.h"
 #include "core/xml/XPathResult.h"
+#include "platform/graphics/Color.h"
 #include "platform/wtf/ListHashSet.h"
 #include "platform/wtf/PtrUtil.h"
 #include "platform/wtf/text/CString.h"
@@ -97,38 +98,6 @@
 const size_t kMaxTextSize = 10000;
 const UChar kEllipsisUChar[] = {0x2026, 0};
 
-Color ParseColor(protocol::DOM::RGBA* rgba) {
-  if (!rgba)
-    return Color::kTransparent;
-
-  int r = rgba->getR();
-  int g = rgba->getG();
-  int b = rgba->getB();
-  if (!rgba->hasA())
-    return Color(r, g, b);
-
-  double a = rgba->getA(1);
-  // Clamp alpha to the [0..1] range.
-  if (a < 0)
-    a = 0;
-  else if (a > 1)
-    a = 1;
-
-  return Color(r, g, b, static_cast<int>(a * 255));
-}
-
-bool ParseQuad(std::unique_ptr<protocol::Array<double>> quad_array,
-               FloatQuad* quad) {
-  const size_t kCoordinatesInQuad = 8;
-  if (!quad_array || quad_array->length() != kCoordinatesInQuad)
-    return false;
-  quad->SetP1(FloatPoint(quad_array->get(0), quad_array->get(1)));
-  quad->SetP2(FloatPoint(quad_array->get(2), quad_array->get(3)));
-  quad->SetP3(FloatPoint(quad_array->get(4), quad_array->get(5)));
-  quad->SetP4(FloatPoint(quad_array->get(6), quad_array->get(7)));
-  return true;
-}
-
 }  // namespace
 
 class InspectorRevalidateDOMTask final
@@ -234,20 +203,38 @@
   }
 }
 
+// static
+Color InspectorDOMAgent::ParseColor(protocol::DOM::RGBA* rgba) {
+  if (!rgba)
+    return Color::kTransparent;
+
+  int r = rgba->getR();
+  int g = rgba->getG();
+  int b = rgba->getB();
+  if (!rgba->hasA())
+    return Color(r, g, b);
+
+  double a = rgba->getA(1);
+  // Clamp alpha to the [0..1] range.
+  if (a < 0)
+    a = 0;
+  else if (a > 1)
+    a = 1;
+
+  return Color(r, g, b, static_cast<int>(a * 255));
+}
+
 InspectorDOMAgent::InspectorDOMAgent(
     v8::Isolate* isolate,
     InspectedFrames* inspected_frames,
-    v8_inspector::V8InspectorSession* v8_session,
-    Client* client)
+    v8_inspector::V8InspectorSession* v8_session)
     : isolate_(isolate),
       inspected_frames_(inspected_frames),
       v8_session_(v8_session),
-      client_(client),
       dom_listener_(nullptr),
       document_node_to_id_map_(new NodeToIdMap()),
       last_node_id_(1),
-      suppress_attribute_modified_event_(false),
-      backend_node_id_to_inspect_(0) {}
+      suppress_attribute_modified_event_(false) {}
 
 InspectorDOMAgent::~InspectorDOMAgent() {}
 
@@ -438,9 +425,6 @@
   dom_editor_ = new DOMEditor(history_.Get());
   document_ = inspected_frames_->Root()->GetDocument();
   instrumenting_agents_->addInspectorDOMAgent(this);
-  if (backend_node_id_to_inspect_)
-    GetFrontend()->inspectNodeRequested(backend_node_id_to_inspect_);
-  backend_node_id_to_inspect_ = 0;
 }
 
 Response InspectorDOMAgent::enable() {
@@ -457,7 +441,6 @@
   if (!Enabled())
     return Response::Error("DOM agent hasn't been enabled");
   state_->setBoolean(DOMAgentState::kDomAgentEnabled, false);
-  SetSearchingForNode(kNotSearching, Maybe<protocol::DOM::HighlightConfig>());
   instrumenting_agents_->removeInspectorDOMAgent(this);
   history_.Clear();
   dom_editor_.Clear();
@@ -1124,155 +1107,8 @@
   return Response::OK();
 }
 
-void InspectorDOMAgent::Inspect(Node* inspected_node) {
-  if (!inspected_node)
-    return;
-
-  Node* node = inspected_node;
-  while (node && !node->IsElementNode() && !node->IsDocumentNode() &&
-         !node->IsDocumentFragment())
-    node = node->ParentOrShadowHostNode();
-  if (!node)
-    return;
-
-  int backend_node_id = DOMNodeIds::IdForNode(node);
-  if (!GetFrontend() || !Enabled()) {
-    backend_node_id_to_inspect_ = backend_node_id;
-    return;
-  }
-
-  GetFrontend()->inspectNodeRequested(backend_node_id);
-}
-
-void InspectorDOMAgent::NodeHighlightedInOverlay(Node* node) {
-  if (!GetFrontend() || !Enabled())
-    return;
-
-  while (node && !node->IsElementNode() && !node->IsDocumentNode() &&
-         !node->IsDocumentFragment())
-    node = node->ParentOrShadowHostNode();
-
-  if (!node)
-    return;
-
-  int node_id = PushNodePathToFrontend(node);
-  GetFrontend()->nodeHighlightRequested(node_id);
-}
-
-Response InspectorDOMAgent::SetSearchingForNode(
-    SearchMode search_mode,
-    Maybe<protocol::DOM::HighlightConfig> highlight_inspector_object) {
-  if (!client_)
-    return Response::OK();
-  if (search_mode == kNotSearching) {
-    client_->SetInspectMode(kNotSearching, nullptr);
-    return Response::OK();
-  }
-  std::unique_ptr<InspectorHighlightConfig> config;
-  Response response = HighlightConfigFromInspectorObject(
-      std::move(highlight_inspector_object), &config);
-  if (!response.isSuccess())
-    return response;
-  client_->SetInspectMode(search_mode, std::move(config));
-  return Response::OK();
-}
-
-Response InspectorDOMAgent::HighlightConfigFromInspectorObject(
-    Maybe<protocol::DOM::HighlightConfig> highlight_inspector_object,
-    std::unique_ptr<InspectorHighlightConfig>* out_config) {
-  if (!highlight_inspector_object.isJust()) {
-    return Response::Error(
-        "Internal error: highlight configuration parameter is missing");
-  }
-
-  protocol::DOM::HighlightConfig* config =
-      highlight_inspector_object.fromJust();
-  std::unique_ptr<InspectorHighlightConfig> highlight_config =
-      WTF::MakeUnique<InspectorHighlightConfig>();
-  highlight_config->show_info = config->getShowInfo(false);
-  highlight_config->show_rulers = config->getShowRulers(false);
-  highlight_config->show_extension_lines = config->getShowExtensionLines(false);
-  highlight_config->display_as_material = config->getDisplayAsMaterial(false);
-  highlight_config->content = ParseColor(config->getContentColor(nullptr));
-  highlight_config->padding = ParseColor(config->getPaddingColor(nullptr));
-  highlight_config->border = ParseColor(config->getBorderColor(nullptr));
-  highlight_config->margin = ParseColor(config->getMarginColor(nullptr));
-  highlight_config->event_target =
-      ParseColor(config->getEventTargetColor(nullptr));
-  highlight_config->shape = ParseColor(config->getShapeColor(nullptr));
-  highlight_config->shape_margin =
-      ParseColor(config->getShapeMarginColor(nullptr));
-  highlight_config->selector_list = config->getSelectorList("");
-
-  *out_config = std::move(highlight_config);
-  return Response::OK();
-}
-
-Response InspectorDOMAgent::setInspectMode(
-    const String& mode,
-    Maybe<protocol::DOM::HighlightConfig> highlight_config) {
-  SearchMode search_mode;
-  if (mode == protocol::DOM::InspectModeEnum::SearchForNode) {
-    search_mode = kSearchingForNormal;
-  } else if (mode == protocol::DOM::InspectModeEnum::SearchForUAShadowDOM) {
-    search_mode = kSearchingForUAShadow;
-  } else if (mode == protocol::DOM::InspectModeEnum::None) {
-    search_mode = kNotSearching;
-  } else {
-    return Response::Error(
-        String("Unknown mode \"" + mode + "\" was provided."));
-  }
-
-  if (search_mode != kNotSearching) {
-    Response response = PushDocumentUponHandlelessOperation();
-    if (!response.isSuccess())
-      return response;
-  }
-
-  return SetSearchingForNode(search_mode, std::move(highlight_config));
-}
-
-Response InspectorDOMAgent::highlightRect(
-    int x,
-    int y,
-    int width,
-    int height,
-    Maybe<protocol::DOM::RGBA> color,
-    Maybe<protocol::DOM::RGBA> outline_color) {
-  std::unique_ptr<FloatQuad> quad =
-      WTF::WrapUnique(new FloatQuad(FloatRect(x, y, width, height)));
-  InnerHighlightQuad(std::move(quad), std::move(color),
-                     std::move(outline_color));
-  return Response::OK();
-}
-
-Response InspectorDOMAgent::highlightQuad(
-    std::unique_ptr<protocol::Array<double>> quad_array,
-    Maybe<protocol::DOM::RGBA> color,
-    Maybe<protocol::DOM::RGBA> outline_color) {
-  std::unique_ptr<FloatQuad> quad = WTF::MakeUnique<FloatQuad>();
-  if (!ParseQuad(std::move(quad_array), quad.get()))
-    return Response::Error("Invalid Quad format");
-  InnerHighlightQuad(std::move(quad), std::move(color),
-                     std::move(outline_color));
-  return Response::OK();
-}
-
-void InspectorDOMAgent::InnerHighlightQuad(
-    std::unique_ptr<FloatQuad> quad,
-    Maybe<protocol::DOM::RGBA> color,
-    Maybe<protocol::DOM::RGBA> outline_color) {
-  std::unique_ptr<InspectorHighlightConfig> highlight_config =
-      WTF::MakeUnique<InspectorHighlightConfig>();
-  highlight_config->content = ParseColor(color.fromMaybe(nullptr));
-  highlight_config->content_outline =
-      ParseColor(outline_color.fromMaybe(nullptr));
-  if (client_)
-    client_->HighlightQuad(std::move(quad), *highlight_config);
-}
-
-Response InspectorDOMAgent::NodeForRemoteId(const String& object_id,
-                                            Node*& node) {
+Response InspectorDOMAgent::NodeForRemoteObjectId(const String& object_id,
+                                                  Node*& node) {
   v8::HandleScope handles(isolate_);
   v8::Local<v8::Value> value;
   v8::Local<v8::Context> context;
@@ -1290,66 +1126,6 @@
   return Response::OK();
 }
 
-Response InspectorDOMAgent::highlightNode(
-    std::unique_ptr<protocol::DOM::HighlightConfig> highlight_inspector_object,
-    Maybe<int> node_id,
-    Maybe<int> backend_node_id,
-    Maybe<String> object_id) {
-  Node* node = nullptr;
-  Response response;
-  if (node_id.isJust()) {
-    response = AssertNode(node_id.fromJust(), node);
-  } else if (backend_node_id.isJust()) {
-    node = DOMNodeIds::NodeForId(backend_node_id.fromJust());
-    response = !node ? Response::Error("No node found for given backend id")
-                     : Response::OK();
-  } else if (object_id.isJust()) {
-    response = NodeForRemoteId(object_id.fromJust(), node);
-  } else {
-    response = Response::Error("Either nodeId or objectId must be specified");
-  }
-
-  if (!response.isSuccess())
-    return response;
-
-  std::unique_ptr<InspectorHighlightConfig> highlight_config;
-  response = HighlightConfigFromInspectorObject(
-      std::move(highlight_inspector_object), &highlight_config);
-  if (!response.isSuccess())
-    return response;
-
-  if (client_)
-    client_->HighlightNode(node, *highlight_config, false);
-  return Response::OK();
-}
-
-Response InspectorDOMAgent::highlightFrame(
-    const String& frame_id,
-    Maybe<protocol::DOM::RGBA> color,
-    Maybe<protocol::DOM::RGBA> outline_color) {
-  LocalFrame* frame =
-      IdentifiersFactory::FrameById(inspected_frames_, frame_id);
-  // FIXME: Inspector doesn't currently work cross process.
-  if (frame && frame->DeprecatedLocalOwner()) {
-    std::unique_ptr<InspectorHighlightConfig> highlight_config =
-        WTF::MakeUnique<InspectorHighlightConfig>();
-    highlight_config->show_info = true;  // Always show tooltips for frames.
-    highlight_config->content = ParseColor(color.fromMaybe(nullptr));
-    highlight_config->content_outline =
-        ParseColor(outline_color.fromMaybe(nullptr));
-    if (client_)
-      client_->HighlightNode(frame->DeprecatedLocalOwner(), *highlight_config,
-                             false);
-  }
-  return Response::OK();
-}
-
-Response InspectorDOMAgent::hideHighlight() {
-  if (client_)
-    client_->HideHighlight();
-  return Response::OK();
-}
-
 Response InspectorDOMAgent::copyTo(int node_id,
                                    int target_element_id,
                                    Maybe<int> anchor_node_id,
@@ -1540,7 +1316,7 @@
 
 Response InspectorDOMAgent::requestNode(const String& object_id, int* node_id) {
   Node* node = nullptr;
-  Response response = NodeForRemoteId(object_id, node);
+  Response response = NodeForRemoteObjectId(object_id, node);
   if (!response.isSuccess())
     return response;
   *node_id = PushNodePathToFrontend(node);
@@ -2354,18 +2130,6 @@
   return Response::OK();
 }
 
-Response InspectorDOMAgent::getHighlightObjectForTest(
-    int node_id,
-    std::unique_ptr<protocol::DictionaryValue>* result) {
-  Node* node = nullptr;
-  Response response = AssertNode(node_id, node);
-  if (!response.isSuccess())
-    return response;
-  InspectorHighlight highlight(node, InspectorHighlight::DefaultConfig(), true);
-  *result = highlight.AsProtocolValue();
-  return Response::OK();
-}
-
 std::unique_ptr<v8_inspector::protocol::Runtime::API::RemoteObject>
 InspectorDOMAgent::ResolveNode(Node* node, const String& object_group) {
   Document* document =
diff --git a/third_party/WebKit/Source/core/inspector/InspectorDOMAgent.h b/third_party/WebKit/Source/core/inspector/InspectorDOMAgent.h
index 1e1680f..37b076c 100644
--- a/third_party/WebKit/Source/core/inspector/InspectorDOMAgent.h
+++ b/third_party/WebKit/Source/core/inspector/InspectorDOMAgent.h
@@ -34,7 +34,6 @@
 #include "core/CoreExport.h"
 #include "core/events/EventListenerMap.h"
 #include "core/inspector/InspectorBaseAgent.h"
-#include "core/inspector/InspectorHighlight.h"
 #include "core/inspector/protocol/DOM.h"
 #include "core/style/ComputedStyleConstants.h"
 #include "platform/geometry/FloatQuad.h"
@@ -48,6 +47,7 @@
 namespace blink {
 
 class CharacterData;
+class Color;
 class DOMEditor;
 class Document;
 class DocumentLoader;
@@ -77,33 +77,14 @@
     virtual void DidModifyDOMAttr(Element*) = 0;
   };
 
-  enum SearchMode {
-    kNotSearching,
-    kSearchingForNormal,
-    kSearchingForUAShadow,
-  };
-
-  class Client {
-   public:
-    virtual ~Client() {}
-    virtual void HideHighlight() {}
-    virtual void HighlightNode(Node*,
-                               const InspectorHighlightConfig&,
-                               bool omit_tooltip) {}
-    virtual void HighlightQuad(std::unique_ptr<FloatQuad>,
-                               const InspectorHighlightConfig&) {}
-    virtual void SetInspectMode(SearchMode search_mode,
-                                std::unique_ptr<InspectorHighlightConfig>) {}
-  };
-
   static protocol::Response ToResponse(ExceptionState&);
   static bool GetPseudoElementType(PseudoId, String*);
   static ShadowRoot* UserAgentShadowRoot(Node*);
+  static Color ParseColor(protocol::DOM::RGBA*);
 
   InspectorDOMAgent(v8::Isolate*,
                     InspectedFrames*,
-                    v8_inspector::V8InspectorSession*,
-                    Client*);
+                    v8_inspector::V8InspectorSession*);
   ~InspectorDOMAgent() override;
   DECLARE_VIRTUAL_TRACE();
 
@@ -165,30 +146,6 @@
   protocol::Response discardSearchResults(const String& search_id) override;
   protocol::Response requestNode(const String& object_id,
                                  int* out_node_id) override;
-  protocol::Response setInspectMode(
-      const String& mode,
-      protocol::Maybe<protocol::DOM::HighlightConfig>) override;
-  protocol::Response highlightRect(
-      int x,
-      int y,
-      int width,
-      int height,
-      protocol::Maybe<protocol::DOM::RGBA> color,
-      protocol::Maybe<protocol::DOM::RGBA> outline_color) override;
-  protocol::Response highlightQuad(
-      std::unique_ptr<protocol::Array<double>> quad,
-      protocol::Maybe<protocol::DOM::RGBA> color,
-      protocol::Maybe<protocol::DOM::RGBA> outline_color) override;
-  protocol::Response highlightNode(
-      std::unique_ptr<protocol::DOM::HighlightConfig>,
-      protocol::Maybe<int> node_id,
-      protocol::Maybe<int> backend_node_id,
-      protocol::Maybe<String> object_id) override;
-  protocol::Response hideHighlight() override;
-  protocol::Response highlightFrame(
-      const String& frame_id,
-      protocol::Maybe<protocol::DOM::RGBA> content_color,
-      protocol::Maybe<protocol::DOM::RGBA> content_outline_color) override;
   protocol::Response pushNodeByPathToFrontend(const String& path,
                                               int* out_node_id) override;
   protocol::Response pushNodesByBackendIdsToFrontend(
@@ -228,9 +185,6 @@
       int* out_node_id) override;
   protocol::Response getRelayoutBoundary(int node_id,
                                          int* out_node_id) override;
-  protocol::Response getHighlightObjectForTest(
-      int node_id,
-      std::unique_ptr<protocol::DictionaryValue>* highlight) override;
 
   bool Enabled() const;
   void ReleaseDanglingNodes();
@@ -261,9 +215,10 @@
   Node* NodeForId(int node_id);
   int BoundNodeId(Node*);
   void SetDOMListener(DOMListener*);
-  void Inspect(Node*);
-  void NodeHighlightedInOverlay(Node*);
   int PushNodePathToFrontend(Node*);
+  protocol::Response PushDocumentUponHandlelessOperation();
+  protocol::Response NodeForRemoteObjectId(const String& remote_object_id,
+                                           Node*&);
 
   static String DocumentURLString(Document*);
 
@@ -296,14 +251,6 @@
   void SetDocument(Document*);
   void InnerEnable();
 
-  protocol::Response SetSearchingForNode(
-      SearchMode,
-      protocol::Maybe<protocol::DOM::HighlightConfig>);
-  protocol::Response HighlightConfigFromInspectorObject(
-      protocol::Maybe<protocol::DOM::HighlightConfig>
-          highlight_inspector_object,
-      std::unique_ptr<InspectorHighlightConfig>*);
-
   // Node-related methods.
   typedef HeapHashMap<Member<Node>, int> NodeToIdMap;
   int Bind(Node*, NodeToIdMap*);
@@ -345,22 +292,14 @@
   BuildDistributedNodesForSlot(HTMLSlotElement*);
 
   Node* NodeForPath(const String& path);
-  protocol::Response NodeForRemoteId(const String& id, Node*&);
 
   void DiscardFrontendBindings();
 
-  void InnerHighlightQuad(std::unique_ptr<FloatQuad>,
-                          protocol::Maybe<protocol::DOM::RGBA> color,
-                          protocol::Maybe<protocol::DOM::RGBA> outline_color);
-
-  protocol::Response PushDocumentUponHandlelessOperation();
-
   InspectorRevalidateDOMTask* RevalidateTask();
 
   v8::Isolate* isolate_;
   Member<InspectedFrames> inspected_frames_;
   v8_inspector::V8InspectorSession* v8_session_;
-  Client* client_;
   Member<DOMListener> dom_listener_;
   Member<NodeToIdMap> document_node_to_id_map_;
   // Owns node mappings for dangling nodes.
@@ -378,7 +317,6 @@
   Member<InspectorHistory> history_;
   Member<DOMEditor> dom_editor_;
   bool suppress_attribute_modified_event_;
-  int backend_node_id_to_inspect_;
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/inspector/InspectorOverlayHost.cpp b/third_party/WebKit/Source/core/inspector/InspectorOverlayHost.cpp
index 8054b7ba..2db1b96 100644
--- a/third_party/WebKit/Source/core/inspector/InspectorOverlayHost.cpp
+++ b/third_party/WebKit/Source/core/inspector/InspectorOverlayHost.cpp
@@ -30,7 +30,8 @@
 
 namespace blink {
 
-InspectorOverlayHost::InspectorOverlayHost() : listener_(nullptr) {}
+InspectorOverlayHost::InspectorOverlayHost(Listener* listener)
+    : listener_(listener) {}
 
 void InspectorOverlayHost::resume() {
   if (listener_)
diff --git a/third_party/WebKit/Source/core/inspector/InspectorOverlayHost.h b/third_party/WebKit/Source/core/inspector/InspectorOverlayHost.h
index 0d5d4be..167833c 100644
--- a/third_party/WebKit/Source/core/inspector/InspectorOverlayHost.h
+++ b/third_party/WebKit/Source/core/inspector/InspectorOverlayHost.h
@@ -40,23 +40,20 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
-  static InspectorOverlayHost* Create() { return new InspectorOverlayHost(); }
-  DECLARE_TRACE();
-
-  void resume();
-  void stepOver();
-
   class Listener : public GarbageCollectedMixin {
    public:
     virtual ~Listener() {}
     virtual void OverlayResumed() = 0;
     virtual void OverlaySteppedOver() = 0;
   };
-  void SetListener(Listener* listener) { listener_ = listener; }
+
+  explicit InspectorOverlayHost(Listener*);
+  DECLARE_TRACE();
+
+  void resume();
+  void stepOver();
 
  private:
-  InspectorOverlayHost();
-
   Member<Listener> listener_;
 };
 
diff --git a/third_party/WebKit/Source/core/inspector/InspectorPageAgent.cpp b/third_party/WebKit/Source/core/inspector/InspectorPageAgent.cpp
index 428dba6e..2e2a503 100644
--- a/third_party/WebKit/Source/core/inspector/InspectorPageAgent.cpp
+++ b/third_party/WebKit/Source/core/inspector/InspectorPageAgent.cpp
@@ -81,8 +81,6 @@
     "pageAgentScriptsToEvaluateOnLoad";
 static const char kScreencastEnabled[] = "screencastEnabled";
 static const char kAutoAttachToCreatedPages[] = "autoAttachToCreatedPages";
-static const char kOverlaySuspended[] = "overlaySuspended";
-static const char kOverlayMessage[] = "overlayMessage";
 }
 
 namespace {
@@ -376,13 +374,6 @@
 void InspectorPageAgent::Restore() {
   if (state_->booleanProperty(PageAgentState::kPageAgentEnabled, false))
     enable();
-  if (client_) {
-    String overlay_message;
-    state_->getString(PageAgentState::kOverlayMessage, &overlay_message);
-    client_->ConfigureOverlay(
-        state_->booleanProperty(PageAgentState::kOverlaySuspended, false),
-        overlay_message);
-  }
 }
 
 Response InspectorPageAgent::enable() {
@@ -403,7 +394,6 @@
       resource_content_loader_client_id_);
 
   stopScreencast();
-  configureOverlay(false, String());
 
   FinishReload();
   return Response::OK();
@@ -864,18 +854,6 @@
   return Response::OK();
 }
 
-Response InspectorPageAgent::configureOverlay(Maybe<bool> suspended,
-                                              Maybe<String> message) {
-  state_->setBoolean(PageAgentState::kOverlaySuspended,
-                     suspended.fromMaybe(false));
-  state_->setString(PageAgentState::kOverlaySuspended,
-                    message.fromMaybe(String()));
-  if (client_)
-    client_->ConfigureOverlay(suspended.fromMaybe(false),
-                              message.fromMaybe(String()));
-  return Response::OK();
-}
-
 Response InspectorPageAgent::getLayoutMetrics(
     std::unique_ptr<protocol::Page::LayoutViewport>* out_layout_viewport,
     std::unique_ptr<protocol::Page::VisualViewport>* out_visual_viewport,
diff --git a/third_party/WebKit/Source/core/inspector/InspectorPageAgent.h b/third_party/WebKit/Source/core/inspector/InspectorPageAgent.h
index 0080aa1..48ec089b 100644
--- a/third_party/WebKit/Source/core/inspector/InspectorPageAgent.h
+++ b/third_party/WebKit/Source/core/inspector/InspectorPageAgent.h
@@ -66,7 +66,6 @@
    public:
     virtual ~Client() {}
     virtual void PageLayoutInvalidated(bool resized) {}
-    virtual void ConfigureOverlay(bool suspended, const String& message) {}
     virtual void WaitForCreateWindow(LocalFrame*) {}
   };
 
@@ -139,8 +138,6 @@
                                      Maybe<int> max_height,
                                      Maybe<int> every_nth_frame) override;
   protocol::Response stopScreencast() override;
-  protocol::Response configureOverlay(Maybe<bool> suspended,
-                                      Maybe<String> message) override;
   protocol::Response getLayoutMetrics(
       std::unique_ptr<protocol::Page::LayoutViewport>*,
       std::unique_ptr<protocol::Page::VisualViewport>*,
diff --git a/third_party/WebKit/Source/core/inspector/browser_protocol.json b/third_party/WebKit/Source/core/inspector/browser_protocol.json
index cca3bc9..ccc2d2b3b 100644
--- a/third_party/WebKit/Source/core/inspector/browser_protocol.json
+++ b/third_party/WebKit/Source/core/inspector/browser_protocol.json
@@ -469,15 +469,6 @@
                 ]
             },
             {
-                "name": "configureOverlay",
-                "parameters": [
-                    { "name": "suspended", "type": "boolean", "optional": true, "description": "Whether overlay should be suspended and not consume any resources." },
-                    { "name": "message", "type": "string", "optional": true, "description": "Overlay message to display." }
-                ],
-                "experimental": true,
-                "description": "Configures overlay."
-            },
-            {
                 "name": "getAppManifest",
                 "experimental": true,
                 "returns": [
@@ -645,11 +636,50 @@
         ]
     },
     {
-        "domain": "Rendering",
-        "description": "This domain allows to control rendering of the page.",
+        "domain": "Overlay",
+        "description": "This domain provides various functionality related to drawing atop the inspected page.",
+        "dependencies": ["DOM", "Page", "Runtime"],
         "experimental": true,
+        "types": [
+            {
+                "id": "HighlightConfig",
+                "type": "object",
+                "properties": [
+                    { "name": "showInfo", "type": "boolean", "optional": true, "description": "Whether the node info tooltip should be shown (default: false)." },
+                    { "name": "showRulers", "type": "boolean", "optional": true, "description": "Whether the rulers should be shown (default: false)." },
+                    { "name": "showExtensionLines", "type": "boolean", "optional": true, "description": "Whether the extension lines from node to the rulers should be shown (default: false)." },
+                    { "name": "displayAsMaterial", "type": "boolean", "optional": true},
+                    { "name": "contentColor", "$ref": "DOM.RGBA", "optional": true, "description": "The content box highlight fill color (default: transparent)." },
+                    { "name": "paddingColor", "$ref": "DOM.RGBA", "optional": true, "description": "The padding highlight fill color (default: transparent)." },
+                    { "name": "borderColor", "$ref": "DOM.RGBA", "optional": true, "description": "The border highlight fill color (default: transparent)." },
+                    { "name": "marginColor", "$ref": "DOM.RGBA", "optional": true, "description": "The margin highlight fill color (default: transparent)." },
+                    { "name": "eventTargetColor", "$ref": "DOM.RGBA", "optional": true, "description": "The event target element highlight fill color (default: transparent)." },
+                    { "name": "shapeColor", "$ref": "DOM.RGBA", "optional": true, "description": "The shape outside fill color (default: transparent)." },
+                    { "name": "shapeMarginColor", "$ref": "DOM.RGBA", "optional": true, "description": "The shape margin fill color (default: transparent)." },
+                    { "name": "selectorList", "type": "string", "optional": true, "description": "Selectors to highlight relevant nodes."}
+                ],
+                "description": "Configuration data for the highlighting of page elements."
+            },
+            {
+                "id": "InspectMode",
+                "type": "string",
+                "enum": [
+                    "searchForNode",
+                    "searchForUAShadowDOM",
+                    "none"
+                ]
+            }
+        ],
         "commands": [
             {
+                "name": "enable",
+                "description": "Enables domain notifications."
+            },
+            {
+                "name": "disable",
+                "description": "Disables domain notifications."
+            },
+            {
                 "name": "setShowPaintRects",
                 "description": "Requests that backend shows paint rectangles",
                 "parameters": [
@@ -683,6 +713,96 @@
                 "parameters": [
                     { "name": "show", "type": "boolean", "description": "Whether to paint size or not." }
                 ]
+            },
+            {
+                "name": "setPausedInDebuggerMessage",
+                "parameters": [
+                    { "name": "message", "type": "string", "optional": true, "description": "The message to display, also triggers resume and step over controls." }
+                ]
+            },
+            {
+                "name": "setSuspended",
+                "parameters": [
+                    { "name": "suspended", "type": "boolean", "description": "Whether overlay should be suspended and not consume any resources until resumed." }
+                ]
+            },
+            {
+                "name": "setInspectMode",
+                "description": "Enters the 'inspect' mode. In this mode, elements that user is hovering over are highlighted. Backend then generates 'inspectNodeRequested' event upon element selection.",
+                "parameters": [
+                    { "name": "mode", "$ref": "InspectMode", "description": "Set an inspection mode." },
+                    { "name": "highlightConfig", "$ref": "HighlightConfig", "optional": true, "description": "A descriptor for the highlight appearance of hovered-over nodes. May be omitted if <code>enabled == false</code>." }
+                ]
+            },
+            {
+                "name": "highlightRect",
+                "description": "Highlights given rectangle. Coordinates are absolute with respect to the main frame viewport.",
+                "parameters": [
+                    { "name": "x", "type": "integer", "description": "X coordinate" },
+                    { "name": "y", "type": "integer", "description": "Y coordinate" },
+                    { "name": "width", "type": "integer", "description": "Rectangle width" },
+                    { "name": "height", "type": "integer", "description": "Rectangle height" },
+                    { "name": "color", "$ref": "DOM.RGBA", "optional": true, "description": "The highlight fill color (default: transparent)." },
+                    { "name": "outlineColor", "$ref": "DOM.RGBA", "optional": true, "description": "The highlight outline color (default: transparent)." }
+                ]
+            },
+            {
+                "name": "highlightQuad",
+                "description": "Highlights given quad. Coordinates are absolute with respect to the main frame viewport.",
+                "parameters": [
+                    { "name": "quad", "$ref": "DOM.Quad", "description": "Quad to highlight" },
+                    { "name": "color", "$ref": "DOM.RGBA", "optional": true, "description": "The highlight fill color (default: transparent)." },
+                    { "name": "outlineColor", "$ref": "DOM.RGBA", "optional": true, "description": "The highlight outline color (default: transparent)." }
+                ]
+            },
+            {
+                "name": "highlightNode",
+                "description": "Highlights DOM node with given id or with the given JavaScript object wrapper. Either nodeId or objectId must be specified.",
+                "parameters": [
+                    { "name": "highlightConfig", "$ref": "HighlightConfig",  "description": "A descriptor for the highlight appearance." },
+                    { "name": "nodeId", "$ref": "DOM.NodeId", "optional": true, "description": "Identifier of the node to highlight." },
+                    { "name": "backendNodeId", "$ref": "DOM.BackendNodeId", "optional": true, "description": "Identifier of the backend node to highlight." },
+                    { "name": "objectId", "$ref": "Runtime.RemoteObjectId", "optional": true, "description": "JavaScript object id of the node to be highlighted." }
+                ]
+            },
+            {
+                "name": "highlightFrame",
+                "description": "Highlights owner element of the frame with given id.",
+                "parameters": [
+                    { "name": "frameId", "$ref": "Page.FrameId", "description": "Identifier of the frame to highlight." },
+                    { "name": "contentColor", "$ref": "DOM.RGBA", "optional": true, "description": "The content box highlight fill color (default: transparent)." },
+                    { "name": "contentOutlineColor", "$ref": "DOM.RGBA", "optional": true, "description": "The content box highlight outline color (default: transparent)." }
+                ]
+            },
+            {
+                "name": "hideHighlight",
+                "description": "Hides any highlight."
+            },
+            {
+                "name": "getHighlightObjectForTest",
+                "description": "For testing.",
+                "parameters": [
+                    { "name": "nodeId", "$ref": "DOM.NodeId", "description": "Id of the node to get highlight object for." }
+                ],
+                "returns": [
+                    { "name": "highlight", "type": "object", "description": "Highlight data for the node." }
+                ]
+            }
+        ],
+        "events": [
+            {
+                "name": "nodeHighlightRequested",
+                "description": "Fired when the node should be highlighted. This happens after call to <code>setInspectMode</code>.",
+                "parameters": [
+                    { "name": "nodeId", "$ref": "DOM.NodeId" }
+                ]
+            },
+            {
+                "name": "inspectNodeRequested",
+                "description": "Fired when the node should be inspected. This happens after call to <code>setInspectMode</code> or when user manually inspects an element.",
+                "parameters": [
+                    { "name": "backendNodeId", "$ref": "DOM.BackendNodeId", "description": "Id of the node to inspect." }
+                ]
             }
         ]
     },
@@ -2138,35 +2258,6 @@
                     { "name": "height", "type": "number", "description": "Rectangle height" }
                 ],
                 "description": "Rectangle."
-            },
-            {
-                "id": "HighlightConfig",
-                "type": "object",
-                "properties": [
-                    { "name": "showInfo", "type": "boolean", "optional": true, "description": "Whether the node info tooltip should be shown (default: false)." },
-                    { "name": "showRulers", "type": "boolean", "optional": true, "description": "Whether the rulers should be shown (default: false)." },
-                    { "name": "showExtensionLines", "type": "boolean", "optional": true, "description": "Whether the extension lines from node to the rulers should be shown (default: false)." },
-                    { "name": "displayAsMaterial", "type": "boolean", "optional": true, "experimental": true},
-                    { "name": "contentColor", "$ref": "RGBA", "optional": true, "description": "The content box highlight fill color (default: transparent)." },
-                    { "name": "paddingColor", "$ref": "RGBA", "optional": true, "description": "The padding highlight fill color (default: transparent)." },
-                    { "name": "borderColor", "$ref": "RGBA", "optional": true, "description": "The border highlight fill color (default: transparent)." },
-                    { "name": "marginColor", "$ref": "RGBA", "optional": true, "description": "The margin highlight fill color (default: transparent)." },
-                    { "name": "eventTargetColor", "$ref": "RGBA", "optional": true, "experimental": true, "description": "The event target element highlight fill color (default: transparent)." },
-                    { "name": "shapeColor", "$ref": "RGBA", "optional": true, "experimental": true, "description": "The shape outside fill color (default: transparent)." },
-                    { "name": "shapeMarginColor", "$ref": "RGBA", "optional": true, "experimental": true, "description": "The shape margin fill color (default: transparent)." },
-                    { "name": "selectorList", "type": "string", "optional": true, "description": "Selectors to highlight relevant nodes."}
-                ],
-                "description": "Configuration data for the highlighting of page elements."
-            },
-            {
-                "id": "InspectMode",
-                "type": "string",
-                "experimental": true,
-                "enum": [
-                    "searchForNode",
-                    "searchForUAShadowDOM",
-                    "none"
-                ]
             }
         ],
         "commands": [
@@ -2357,59 +2448,19 @@
                 "description": "Requests that the node is sent to the caller given the JavaScript node object reference. All nodes that form the path from the node to the root are also sent to the client as a series of <code>setChildNodes</code> notifications."
             },
             {
-                "name": "setInspectMode",
-                "experimental": true,
-                "parameters": [
-                    { "name": "mode", "$ref": "InspectMode", "description": "Set an inspection mode." },
-                    { "name": "highlightConfig", "$ref": "HighlightConfig", "optional": true, "description": "A descriptor for the highlight appearance of hovered-over nodes. May be omitted if <code>enabled == false</code>." }
-                ],
-                "description": "Enters the 'inspect' mode. In this mode, elements that user is hovering over are highlighted. Backend then generates 'inspectNodeRequested' event upon element selection."
-            },
-            {
                 "name": "highlightRect",
-                "parameters": [
-                    { "name": "x", "type": "integer", "description": "X coordinate" },
-                    { "name": "y", "type": "integer", "description": "Y coordinate" },
-                    { "name": "width", "type": "integer", "description": "Rectangle width" },
-                    { "name": "height", "type": "integer", "description": "Rectangle height" },
-                    { "name": "color", "$ref": "RGBA", "optional": true, "description": "The highlight fill color (default: transparent)." },
-                    { "name": "outlineColor", "$ref": "RGBA", "optional": true, "description": "The highlight outline color (default: transparent)." }
-                ],
-                "description": "Highlights given rectangle. Coordinates are absolute with respect to the main frame viewport."
-            },
-            {
-                "name": "highlightQuad",
-                "parameters": [
-                    { "name": "quad", "$ref": "Quad", "description": "Quad to highlight" },
-                    { "name": "color", "$ref": "RGBA", "optional": true, "description": "The highlight fill color (default: transparent)." },
-                    { "name": "outlineColor", "$ref": "RGBA", "optional": true, "description": "The highlight outline color (default: transparent)." }
-                ],
-                "description": "Highlights given quad. Coordinates are absolute with respect to the main frame viewport.",
-                "experimental": true
+                "description": "Highlights given rectangle.",
+                "redirect": "Overlay"
             },
             {
                 "name": "highlightNode",
-                "parameters": [
-                    { "name": "highlightConfig", "$ref": "HighlightConfig",  "description": "A descriptor for the highlight appearance." },
-                    { "name": "nodeId", "$ref": "NodeId", "optional": true, "description": "Identifier of the node to highlight." },
-                    { "name": "backendNodeId", "$ref": "BackendNodeId", "optional": true, "description": "Identifier of the backend node to highlight." },
-                    { "name": "objectId", "$ref": "Runtime.RemoteObjectId", "optional": true, "description": "JavaScript object id of the node to be highlighted.", "experimental": true }
-                ],
-                "description": "Highlights DOM node with given id or with the given JavaScript object wrapper. Either nodeId or objectId must be specified."
+                "description": "Highlights DOM node.",
+                "redirect": "Overlay"
             },
             {
                 "name": "hideHighlight",
-                "description": "Hides DOM node highlight."
-            },
-            {
-                "name": "highlightFrame",
-                "parameters": [
-                    { "name": "frameId", "$ref": "Page.FrameId", "description": "Identifier of the frame to highlight." },
-                    { "name": "contentColor", "$ref": "RGBA", "optional": true, "description": "The content box highlight fill color (default: transparent)." },
-                    { "name": "contentOutlineColor", "$ref": "RGBA", "optional": true, "description": "The content box highlight outline color (default: transparent)." }
-                ],
-                "description": "Highlights owner element of the frame with given id.",
-                "experimental": true
+                "description": "Hides any highlight.",
+                "redirect": "Overlay"
             },
             {
                 "name": "pushNodeByPathToFrontend",
@@ -2553,17 +2604,6 @@
                 ],
                 "description": "Returns the id of the nearest ancestor that is a relayout boundary.",
                 "experimental": true
-            },
-            {
-                "name": "getHighlightObjectForTest",
-                "parameters": [
-                    { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node to get highlight object for." }
-                ],
-                "returns": [
-                    { "name": "highlight", "type": "object", "description": "Highlight data for the node." }
-                ],
-                "description": "For testing.",
-                "experimental": true
             }
         ],
         "events": [
@@ -2572,14 +2612,6 @@
                 "description": "Fired when <code>Document</code> has been totally updated. Node ids are no longer valid."
             },
             {
-                "name": "inspectNodeRequested",
-                "parameters": [
-                    { "name": "backendNodeId", "$ref": "BackendNodeId", "description": "Id of the node to inspect." }
-                ],
-                "description": "Fired when the node should be inspected. This happens after call to <code>setInspectMode</code>.",
-                "experimental" : true
-            },
-            {
                 "name": "setChildNodes",
                 "parameters": [
                     { "name": "parentId", "$ref": "NodeId", "description": "Parent node id to populate with children." },
@@ -2689,13 +2721,6 @@
                 ],
                 "description": "Called when distrubution is changed.",
                 "experimental": true
-            },
-            {
-                "name": "nodeHighlightRequested",
-                "parameters": [
-                    {"name": "nodeId", "$ref": "NodeId"}
-                ],
-                "experimental": true
             }
         ]
     },
diff --git a/third_party/WebKit/Source/core/inspector/inspector_protocol_config.json b/third_party/WebKit/Source/core/inspector/inspector_protocol_config.json
index 21048f15..4fb3f5b6 100644
--- a/third_party/WebKit/Source/core/inspector/inspector_protocol_config.json
+++ b/third_party/WebKit/Source/core/inspector/inspector_protocol_config.json
@@ -51,7 +51,7 @@
                 "domain": "Log"
             },
             {
-                "domain": "Rendering"
+                "domain": "Overlay"
             },
             {
                 "domain": "Input",
diff --git a/third_party/WebKit/Source/core/loader/DocumentLoader.cpp b/third_party/WebKit/Source/core/loader/DocumentLoader.cpp
index c0329ed..0bb4b57 100644
--- a/third_party/WebKit/Source/core/loader/DocumentLoader.cpp
+++ b/third_party/WebKit/Source/core/loader/DocumentLoader.cpp
@@ -402,23 +402,31 @@
   }
 
   HistoryCommitType history_commit_type = LoadTypeToCommitType(load_type_);
-  FrameLoader& loader = GetFrameLoader();
-  if (state_ < kCommitted) {
-    if (state_ == kNotStarted)
+  switch (state_) {
+    case kNotStarted:
       probe::frameClearedScheduledClientNavigation(frame_);
-    state_ = kSentDidFinishLoad;
-    GetLocalFrameClient().DispatchDidFailProvisionalLoad(error,
-                                                         history_commit_type);
-    if (!frame_)
-      return;
-    loader.DetachProvisionalDocumentLoader(this);
-  } else if (state_ == kCommitted) {
-    if (frame_->GetDocument()->Parser())
-      frame_->GetDocument()->Parser()->StopParsing();
-    state_ = kSentDidFinishLoad;
-    GetLocalFrameClient().DispatchDidFailLoad(error, history_commit_type);
+    // Fall-through
+    case kProvisional:
+      state_ = kSentDidFinishLoad;
+      GetLocalFrameClient().DispatchDidFailProvisionalLoad(error,
+                                                           history_commit_type);
+      if (frame_)
+        GetFrameLoader().DetachProvisionalDocumentLoader(this);
+      break;
+    case kCommitted:
+      if (frame_->GetDocument()->Parser())
+        frame_->GetDocument()->Parser()->StopParsing();
+      state_ = kSentDidFinishLoad;
+      GetLocalFrameClient().DispatchDidFailLoad(error, history_commit_type);
+      if (frame_)
+        frame_->GetDocument()->CheckCompleted();
+      break;
+    case kSentDidFinishLoad:
+      // TODO(japhet): Why do we need to call DidFinishNavigation() again?
+      GetFrameLoader().DidFinishNavigation();
+      break;
   }
-  loader.CheckCompleted();
+  DCHECK_EQ(kSentDidFinishLoad, state_);
 }
 
 void DocumentLoader::FinishedLoading(double finish_time) {
diff --git a/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp b/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp
index 1fc2491..295d433 100644
--- a/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp
+++ b/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp
@@ -616,10 +616,11 @@
 }
 
 void FrameFetchContext::DidLoadResource(Resource* resource) {
+  if (!GetDocument())
+    return;
+  FirstMeaningfulPaintDetector::From(*GetDocument()).CheckNetworkStable();
   if (resource->IsLoadEventBlockingResourceType())
-    GetFrame()->Loader().CheckCompleted();
-  if (GetDocument())
-    FirstMeaningfulPaintDetector::From(*GetDocument()).CheckNetworkStable();
+    GetDocument()->CheckCompleted();
 }
 
 void FrameFetchContext::AddResourceTiming(const ResourceTimingInfo& info) {
diff --git a/third_party/WebKit/Source/core/loader/FrameFetchContextTest.cpp b/third_party/WebKit/Source/core/loader/FrameFetchContextTest.cpp
index 3381bf4e..3d65c243 100644
--- a/third_party/WebKit/Source/core/loader/FrameFetchContextTest.cpp
+++ b/third_party/WebKit/Source/core/loader/FrameFetchContextTest.cpp
@@ -705,6 +705,11 @@
 }
 
 TEST_F(FrameFetchContextTest, SubResourceCachePolicy) {
+  // Reset load event state: if the load event is finished, we ignore the
+  // DocumentLoader load type.
+  document->open();
+  ASSERT_FALSE(document->LoadEventFinished());
+
   // Default case
   ResourceRequest request("http://www.example.com/mock");
   EXPECT_EQ(WebCachePolicy::kUseProtocolCachePolicy,
diff --git a/third_party/WebKit/Source/core/loader/FrameLoader.cpp b/third_party/WebKit/Source/core/loader/FrameLoader.cpp
index 304c3d4..b9cfeeda1 100644
--- a/third_party/WebKit/Source/core/loader/FrameLoader.cpp
+++ b/third_party/WebKit/Source/core/loader/FrameLoader.cpp
@@ -89,6 +89,7 @@
 #include "platform/loader/fetch/ResourceFetcher.h"
 #include "platform/loader/fetch/ResourceRequest.h"
 #include "platform/network/HTTPParsers.h"
+#include "platform/network/NetworkUtils.h"
 #include "platform/scroll/ScrollAnimatorBase.h"
 #include "platform/weborigin/SchemeRegistry.h"
 #include "platform/weborigin/SecurityOrigin.h"
@@ -453,7 +454,7 @@
         document_loader_ ? document_loader_->IsCommittedButEmpty() : true);
   }
 
-  CheckCompleted();
+  frame_->GetDocument()->CheckCompleted();
 
   if (!frame_->View())
     return;
@@ -465,15 +466,6 @@
                   kNavigationToDifferentDocument);
 }
 
-static bool AllDescendantsAreComplete(Frame* frame) {
-  for (Frame* child = frame->Tree().FirstChild(); child;
-       child = child->Tree().TraverseNext(frame)) {
-    if (child->IsLoading())
-      return false;
-  }
-  return true;
-}
-
 bool FrameLoader::AllAncestorsAreComplete() const {
   for (Frame* ancestor = frame_; ancestor;
        ancestor = ancestor->Tree().Parent()) {
@@ -483,96 +475,15 @@
   return true;
 }
 
-static bool ShouldComplete(Document* document) {
-  if (!document->GetFrame())
-    return false;
-  if (document->Parsing() || document->IsInDOMContentLoaded())
-    return false;
-  if (!document->HaveImportsLoaded())
-    return false;
-  if (document->Fetcher()->BlockingRequestCount())
-    return false;
-  if (document->IsDelayingLoadEvent())
-    return false;
-  return AllDescendantsAreComplete(document->GetFrame());
-}
-
-static bool ShouldSendFinishNotification(LocalFrame* frame) {
-  // Don't send didFinishLoad more than once per DocumentLoader.
-  if (frame->Loader().GetDocumentLoader()->SentDidFinishLoad())
-    return false;
-
-  // We might have declined to run the load event due to an imminent
-  // content-initiated navigation.
-  if (!frame->GetDocument()->LoadEventFinished())
-    return false;
-
-  // An event might have restarted a child frame.
-  if (!AllDescendantsAreComplete(frame))
-    return false;
-
-  // Don't notify if the frame is being detached.
-  if (!frame->IsAttached())
-    return false;
-
-  return true;
-}
-
-static bool ShouldSendCompleteNotification(LocalFrame* frame) {
-  // FIXME: We might have already sent stop notifications and be re-completing.
-  if (!frame->IsLoading())
-    return false;
-  // Only send didStopLoading() if there are no navigations in progress at all,
-  // whether committed, provisional, or pending.
-  return frame->Loader().GetDocumentLoader()->SentDidFinishLoad() &&
-         !frame->Loader().HasProvisionalNavigation();
-}
-
-void FrameLoader::CheckCompleted() {
-  if (!ShouldComplete(frame_->GetDocument()))
+void FrameLoader::DidFinishNavigation() {
+  // We should have either finished the provisional or committed navigation if
+  // this is called. Only delcare the whole frame finished if neither is in
+  // progress.
+  DCHECK(document_loader_->SentDidFinishLoad() || !HasProvisionalNavigation());
+  if (!document_loader_->SentDidFinishLoad() || HasProvisionalNavigation())
     return;
 
-  if (Client()) {
-    Client()->RunScriptsAtDocumentIdle();
-
-    // Injected scripts may have disconnected this frame.
-    if (!Client())
-      return;
-
-    // Check again, because runScriptsAtDocumentIdle() may have delayed the load
-    // event.
-    if (!ShouldComplete(frame_->GetDocument()))
-      return;
-  }
-
-  // OK, completed.
-  frame_->GetDocument()->SetReadyState(Document::kComplete);
-  if (frame_->GetDocument()->LoadEventStillNeeded())
-    frame_->GetDocument()->ImplicitClose();
-
-  frame_->GetNavigationScheduler().StartTimer();
-
-  if (frame_->View())
-    frame_->View()->HandleLoadCompleted();
-
-  // The readystatechanged or load event may have disconnected this frame.
-  if (!frame_->Client())
-    return;
-
-  if (ShouldSendFinishNotification(frame_)) {
-    // Report mobile vs. desktop page statistics. This will only report on
-    // Android.
-    if (frame_->IsMainFrame())
-      frame_->GetDocument()->GetViewportDescription().ReportMobilePageStats(
-          frame_);
-    document_loader_->SetSentDidFinishLoad();
-    Client()->DispatchDidFinishLoad();
-    // Finishing the load can detach the frame when running layout tests.
-    if (!frame_->Client())
-      return;
-  }
-
-  if (ShouldSendCompleteNotification(frame_)) {
+  if (frame_->IsLoading()) {
     progress_tracker_->ProgressCompleted();
     // Retry restoring scroll offset since finishing loading disables content
     // size clamping.
@@ -584,7 +495,7 @@
 
   Frame* parent = frame_->Tree().Parent();
   if (parent && parent->IsLocalFrame())
-    ToLocalFrame(parent)->Loader().CheckCompleted();
+    ToLocalFrame(parent)->GetDocument()->CheckCompleted();
 }
 
 void FrameLoader::CheckTimerFired(TimerBase*) {
@@ -592,7 +503,7 @@
     if (page->Suspended())
       return;
   }
-  CheckCompleted();
+  frame_->GetDocument()->CheckCompleted();
 }
 
 void FrameLoader::ScheduleCheckCompleted() {
@@ -700,7 +611,7 @@
 
   document_loader_->GetInitialScrollState().was_scrolled_by_user = false;
 
-  CheckCompleted();
+  frame_->GetDocument()->CheckCompleted();
 
   frame_->DomWindow()->StatePopped(state_object
                                        ? std::move(state_object)
@@ -805,6 +716,23 @@
     return false;
   }
 
+  // Block renderer-initiated loads of data URLs in the top frame. If the mime
+  // type of the data URL is supported, the URL will eventually be rendered, so
+  // block it here. Otherwise, the load might be handled by a plugin or end up
+  // as a download, so allow it to let the embedder figure out what to do with
+  // it.
+  if (frame_->IsMainFrame() &&
+      !request.GetResourceRequest().IsSameDocumentNavigation() &&
+      !frame_->Client()->AllowContentInitiatedDataUrlNavigations(
+          request.OriginDocument()->Url()) &&
+      url.ProtocolIsData() && NetworkUtils::IsDataURLMimeTypeSupported(url)) {
+    frame_->GetDocument()->AddConsoleMessage(ConsoleMessage::Create(
+        kSecurityMessageSource, kErrorMessageLevel,
+        "Not allowed to navigate top frame to data URL: " +
+            url.ElidedString()));
+    return false;
+  }
+
   if (!request.Form() && request.FrameName().IsEmpty())
     request.SetFrameName(frame_->GetDocument()->BaseTarget());
   return true;
@@ -1062,10 +990,9 @@
       ToLocalFrame(child)->Loader().StopAllLoaders();
   }
 
-  frame_->GetDocument()->SuppressLoadEvent();
+  frame_->GetDocument()->CancelParsing();
   if (document_loader_)
     document_loader_->Fetcher()->StopFetching();
-  frame_->GetDocument()->CancelParsing();
   if (!protect_provisional_loader_)
     DetachDocumentLoader(provisional_document_loader_);
 
@@ -1312,6 +1239,7 @@
 void FrameLoader::DetachProvisionalDocumentLoader(DocumentLoader* loader) {
   DCHECK_EQ(loader, provisional_document_loader_);
   DetachDocumentLoader(provisional_document_loader_);
+  DidFinishNavigation();
 }
 
 bool FrameLoader::ShouldPerformFragmentNavigation(bool is_form_submission,
diff --git a/third_party/WebKit/Source/core/loader/FrameLoader.h b/third_party/WebKit/Source/core/loader/FrameLoader.h
index 7ea22eb..75155e4 100644
--- a/third_party/WebKit/Source/core/loader/FrameLoader.h
+++ b/third_party/WebKit/Source/core/loader/FrameLoader.h
@@ -159,7 +159,7 @@
   void Detach();
 
   void FinishedParsing();
-  void CheckCompleted();
+  void DidFinishNavigation();
 
   // This prepares the FrameLoader for the next commit. It will dispatch unload
   // events, abort XHR requests and detach the document. Returns true if the
diff --git a/third_party/WebKit/Source/core/loader/NavigationScheduler.cpp b/third_party/WebKit/Source/core/loader/NavigationScheduler.cpp
index 0ff8cfa..b2eaf71 100644
--- a/third_party/WebKit/Source/core/loader/NavigationScheduler.cpp
+++ b/third_party/WebKit/Source/core/loader/NavigationScheduler.cpp
@@ -507,6 +507,8 @@
 
   Cancel();
   redirect_ = redirect;
+  if (redirect_->IsLocationChange())
+    frame_->GetDocument()->SuppressLoadEvent();
   StartTimer();
 }
 
diff --git a/third_party/WebKit/Source/core/svg/SVGSVGElement.cpp b/third_party/WebKit/Source/core/svg/SVGSVGElement.cpp
index 5a33e7b..7dd63702 100644
--- a/third_party/WebKit/Source/core/svg/SVGSVGElement.cpp
+++ b/third_party/WebKit/Source/core/svg/SVGSVGElement.cpp
@@ -504,8 +504,8 @@
       // the load event, but if we miss that train (deferred programmatic
       // element insertion for example) we need to initialize the time container
       // here.
-      if (!GetDocument().Parsing() && !GetDocument().ProcessingLoadEvent() &&
-          GetDocument().LoadEventFinished() && !TimeContainer()->IsStarted())
+      if (!GetDocument().Parsing() && GetDocument().LoadEventFinished() &&
+          !TimeContainer()->IsStarted())
         TimeContainer()->Start();
     }
   }
diff --git a/third_party/WebKit/Source/core/xmlhttprequest/XMLHttpRequest.cpp b/third_party/WebKit/Source/core/xmlhttprequest/XMLHttpRequest.cpp
index a1ec3ad..8c027254 100644
--- a/third_party/WebKit/Source/core/xmlhttprequest/XMLHttpRequest.cpp
+++ b/third_party/WebKit/Source/core/xmlhttprequest/XMLHttpRequest.cpp
@@ -1638,7 +1638,7 @@
 
   ClearVariablesForLoading();
 
-  response_document_->ImplicitClose();
+  response_document_->CheckCompleted();
 
   if (!response_document_->WellFormed())
     response_document_ = nullptr;
diff --git a/third_party/WebKit/Source/devtools/.eslintignore b/third_party/WebKit/Source/devtools/.eslintignore
index 36ce56b..63b1c52 100644
--- a/third_party/WebKit/Source/devtools/.eslintignore
+++ b/third_party/WebKit/Source/devtools/.eslintignore
@@ -1,6 +1,7 @@
 front_end/.eslintrc.js
 front_end/acorn/*
-front_end/audits2_worker/lighthouse/lighthouse-background.js
+front_end/audits2_worker/lighthouse/
+front_end/audits2/lighthouse/
 front_end/cm/*
 front_end/cm_headless/*
 front_end/cm_modes/*
diff --git a/third_party/WebKit/Source/devtools/BUILD.gn b/third_party/WebKit/Source/devtools/BUILD.gn
index 96dfa7b..dfc55fb5 100644
--- a/third_party/WebKit/Source/devtools/BUILD.gn
+++ b/third_party/WebKit/Source/devtools/BUILD.gn
@@ -44,6 +44,9 @@
   "front_end/audits2_worker/lighthouse/lighthouse-background.js",
   "front_end/audits2_worker/module.json",
   "front_end/audits2/Audits2Panel.js",
+  "front_end/audits2/lighthouse/renderer/dom.js",
+  "front_end/audits2/lighthouse/renderer/details-renderer.js",
+  "front_end/audits2/lighthouse/renderer/report-renderer.js",
   "front_end/audits2/module.json",
   "front_end/bindings/BlackboxManager.js",
   "front_end/bindings/BreakpointManager.js",
@@ -292,7 +295,6 @@
   "front_end/main/GCActionDelegate.js",
   "front_end/main/Main.js",
   "front_end/main/module.json",
-  "front_end/main/OverlayController.js",
   "front_end/main/remoteDebuggingTerminatedScreen.css",
   "front_end/main/renderingOptions.css",
   "front_end/main/RenderingOptions.js",
@@ -474,6 +476,7 @@
   "front_end/sdk/module.json",
   "front_end/sdk/NetworkManager.js",
   "front_end/sdk/NetworkRequest.js",
+  "front_end/sdk/OverlayModel.js",
   "front_end/sdk/PaintProfiler.js",
   "front_end/sdk/ProfileTreeModel.js",
   "front_end/sdk/RemoteObject.js",
diff --git a/third_party/WebKit/Source/devtools/front_end/Tests.js b/third_party/WebKit/Source/devtools/front_end/Tests.js
index ddd45de..b0c51b9 100644
--- a/third_party/WebKit/Source/devtools/front_end/Tests.js
+++ b/third_party/WebKit/Source/devtools/front_end/Tests.js
@@ -612,19 +612,16 @@
 
   // Regression test for crbug.com/370035.
   TestSuite.prototype.testDeviceMetricsOverrides = function() {
-    const dumpPageMetrics = function() {
+    function dumpPageMetrics() {
       return JSON.stringify(
           {width: window.innerWidth, height: window.innerHeight, deviceScaleFactor: window.devicePixelRatio});
-    };
+    }
 
     var test = this;
 
-    function testOverrides(params, metrics, callback) {
-      SDK.targetManager.mainTarget().emulationAgent().invoke_setDeviceMetricsOverride(params, getMetrics);
-
-      function getMetrics() {
-        test.evaluateInConsole_('(' + dumpPageMetrics.toString() + ')()', checkMetrics);
-      }
+    async function testOverrides(params, metrics, callback) {
+      await SDK.targetManager.mainTarget().emulationAgent().invoke_setDeviceMetricsOverride(params);
+      test.evaluateInConsole_('(' + dumpPageMetrics.toString() + ')()', checkMetrics);
 
       function checkMetrics(consoleResult) {
         test.assertEquals(
diff --git a/third_party/WebKit/Source/devtools/front_end/accessibility/AXTreePane.js b/third_party/WebKit/Source/devtools/front_end/accessibility/AXTreePane.js
index fc69390e..884dd30 100644
--- a/third_party/WebKit/Source/devtools/front_end/accessibility/AXTreePane.js
+++ b/third_party/WebKit/Source/devtools/front_end/accessibility/AXTreePane.js
@@ -157,7 +157,7 @@
       if (!this.node())
         return;
       // Highlight and scroll into view the currently inspected node.
-      this.node().domModel().nodeHighlightRequested(this.node().id);
+      this.node().domModel().overlayModel().nodeHighlightRequested(this.node().id);
     }
   }
 
diff --git a/third_party/WebKit/Source/devtools/front_end/accessibility/AccessibilityModel.js b/third_party/WebKit/Source/devtools/front_end/accessibility/AccessibilityModel.js
index 5fe56b5..87dd668 100644
--- a/third_party/WebKit/Source/devtools/front_end/accessibility/AccessibilityModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/accessibility/AccessibilityModel.js
@@ -156,7 +156,7 @@
     this.deferredDOMNode().resolvePromise().then(node => {
       if (!node)
         return;
-      node.domModel().nodeHighlightRequested(node.id);
+      node.domModel().overlayModel().nodeHighlightRequested(node.id);
     });
   }
 
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/Audits2Panel.js b/third_party/WebKit/Source/devtools/front_end/audits2/Audits2Panel.js
index bdd4fbc..a60b2ea 100644
--- a/third_party/WebKit/Source/devtools/front_end/audits2/Audits2Panel.js
+++ b/third_party/WebKit/Source/devtools/front_end/audits2/Audits2Panel.js
@@ -3,26 +3,6 @@
 // found in the LICENSE file.
 
 /**
- * @typedef {{
- *     lighthouseVersion: !string,
- *     generatedTime: !string,
- *     initialUrl: !string,
- *     url: !string,
- *     audits: ?Object,
- *     aggregations: !Array<*>
- * }}
- */
-Audits2.LighthouseResult;
-
-/**
- * @typedef {{
- *     blobUrl: !string,
- *     result: !Audits2.LighthouseResult
- * }}
- */
-Audits2.WorkerResult;
-
-/**
  * @unrestricted
  */
 Audits2.Audits2Panel = class extends UI.Panel {
@@ -30,6 +10,7 @@
     super('audits2');
     this.setHideOnDetach();
     this.registerRequiredCSS('audits2/audits2Panel.css');
+    this.registerRequiredCSS('audits2/lighthouse/report-styles.css');
 
     this._protocolService = new Audits2.ProtocolService();
     this._protocolService.registerStatusCallback(msg => this._updateStatus(Common.UIString(msg)));
@@ -42,7 +23,6 @@
 
     var auditsViewElement = this.contentElement.createChild('div', 'hbox audits2-view');
     this._resultsView = this.contentElement.createChild('div', 'vbox results-view');
-    auditsViewElement.createChild('div', 'audits2-logo');
     this._createLauncherUI(auditsViewElement);
   }
 
@@ -55,6 +35,7 @@
    * @param {!Element} auditsViewElement
    */
   _createLauncherUI(auditsViewElement) {
+    auditsViewElement.createChild('div', 'audits2-logo');
     var uiElement = auditsViewElement.createChild('div');
     var headerElement = uiElement.createChild('header');
     headerElement.createChild('p').textContent = Common.UIString(
@@ -90,7 +71,7 @@
   _start() {
     this._inspectedURL = SDK.targetManager.mainTarget().inspectedURL();
 
-    const aggregationIDs = this._settings.map(setting => {
+    const categoryIDs = this._settings.map(setting => {
       const preset = Audits2.Audits2Panel.Presets.find(preset => preset.id === setting.name);
       return {configID: preset.configID, value: setting.get()};
     }).filter(agg => !!agg.value).map(agg => agg.configID);
@@ -102,9 +83,9 @@
           this._updateButton();
           this._updateStatus(Common.UIString('Loading...'));
         })
-        .then(_ => this._protocolService.startLighthouse(this._inspectedURL, aggregationIDs))
-        .then(workerResult => {
-          this._finish(workerResult);
+        .then(_ => this._protocolService.startLighthouse(this._inspectedURL, categoryIDs))
+        .then(lighthouseResult => {
+          this._finish(lighthouseResult);
           return this._stop();
         });
   }
@@ -150,31 +131,41 @@
   }
 
   /**
-   * @param {!Audits2.WorkerResult} workerResult
+   * @param {!ReportRenderer.ReportJSON} lighthouseResult
    */
-  _finish(workerResult) {
-    if (workerResult === null) {
+  _finish(lighthouseResult) {
+    if (lighthouseResult === null) {
       this._updateStatus(Common.UIString('Auditing failed.'));
       return;
     }
     this._resultsView.removeChildren();
 
-    var url = workerResult.result.url;
-    var timestamp = workerResult.result.generatedTime;
+    var url = lighthouseResult.url;
+    var timestamp = lighthouseResult.generatedTime;
     this._createResultsBar(this._resultsView, url, timestamp);
-    this._createIframe(this._resultsView, workerResult.blobUrl);
+    this._renderReport(this._resultsView, lighthouseResult);
     this.contentElement.classList.add('show-results');
   }
 
   /**
    * @param {!Element} resultsView
-   * @param {string} blobUrl
+   * @param {!ReportRenderer.ReportJSON} lighthouseResult
+   * @suppressGlobalPropertiesCheck
    */
-  _createIframe(resultsView, blobUrl) {
-    var iframeContainer = resultsView.createChild('div', 'iframe-container');
-    var iframe = iframeContainer.createChild('iframe', 'fill');
-    iframe.setAttribute('sandbox', 'allow-scripts allow-popups-to-escape-sandbox allow-popups');
-    iframe.src = blobUrl;
+  _renderReport(resultsView, lighthouseResult) {
+    var reportContainer = resultsView.createChild('div', 'report-container');
+
+    var dom = new DOM(document);
+    var detailsRenderer = new DetailsRenderer(dom);
+    var renderer = new ReportRenderer(dom, detailsRenderer);
+
+    var templatesHTML = Runtime.cachedResources['audits2/lighthouse/templates.html'];
+    var templatesDOM = new DOMParser().parseFromString(templatesHTML, 'text/html');
+    if (!templatesDOM)
+      return;
+
+    renderer.setTemplateContext(templatesDOM);
+    reportContainer.appendChild(renderer.renderReport(lighthouseResult));
   }
 
   /**
@@ -206,10 +197,11 @@
 
 /** @type {!Array.<!Audits2.Audits2Panel.Preset>} */
 Audits2.Audits2Panel.Presets = [
-  // configID maps to Lighthouse's config.aggregations[0].id value
-  {id: 'audits2_cat_pwa', configID: 'pwa', description: 'Progressive web app audits'},
-  {id: 'audits2_cat_perf', configID: 'perf', description: 'Performance metrics and diagnostics'},
-  {id: 'audits2_cat_best_practices', configID: 'bp', description: 'Modern web development best practices'},
+  // configID maps to Lighthouse's Object.keys(config.categories)[0] value
+  {id: 'audits2_cat_pwa', configID: 'pwa', description: 'Progressive Web App'},
+  {id: 'audits2_cat_perf', configID: 'performance', description: 'Performance metrics and diagnostics'},
+  {id: 'audits2_cat_a11y', configID: 'accessibility', description: 'Accessibility'},
+  {id: 'audits2_cat_best_practices', configID: 'best-practices', description: 'Modern best practices'},
 ];
 
 Audits2.ProtocolService = class extends Common.Object {
@@ -236,11 +228,11 @@
 
   /**
    * @param {string} inspectedURL
-   * @param {!Array<string>} aggregationIDs
-   * @return {!Promise<!Audits2.WorkerResult|undefined>}
+   * @param {!Array<string>} categoryIDs
+   * @return {!Promise<!ReportRenderer.ReportJSON>}
    */
-  startLighthouse(inspectedURL, aggregationIDs) {
-    return this._send('start', {url: inspectedURL, aggregationIDs});
+  startLighthouse(inspectedURL, categoryIDs) {
+    return this._send('start', {url: inspectedURL, categoryIDs});
   }
 
   /**
@@ -282,7 +274,7 @@
   /**
    * @param {string} method
    * @param {!Object=} params
-   * @return {!Promise<!Audits2.WorkerResult|undefined>}
+   * @return {!Promise<!ReportRenderer.ReportJSON>}
    */
   _send(method, params) {
     if (!this._backendPromise)
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/audits2Panel.css b/third_party/WebKit/Source/devtools/front_end/audits2/audits2Panel.css
index 69ed916..a6394e3 100644
--- a/third_party/WebKit/Source/devtools/front_end/audits2/audits2Panel.css
+++ b/third_party/WebKit/Source/devtools/front_end/audits2/audits2Panel.css
@@ -4,14 +4,6 @@
  * found in the LICENSE file.
  */
 
-iframe {
-    width: 100%;
-    height: 100%;
-    flex-direction: column !important;
-    position: relative;
-    flex: auto;
-}
-
 .audits2-view {
     max-width: 550px;
     min-width: 334px;
@@ -92,10 +84,11 @@
     flex-shrink: 0;
 }
 
-.iframe-container {
+.report-container {
     flex: 1 1 auto;
     align-self: auto;
     position: relative;
+    overflow: auto;
 }
 
 .audits2-form label {
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/LICENSE b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/LICENSE
new file mode 100644
index 0000000..a4c5efd8
--- /dev/null
+++ b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2014 Google Inc.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/README.chromium b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/README.chromium
new file mode 100644
index 0000000..76195996
--- /dev/null
+++ b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/README.chromium
@@ -0,0 +1,8 @@
+Name: Lighthouse is a performance auditing component written in JavaScript that works in the browser.
+Short Name: lighthouse
+URL: github.com/GoogleChrome/lighthouse
+License: Apache License 2.0
+Security Critical: no
+
+This directory contains Chromium's version of the lighthouse report assets, including renderer.
+
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/details-renderer.js b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/details-renderer.js
new file mode 100644
index 0000000..b9b53227
--- /dev/null
+++ b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/details-renderer.js
@@ -0,0 +1,141 @@
+/**
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+class DetailsRenderer {
+  /**
+   * @param {!DOM} dom
+   */
+  constructor(dom) {
+    this._dom = dom;
+  }
+
+  /**
+   * @param {(!DetailsRenderer.DetailsJSON|!DetailsRenderer.CardsDetailsJSON)} details
+   * @return {!Element}
+   */
+  render(details) {
+    switch (details.type) {
+      case 'text':
+        return this._renderText(details);
+      case 'block':
+        return this._renderBlock(details);
+      case 'cards':
+        return this._renderCards(/** @type {!DetailsRenderer.CardsDetailsJSON} */ (details));
+      case 'list':
+        return this._renderList(details);
+      default:
+        throw new Error(`Unknown type: ${details.type}`);
+    }
+  }
+
+  /**
+   * @param {!DetailsRenderer.DetailsJSON} text
+   * @return {!Element}
+   */
+  _renderText(text) {
+    const element = this._dom.createElement('div', 'lh-text');
+    element.textContent = text.text;
+    return element;
+  }
+
+  /**
+   * @param {!DetailsRenderer.DetailsJSON} block
+   * @return {!Element}
+   */
+  _renderBlock(block) {
+    const element = this._dom.createElement('div', 'lh-block');
+    const items = block.items || [];
+    for (const item of items) {
+      element.appendChild(this.render(item));
+    }
+    return element;
+  }
+
+  /**
+   * @param {!DetailsRenderer.DetailsJSON} list
+   * @return {!Element}
+   */
+  _renderList(list) {
+    const element = this._dom.createElement('details', 'lh-details');
+    if (list.header) {
+      const summary = this._dom.createElement('summary', 'lh-list__header');
+      summary.textContent = list.header.text;
+      element.appendChild(summary);
+    }
+
+    const itemsElem = this._dom.createElement('div', 'lh-list__items');
+    const items = list.items || [];
+    for (const item of items) {
+      itemsElem.appendChild(this.render(item));
+    }
+    element.appendChild(itemsElem);
+    return element;
+  }
+
+  /**
+   * @param {!DetailsRenderer.CardsDetailsJSON} details
+   * @return {!Element}
+   */
+  _renderCards(details) {
+    const element = this._dom.createElement('details', 'lh-details');
+    if (details.header) {
+      element.appendChild(this._dom.createElement('summary')).textContent = details.header.text;
+    }
+
+    const cardsParent = this._dom.createElement('div', 'lh-scorecards');
+    for (const item of details.items) {
+      const card = cardsParent.appendChild(
+          this._dom.createElement('div', 'lh-scorecard', {title: item.snippet}));
+      const titleEl = this._dom.createElement('div', 'lh-scorecard__title');
+      const valueEl = this._dom.createElement('div', 'lh-scorecard__value');
+      const targetEl = this._dom.createElement('div', 'lh-scorecard__target');
+
+      card.appendChild(titleEl).textContent = item.title;
+      card.appendChild(valueEl).textContent = item.value;
+
+      if (item.target) {
+        card.appendChild(targetEl).textContent = `target: ${item.target}`;
+      }
+    }
+
+    element.appendChild(cardsParent);
+    return element;
+  }
+}
+
+if (typeof module !== 'undefined' && module.exports) {
+  module.exports = DetailsRenderer;
+}
+
+/**
+ * @typedef {{
+ *     type: string,
+ *     text: (string|undefined),
+ *     header: (!DetailsRenderer.DetailsJSON|undefined),
+ *     items: (!Array<!DetailsRenderer.DetailsJSON>|undefined)
+ * }}
+ */
+DetailsRenderer.DetailsJSON; // eslint-disable-line no-unused-expressions
+
+/** @typedef {{
+ *     type: string,
+ *     text: string,
+ *     header: !DetailsRenderer.DetailsJSON,
+ *     items: !Array<{title: string, value: string, snippet: (string|undefined), target: string}>
+ * }}
+ */
+DetailsRenderer.CardsDetailsJSON; // eslint-disable-line no-unused-expressions
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/dom.js b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/dom.js
new file mode 100644
index 0000000..6989856
--- /dev/null
+++ b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/dom.js
@@ -0,0 +1,105 @@
+/**
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+/* globals URL */
+
+class DOM {
+  /**
+   * @param {!Document} document
+   */
+  constructor(document) {
+    this._document = document;
+  }
+
+ /**
+   * @param {string} name
+   * @param {string=} className
+   * @param {!Object<string, (string|undefined)>=} attrs Attribute key/val pairs.
+   *     Note: if an attribute key has an undefined value, this method does not
+   *     set the attribute on the node.
+   * @return {!Element}
+   */
+  createElement(name, className, attrs) {
+    // TODO(all): adopt `attrs` default arg when https://codereview.chromium.org/2821773002/ lands
+    attrs = attrs || {};
+    const element = this._document.createElement(name);
+    if (className) {
+      element.className = className;
+    }
+    Object.keys(attrs).forEach(key => {
+      const value = attrs[key];
+      if (typeof value !== 'undefined') {
+        element.setAttribute(key, value);
+      }
+    });
+    return element;
+  }
+
+  /**
+   * @param {string} selector
+   * @param {!Document|!Element} context
+   * @return {!DocumentFragment} A clone of the template content.
+   * @throws {Error}
+   */
+  cloneTemplate(selector, context) {
+    const template = context.querySelector(selector);
+    if (!template) {
+      throw new Error(`Template not found: template${selector}`);
+    }
+    return /** @type {!DocumentFragment} */ (this._document.importNode(template.content, true));
+  }
+
+  /**
+   * @param {string} text
+   * @return {!Element}
+   */
+  createSpanFromMarkdown(text) {
+    const element = this.createElement('span');
+
+    // Split on markdown links (e.g. [some link](https://...)).
+    const parts = text.split(/\[(.*?)\]\((https?:\/\/.*?)\)/g);
+
+    while (parts.length) {
+      // Pop off the same number of elements as there are capture groups.
+      const [preambleText, linkText, linkHref] = parts.splice(0, 3);
+      element.appendChild(this._document.createTextNode(preambleText));
+
+      // Append link if there are any.
+      if (linkText && linkHref) {
+        const a = this.createElement('a');
+        a.rel = 'noopener';
+        a.target = '_blank';
+        a.textContent = linkText;
+        a.href = (new URL(linkHref)).href;
+        element.appendChild(a);
+      }
+    }
+
+    return element;
+  }
+
+  /**
+   * @return {!Document}
+   */
+  document() {
+    return this._document;
+  }
+}
+
+if (typeof module !== 'undefined' && module.exports) {
+  module.exports = DOM;
+}
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/report-renderer.js b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/report-renderer.js
new file mode 100644
index 0000000..88c28fb
--- /dev/null
+++ b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/report-renderer.js
@@ -0,0 +1,252 @@
+/**
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+/**
+ * @fileoverview The entry point for rendering the Lighthouse report based on the JSON output.
+ *    This file is injected into the report HTML along with the JSON report.
+ *
+ * Dummy text for ensuring report robustness: </script> pre$`post %%LIGHTHOUSE_JSON%%
+ */
+
+const RATINGS = {
+  PASS: {label: 'pass', minScore: 75},
+  AVERAGE: {label: 'average', minScore: 45},
+  FAIL: {label: 'fail'}
+};
+
+/**
+ * Convert a score to a rating label.
+ * @param {number} score
+ * @return {string}
+ */
+function calculateRating(score) {
+  let rating = RATINGS.FAIL.label;
+  if (score >= RATINGS.PASS.minScore) {
+    rating = RATINGS.PASS.label;
+  } else if (score >= RATINGS.AVERAGE.minScore) {
+    rating = RATINGS.AVERAGE.label;
+  }
+  return rating;
+}
+
+/**
+ * Format number.
+ * @param {number} number
+ * @return {string}
+ */
+function formatNumber(number) {
+  return number.toLocaleString(undefined, {maximumFractionDigits: 1});
+}
+
+class ReportRenderer {
+  /**
+   * @param {!DOM} dom
+   * @param {!DetailsRenderer} detailsRenderer
+   */
+  constructor(dom, detailsRenderer) {
+    this._dom = dom;
+    this._detailsRenderer = detailsRenderer;
+
+    this._templateContext = this._dom.document();
+  }
+
+  /**
+   * @param {!ReportRenderer.ReportJSON} report
+   * @return {!Element}
+   */
+  renderReport(report) {
+    try {
+      return this._renderReport(report);
+    } catch (e) {
+      return this._renderException(e);
+    }
+  }
+
+  /**
+   * @param {!DocumentFragment|!Element} element DOM node to populate with values.
+   * @param {number} score
+   * @param {string} scoringMode
+   * @param {string} title
+   * @param {string} description
+   * @return {!Element}
+   */
+  _populateScore(element, score, scoringMode, title, description) {
+    // Fill in the blanks.
+    const valueEl = element.querySelector('.lh-score__value');
+    valueEl.textContent = formatNumber(score);
+    valueEl.classList.add(`lh-score__value--${calculateRating(score)}`,
+                          `lh-score__value--${scoringMode}`);
+
+    element.querySelector('.lh-score__title').textContent = title;
+    element.querySelector('.lh-score__description')
+        .appendChild(this._dom.createSpanFromMarkdown(description));
+
+    return /** @type {!Element} **/ (element);
+  }
+
+  /**
+   * Define a custom element for <templates> to be extracted from. For example:
+   *     this.setTemplateContext(new DOMParser().parseFromString(htmlStr, 'text/html'))
+   * @param {!Document|!Element} context
+   */
+  setTemplateContext(context) {
+    this._templateContext = context;
+  }
+
+  /**
+   * @param {!ReportRenderer.AuditJSON} audit
+   * @return {!Element}
+   */
+  _renderAuditScore(audit) {
+    const tmpl = this._dom.cloneTemplate('#tmpl-lh-audit-score', this._templateContext);
+
+    const scoringMode = audit.result.scoringMode;
+    const description = audit.result.helpText;
+    let title = audit.result.description;
+
+    if (audit.result.displayValue) {
+      title += `:  ${audit.result.displayValue}`;
+    }
+    if (audit.result.optimalValue) {
+      title += ` (target: ${audit.result.optimalValue})`;
+    }
+
+    // Append audit details to header section so the entire audit is within a <details>.
+    const header = tmpl.querySelector('.lh-score__header');
+    header.open = audit.score < 100; // expand failed audits
+    if (audit.result.details) {
+      header.appendChild(this._detailsRenderer.render(audit.result.details));
+    }
+
+    return this._populateScore(tmpl, audit.score, scoringMode, title, description);
+  }
+
+  /**
+   * @param {!ReportRenderer.CategoryJSON} category
+   * @return {!Element}
+   */
+  _renderCategoryScore(category) {
+    const tmpl = this._dom.cloneTemplate('#tmpl-lh-category-score', this._templateContext);
+    const score = Math.round(category.score);
+    return this._populateScore(tmpl, score, 'numeric', category.name, category.description);
+  }
+
+  /**
+   * @param {!Error} e
+   * @return {!Element}
+   */
+  _renderException(e) {
+    const element = this._dom.createElement('div', 'lh-exception');
+    element.textContent = String(e.stack);
+    return element;
+  }
+
+  /**
+   * @param {!ReportRenderer.ReportJSON} report
+   * @return {!Element}
+   */
+  _renderReport(report) {
+    const element = this._dom.createElement('div', 'lh-report');
+    for (const category of report.reportCategories) {
+      element.appendChild(this._renderCategory(category));
+    }
+    return element;
+  }
+
+  /**
+   * @param {!ReportRenderer.CategoryJSON} category
+   * @return {!Element}
+   */
+  _renderCategory(category) {
+    const element = this._dom.createElement('div', 'lh-category');
+    element.appendChild(this._renderCategoryScore(category));
+
+    const passedAudits = category.audits.filter(audit => audit.score === 100);
+    const nonPassedAudits = category.audits.filter(audit => !passedAudits.includes(audit));
+
+    for (const audit of nonPassedAudits) {
+      element.appendChild(this._renderAudit(audit));
+    }
+
+    // don't create a passed section if there are no passed
+    if (!passedAudits.length) return element;
+
+    const passedElem = this._dom.createElement('details', 'lh-passed-audits');
+    const passedSummary = this._dom.createElement('summary', 'lh-passed-audits-summary');
+    passedSummary.textContent = `View ${passedAudits.length} passed items`;
+    passedElem.appendChild(passedSummary);
+
+    for (const audit of passedAudits) {
+      passedElem.appendChild(this._renderAudit(audit));
+    }
+    element.appendChild(passedElem);
+    return element;
+  }
+
+  /**
+   * @param {!ReportRenderer.AuditJSON} audit
+   * @return {!Element}
+   */
+  _renderAudit(audit) {
+    const element = this._dom.createElement('div', 'lh-audit');
+    element.appendChild(this._renderAuditScore(audit));
+    return element;
+  }
+}
+
+if (typeof module !== 'undefined' && module.exports) {
+  module.exports = ReportRenderer;
+}
+
+/**
+ * @typedef {{
+ *     id: string, weight:
+ *     number, score: number,
+ *     result: {
+ *       description: string,
+ *       displayValue: string,
+ *       helpText: string,
+ *       score: (number|boolean),
+ *       scoringMode: string,
+ *       details: (!DetailsRenderer.DetailsJSON|!DetailsRenderer.CardsDetailsJSON|undefined)
+ *     }
+ * }}
+ */
+ReportRenderer.AuditJSON; // eslint-disable-line no-unused-expressions
+
+/**
+ * @typedef {{
+ *     name: string,
+ *     weight: number,
+ *     score: number,
+ *     description: string,
+ *     audits: !Array<!ReportRenderer.AuditJSON>
+ * }}
+ */
+ReportRenderer.CategoryJSON; // eslint-disable-line no-unused-expressions
+
+/**
+ * @typedef {{
+ *     lighthouseVersion: !string,
+ *     generatedTime: !string,
+ *     initialUrl: !string,
+ *     url: !string,
+ *     audits: ?Object,
+ *     reportCategories: !Array<!ReportRenderer.CategoryJSON>
+ * }}
+ */
+ReportRenderer.ReportJSON; // eslint-disable-line no-unused-expressions
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/report-styles.css b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/report-styles.css
new file mode 100644
index 0000000..5967638
--- /dev/null
+++ b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/report-styles.css
@@ -0,0 +1,304 @@
+/**
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ :root {
+  --text-font-family: '.SFNSDisplay-Regular', 'Helvetica Neue', 'Lucida Grande', sans-serif;
+  --body-font-size: 13px;
+  --default-padding: 16px;
+
+  --secondary-text-color: #565656;
+  /*--accent-color: #3879d9;*/
+  --fail-color: #df332f;
+  --pass-color: #2b882f;
+  --average-color: #ef6c00; /* md orange 800 */
+  --warning-color: #757575; /* md grey 600 */
+
+  --report-border-color: #ebebeb;
+
+  --lh-score-highlight-bg: #fafafa;
+  --lh-score-icon-background-size: 17px;
+  --lh-score-margin: var(--default-padding);
+  --lh-audit-score-width: 35px;
+  --lh-category-score-width: 50px;
+}
+
+* {
+  box-sizing: border-box;
+}
+
+body {
+  font-family: var(--text-font-family);
+  font-size: var(--body-font-size);
+  margin: 0;
+  line-height: var(--body-line-height);
+}
+
+[hidden] {
+  display: none !important;
+}
+
+.lh-details {
+  font-size: smaller;
+  margin-top: var(--default-padding);
+}
+
+.lh-details summary {
+  cursor: pointer;
+}
+
+.lh-details[open] summary {
+  margin-bottom: var(--default-padding);
+}
+
+/* List */
+.lh-list__items {
+  padding-left: var(--default-padding);
+}
+
+.lh-list__items > * {
+  border-bottom: 1px solid gray;
+  margin-bottom: 2px;
+}
+
+/* Card */
+.lh-scorecards {
+  display: flex;
+  flex-wrap: wrap;
+}
+.lh-scorecard {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex: 0 0 180px;
+  flex-direction: column;
+  padding: var(--default-padding);
+  padding-top: calc(32px + var(--default-padding));
+  border-radius: 3px;
+  margin-right: var(--default-padding);
+  position: relative;
+  line-height: inherit;
+  border: 1px solid #ebebeb;
+}
+.lh-scorecard__title {
+  background-color: #eee;
+  position: absolute;
+  top: 0;
+  right: 0;
+  left: 0;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: calc(var(--default-padding) / 2);
+}
+.lh-scorecard__value {
+  font-size: 28px;
+}
+.lh-scorecard__target {
+  margin-top: calc(var(--default-padding) / 2);
+}
+
+/* Score */
+
+.lh-score {
+  display: flex;
+  align-items: flex-start;
+}
+
+.lh-score__value {
+  flex: none;
+  padding: 5px;
+  margin-right: var(--lh-score-margin);
+  width: var(--lh-audit-score-width);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background: var(--warning-color);
+  color: #fff;
+  border-radius: 2px;
+  position: relative;
+}
+
+.lh-score__value::after {
+  content: '';
+  position: absolute;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: 0;
+  background-color: #000;
+  border-radius: inherit;
+}
+
+.lh-score__value--binary {
+  text-indent: -500px;
+}
+
+/* No icon for audits with number scores. */
+.lh-score__value:not(.lh-score__value--binary)::after {
+  content: none;
+}
+
+.lh-score__value--pass {
+  background: var(--pass-color);
+}
+
+.lh-score__value--pass::after {
+  background: url('data:image/svg+xml;utf8,<svg width="12" height="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><title>pass</title><path d="M9.17 2.33L4.5 7 2.83 5.33 1.5 6.66l3 3 6-6z" fill="white" fill-rule="evenodd"/></svg>') no-repeat 50% 50%;
+  background-size: var(--lh-score-icon-background-size);
+}
+
+.lh-score__value--average {
+  background: var(--average-color);
+}
+
+.lh-score__value--average::after {
+  background: none;
+  content: '!';
+  background-color: var(--average-color);
+  color: #fff;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-weight: 500;
+  font-size: 15px;
+}
+
+.lh-score__value--fail {
+  background: var(--fail-color);
+}
+
+.lh-score__value--fail::after {
+  background: url('data:image/svg+xml;utf8,<svg width="12" height="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><title>fail</title><path d="M8.33 2.33l1.33 1.33-2.335 2.335L9.66 8.33 8.33 9.66 5.995 7.325 3.66 9.66 2.33 8.33l2.335-2.335L2.33 3.66l1.33-1.33 2.335 2.335z" fill="white"/></svg>') no-repeat 50% 50%;
+  background-size: var(--lh-score-icon-background-size);
+}
+
+.lh-score__title {
+  margin-bottom: calc(var(--default-padding) / 2);
+}
+
+.lh-score__description {
+  font-size: smaller;
+  color: var(--secondary-text-color);
+  margin-top: calc(var(--default-padding) / 2);
+}
+
+.lh-score__header {
+  flex: 1;
+  margin-top: 2px;
+}
+
+.lh-score__header[open] .lh-score__arrow {
+  transform: rotateZ(90deg);
+}
+
+.lh-score__arrow {
+  background: url('data:image/svg+xml;utf8,<svg fill="black" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"/><path d="M0-.25h24v24H0z" fill="none"/></svg>') no-repeat 50% 50%;
+  background-size: contain;
+  background-color: transparent;
+  width: 24px;
+  height: 24px;
+  flex: none;
+  margin: 0 8px 0 8px;
+  transition: transform 150ms ease-in-out;
+}
+
+.lh-score__snippet {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  cursor: pointer;
+  /*outline: none;*/
+}
+
+.lh-score__snippet::-moz-list-bullet {
+  display: none;
+}
+
+.lh-score__snippet::-webkit-details-marker {
+  display: none;
+}
+
+/*.lh-score__snippet:focus .lh-score__title {
+  outline: rgb(59, 153, 252) auto 5px;
+}*/
+
+/* Audit */
+
+.lh-audit {
+  margin-top: var(--default-padding);
+}
+
+.lh-audit > .lh-score {
+  font-size: 16px;
+}
+
+/* Report */
+
+.lh-exception {
+  font-size: large;
+}
+
+.lh-report {
+  padding: var(--default-padding);
+}
+
+.lh-category {
+  padding: 24px 0;
+  border-top: 1px solid var(--report-border-color);
+}
+
+.lh-category:first-of-type {
+  border: none;
+  padding-top: 0;
+}
+
+.lh-category > .lh-audit,
+.lh-category > .lh-passed-audits > .lh-audit {
+  margin-left: calc(var(--lh-category-score-width) + var(--lh-score-margin));
+}
+
+.lh-category > .lh-score {
+  font-size: 20px;
+}
+
+.lh-category > .lh-score .lh-score__value {
+  width: var(--lh-category-score-width);
+}
+
+/* Category snippet shouldnt have pointer cursor. */
+.lh-category > .lh-score .lh-score__snippet {
+  cursor: initial;
+}
+
+.lh-category > .lh-score .lh-score__title {
+  font-size: 24px;
+  font-weight: 400;
+}
+
+summary.lh-passed-audits-summary {
+  margin: 10px 5px;
+  font-size: 15px;
+}
+
+summary.lh-passed-audits-summary::-webkit-details-marker {
+  background: rgb(66, 175, 69);
+  color: white;
+  position:relative;
+  content: '';
+  padding: 3px;
+}
+
+/*# sourceURL=report.styles.css */
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/templates.html b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/templates.html
new file mode 100644
index 0000000..32aff33
--- /dev/null
+++ b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/templates.html
@@ -0,0 +1,26 @@
+<!-- Lighthouse category score -->
+<template id="tmpl-lh-category-score">
+  <div class="lh-score">
+    <div class="lh-score__value"><!-- fill me --></div>
+    <div class="lh-score__header">
+      <div class="lh-score__snippet">
+        <span class="lh-score__title"><!-- fill me --></span>
+      </div>
+      <div class="lh-score__description"><!-- fill me --></div>
+    </div>
+  </div>
+</template>
+
+<!-- Lighthouse audit score -->
+<template id="tmpl-lh-audit-score">
+  <div class="lh-score">
+    <div class="lh-score__value"><!-- fill me --></div>
+    <details class="lh-score__header">
+      <summary class="lh-score__snippet">
+        <span class="lh-score__title"><!-- fill me --></span>
+        <div class="lh-score__arrow" title="See audits"></div>
+      </summary>
+      <div class="lh-score__description"><!-- fill me --></div>
+    </details>
+  </div>
+</template>
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/module.json b/third_party/WebKit/Source/devtools/front_end/audits2/module.json
index 395965f..4cd18338 100644
--- a/third_party/WebKit/Source/devtools/front_end/audits2/module.json
+++ b/third_party/WebKit/Source/devtools/front_end/audits2/module.json
@@ -18,9 +18,14 @@
     ],
     "experiment": "audits2",
     "scripts": [
+        "lighthouse/renderer/dom.js",
+        "lighthouse/renderer/details-renderer.js",
+        "lighthouse/renderer/report-renderer.js",
         "Audits2Panel.js"
     ],
     "resources": [
-        "audits2Panel.css"
+        "audits2Panel.css",
+        "lighthouse/report-styles.css",
+        "lighthouse/templates.html"
     ]
 }
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2_worker/Audits2Service.js b/third_party/WebKit/Source/devtools/front_end/audits2_worker/Audits2Service.js
index 1b8f04af..a1a4afa 100644
--- a/third_party/WebKit/Source/devtools/front_end/audits2_worker/Audits2Service.js
+++ b/third_party/WebKit/Source/devtools/front_end/audits2_worker/Audits2Service.js
@@ -37,7 +37,7 @@
   }
 
   /**
-   * @return {!Promise<!Audits2.WorkerResult>}
+   * @return {!Promise<!ReportRenderer.ReportJSON>}
    */
   start(params) {
     self.listenForStatus(message => {
@@ -45,9 +45,8 @@
     });
 
     return Promise.resolve()
-      .then(_ => self.runLighthouseInWorker(this, params.url, undefined, params.aggregationIDs))
-      .then(/** @type {!Audits2.LighthouseResult} */ result =>
-                ({blobUrl: /** @type {?string} */ self.createReportPageAsBlob(result, 'devtools'), result}))
+      .then(_ => self.runLighthouseInWorker(this, params.url, undefined, params.categoryIDs))
+      .then(/** @type {!ReportRenderer.ReportJSON} */ result => result)
       .catchException(null);
   }
 
@@ -105,7 +104,7 @@
   }
 };
 
-// Make lighthouse happy.
+// Make lighthouse and traceviewer happy.
 global = self;
 global.isVinn = true;
 global.document = {};
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2_worker/lighthouse/lighthouse-background.js b/third_party/WebKit/Source/devtools/front_end/audits2_worker/lighthouse/lighthouse-background.js
index 92ac630..e7a2b44c 100644
--- a/third_party/WebKit/Source/devtools/front_end/audits2_worker/lighthouse/lighthouse-background.js
+++ b/third_party/WebKit/Source/devtools/front_end/audits2_worker/lighthouse/lighthouse-background.js
@@ -1,9042 +1,10750 @@
-require=function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f;}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e);},l,l.exports,e,t,n,r);}return n[o].exports;}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s;}({"../audits/accessibility/aria-allowed-attr":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({"../audits/accessibility/accesskeys":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
+/**
+ * @fileoverview Ensures every accesskey attribute value is unique.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
+const AxeAudit = require('./axe-audit');
 
+class Accesskeys extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'accesskeys',
+      description: '`[accesskey]` values are unique.',
+      helpText: '`accesskey` attributes allow the user to quickly activate or focus part ' +
+          'of the page.' +
+          'Using the same `accesskey` more than once could lead to a confusing experience.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
+}
 
+module.exports = Accesskeys;
 
-
-const AxeAudit=require('./axe-audit');
-
-class ARIAAllowedAttr extends AxeAudit{
-
-
-
-static get meta(){
-return{
-category:'Accessibility',
-name:'aria-allowed-attr',
-description:'Element aria-* attributes are allowed for this role',
-helpText:'Each ARIA `role` supports a specific subset of `aria-*` attributes. '+
-'Mismatching these invalidates the `aria-*` attributes. [Learn '+
-'more](https://developers.google.com/web/tools/lighthouse/audits/aria-allowed-attributes).',
-requiredArtifacts:['Accessibility']};
-
-}}
-
-
-module.exports=ARIAAllowedAttr;
-
-},{"./axe-audit":2}],"../audits/accessibility/aria-required-attr":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/aria-allowed-attr":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
+/**
+ * @fileoverview Ensures ARIA attributes are allowed for an element's role.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
+const AxeAudit = require('./axe-audit');
 
+class ARIAAllowedAttr extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'aria-allowed-attr',
+      description: '`[aria-*]` attributes match their roles.',
+      helpText: 'Each ARIA `role` supports a specific subset of `aria-*` attributes. ' +
+          'Mismatching these invalidates the `aria-*` attributes. [Learn ' +
+          'more](https://developers.google.com/web/tools/lighthouse/audits/aria-allowed-attributes).',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
+}
 
+module.exports = ARIAAllowedAttr;
 
-
-const AxeAudit=require('./axe-audit');
-
-class ARIARequiredAttr extends AxeAudit{
-
-
-
-static get meta(){
-return{
-category:'Accessibility',
-name:'aria-required-attr',
-description:'Elements with ARIA roles have the required aria-* attributes',
-helpText:'Some ARIA roles have required attributes that describe the state '+
-'of the element to screen readers. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/required-aria-attributes).',
-requiredArtifacts:['Accessibility']};
-
-}}
-
-
-module.exports=ARIARequiredAttr;
-
-},{"./axe-audit":2}],"../audits/accessibility/aria-valid-attr-value":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/aria-required-attr":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
+/**
+ * @fileoverview Ensures elements with ARIA roles have all required ARIA attributes.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
+const AxeAudit = require('./axe-audit');
 
+class ARIARequiredAttr extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'aria-required-attr',
+      description: '`[role]`s have all required `[aria-*]` attributes.',
+      helpText: 'Some ARIA roles have required attributes that describe the state ' +
+          'of the element to screen readers. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/required-aria-attributes).',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
+}
 
+module.exports = ARIARequiredAttr;
 
-
-const AxeAudit=require('./axe-audit');
-
-class ARIAValidAttr extends AxeAudit{
-
-
-
-static get meta(){
-return{
-category:'Accessibility',
-name:'aria-valid-attr-value',
-description:'Element aria-* attributes have valid values',
-helpText:'Assistive technologies, like screen readers, can\'t interpret ARIA '+
-'attributes with invalid values. [Learn '+
-'more](https://developers.google.com/web/tools/lighthouse/audits/valid-aria-values).',
-requiredArtifacts:['Accessibility']};
-
-}}
-
-
-module.exports=ARIAValidAttr;
-
-},{"./axe-audit":2}],"../audits/accessibility/aria-valid-attr":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/aria-required-children":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
+/**
+ * @fileoverview Ensures elements with an ARIA role contain any required children.
+ * e.g. A parent node with role="list" should contain children with role="listitem".
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
+const AxeAudit = require('./axe-audit');
 
+class AriaRequiredChildren extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'aria-required-children',
+      description: '`[role]`s that require child `[role]`s contain them.',
+      helpText: 'Some ARIA parent roles require specific roles on their children to perform ' +
+          'their accessibility function.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
+}
 
+module.exports = AriaRequiredChildren;
 
-
-const AxeAudit=require('./axe-audit');
-
-class ARIAValidAttr extends AxeAudit{
-
-
-
-static get meta(){
-return{
-category:'Accessibility',
-name:'aria-valid-attr',
-description:'Element aria-* attributes are valid and not misspelled or non-existent.',
-helpText:'Assistive technologies, like screen readers, can\'t interpret ARIA '+
-'attributes with invalid names. [Learn '+
-'more](https://developers.google.com/web/tools/lighthouse/audits/valid-aria-attributes).',
-requiredArtifacts:['Accessibility']};
-
-}}
-
-
-module.exports=ARIAValidAttr;
-
-},{"./axe-audit":2}],"../audits/accessibility/color-contrast":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/aria-required-parent":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
+/**
+ * @fileoverview Ensures elements with an ARIA role are contained by their required parents.
+ * e.g. A child node with role="listitem" should be contained by a parent with role="list".
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
+const AxeAudit = require('./axe-audit');
 
+class AriaRequiredParent extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'aria-required-parent',
+      description: '`[role]`s are contained by their required parent element.',
+      helpText: 'Some ARIA roles require specific roles on their parent element to perform ' +
+          'their accessibility function.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
+}
 
+module.exports = AriaRequiredParent;
 
-
-
-const AxeAudit=require('./axe-audit');
-
-class ColorContrast extends AxeAudit{
-
-
-
-static get meta(){
-return{
-category:'Accessibility',
-name:'color-contrast',
-description:'Background and foreground colors have a sufficient contrast ratio',
-helpText:'Low-contrast text is difficult or impossible for many users to read. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/contrast-ratio).',
-requiredArtifacts:['Accessibility']};
-
-}}
-
-
-module.exports=ColorContrast;
-
-},{"./axe-audit":2}],"../audits/accessibility/image-alt":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/aria-roles":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
+/**
+ * @fileoverview Ensures all elements with a role attribute use a valid value.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
+const AxeAudit = require('./axe-audit');
 
+class AriaRoles extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'aria-roles',
+      description: '`[role]` values are valid.',
+      helpText: 'ARIA roles require specific values to perform their accessibility function.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
+}
 
+module.exports = AriaRoles;
 
-
-const AxeAudit=require('./axe-audit');
-
-class ImageAlt extends AxeAudit{
-
-
-
-static get meta(){
-return{
-category:'Accessibility',
-name:'image-alt',
-description:'Every image element has an alt attribute',
-helpText:'Screen reader users rely on `alt` text to provide descriptions of '+
-'images. It\'s also used as fallback content when an image fails to load. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/alt-attribute).',
-requiredArtifacts:['Accessibility']};
-
-}}
-
-
-module.exports=ImageAlt;
-
-},{"./axe-audit":2}],"../audits/accessibility/label":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/aria-valid-attr-value":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
+/**
+ * @fileoverview Ensures all ARIA attributes have valid values.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
+const AxeAudit = require('./axe-audit');
 
+class ARIAValidAttr extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'aria-valid-attr-value',
+      description: '`[aria-*]` attributes have valid values.',
+      helpText: 'Assistive technologies, like screen readers, can\'t interpret ARIA ' +
+          'attributes with invalid values. [Learn ' +
+          'more](https://developers.google.com/web/tools/lighthouse/audits/valid-aria-values).',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
+}
 
+module.exports = ARIAValidAttr;
 
-
-const AxeAudit=require('./axe-audit');
-
-class Label extends AxeAudit{
-
-
-
-static get meta(){
-return{
-category:'Accessibility',
-name:'label',
-description:'Every form element has a label',
-helpText:'Labels ensure that form controls are announced properly by assistive '+
-'technologies, like screen readers. [Learn '+
-'more](https://developers.google.com/web/tools/lighthouse/audits/form-labels).',
-requiredArtifacts:['Accessibility']};
-
-}}
-
-
-module.exports=Label;
-
-},{"./axe-audit":2}],"../audits/accessibility/tabindex":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/aria-valid-attr":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
+/**
+ * @fileoverview Ensures aria-* attributes are valid and not misspelled or non-existent.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
+const AxeAudit = require('./axe-audit');
 
+class ARIAValidAttr extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'aria-valid-attr',
+      description: '`[aria-*]` attributes are valid and not misspelled.',
+      helpText: 'Assistive technologies, like screen readers, can\'t interpret ARIA ' +
+          'attributes with invalid names. [Learn ' +
+          'more](https://developers.google.com/web/tools/lighthouse/audits/valid-aria-attributes).',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
+}
 
+module.exports = ARIAValidAttr;
 
-
-const AxeAudit=require('./axe-audit');
-
-class TabIndex extends AxeAudit{
-
-
-
-static get meta(){
-return{
-category:'Accessibility',
-name:'tabindex',
-description:'No element has a `tabindex` attribute greater than 0',
-helpText:'A value greater than 0 implies an explicit navigation ordering. '+
-'Although technically valid, this often creates frustrating experiences '+
-'for users who rely on assistive technologies. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/tabindex).',
-requiredArtifacts:['Accessibility']};
-
-}}
-
-
-module.exports=TabIndex;
-
-},{"./axe-audit":2}],"../audits/byte-efficiency/total-byte-weight":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/audio-caption":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('./byte-efficiency-audit');
-const Formatter=require('../../report/formatter');
-const TracingProcessor=require('../../lib/traces/tracing-processor');
-const URL=require('../../lib/url-shim');
+/**
+ * @fileoverview Ensures `<audio>` elements have captions.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
+const AxeAudit = require('./axe-audit');
 
-
-const OPTIMAL_VALUE=1600*1024;
-const SCORING_POINT_OF_DIMINISHING_RETURNS=2500*1024;
-const SCORING_MEDIAN=4000*1024;
-
-class TotalByteWeight extends Audit{
-
-
-
-static get meta(){
-return{
-category:'Network',
-name:'total-byte-weight',
-optimalValue:this.bytesToKbString(OPTIMAL_VALUE),
-description:'Avoids enormous network payloads',
-informative:true,
-helpText:
-'Network transfer size [costs users real dollars](https://whatdoesmysitecost.com/) '+
-'and is [highly correlated](http://httparchive.org/interesting.php#onLoad) with long load times. '+
-'Try to find ways to reduce the size of required files.',
-requiredArtifacts:['networkRecords']};
-
+class AudioCaption extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'audio-caption',
+      description: '`<audio>` elements contain a `<track>` element with `[kind="captions"]`.',
+      helpText: 'Captions convey information such as identifying who is speaking, dialogue, and ' +
+          'non-speech information. This can help deaf or hearing impaired users access ' +
+          'meaningful content.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = AudioCaption;
 
-
-
-
-static audit(artifacts){
-const networkRecords=artifacts.networkRecords[Audit.DEFAULT_PASS];
-return artifacts.requestNetworkThroughput(networkRecords).then(networkThroughput=>{
-let totalBytes=0;
-const results=networkRecords.reduce((prev,record)=>{
-
-if(record.scheme==='data'){
-return prev;
-}
-
-const result={
-url:URL.getDisplayName(record.url,{preserveQuery:true}),
-totalBytes:record.transferSize,
-totalKb:this.bytesToKbString(record.transferSize),
-totalMs:this.bytesToMsString(record.transferSize,networkThroughput)};
-
-
-totalBytes+=result.totalBytes;
-prev.push(result);
-return prev;
-},[]).sort((itemA,itemB)=>itemB.totalBytes-itemA.totalBytes).slice(0,10);
-
-
-
-
-
-
-const distribution=TracingProcessor.getLogNormalDistribution(
-SCORING_MEDIAN,SCORING_POINT_OF_DIMINISHING_RETURNS);
-const score=100*distribution.computeComplementaryPercentile(totalBytes);
-
-return this.generateAuditResult({
-rawValue:totalBytes,
-optimalValue:this.meta.optimalValue,
-displayValue:`Total size was ${Audit.bytesToKbString(totalBytes)}`,
-score:Math.round(Math.max(0,Math.min(score,100))),
-extendedInfo:{
-formatter:Formatter.SUPPORTED_FORMATS.TABLE,
-value:{
-results,
-tableHeadings:{
-url:'URL',
-totalKb:'Total Size',
-totalMs:'Transfer Time'}}}});
-
-
-
-
-});
-}}
-
-
-module.exports=TotalByteWeight;
-
-},{"../../lib/traces/tracing-processor":24,"../../lib/url-shim":25,"../../report/formatter":27,"./byte-efficiency-audit":4}],"../audits/byte-efficiency/unused-css-rules":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/button-name":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('./byte-efficiency-audit');
-const URL=require('../../lib/url-shim');
+/**
+ * @fileoverview Ensures buttons have discernible text.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-const PREVIEW_LENGTH=100;
+const AxeAudit = require('./axe-audit');
 
-class UnusedCSSRules extends Audit{
-
-
-
-static get meta(){
-return{
-category:'CSS',
-name:'unused-css-rules',
-description:'Unused CSS rules',
-informative:true,
-helpText:'Remove unused rules from stylesheets to reduce unnecessary '+
-'bytes consumed by network activity. '+
-'[Learn more](https://developers.google.com/speed/docs/insights/OptimizeCSSDelivery)',
-requiredArtifacts:['CSSUsage','Styles','URL','networkRecords']};
-
+class ButtonName extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'button-name',
+      description: 'Buttons have an accessible name.',
+      helpText: 'An accessible name helps convey the purpose of a button. Without one, a screen ' +
+          'reader will only announce the word "button".',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = ButtonName;
 
-
-
-
-
-static indexStylesheetsById(styles,networkRecords){
-const indexedNetworkRecords=networkRecords.
-filter(record=>record._resourceType&&record._resourceType._name==='stylesheet').
-reduce((indexed,record)=>{
-indexed[record.url]=record;
-return indexed;
-},{});
-return styles.reduce((indexed,stylesheet)=>{
-indexed[stylesheet.header.styleSheetId]=Object.assign({
-used:[],
-unused:[],
-networkRecord:indexedNetworkRecords[stylesheet.header.sourceURL]},
-stylesheet);
-return indexed;
-},{});
-}
-
-
-
-
-
-
-
-static countUnusedRules(rules,indexedStylesheets){
-let unused=0;
-
-rules.forEach(rule=>{
-const stylesheetInfo=indexedStylesheets[rule.styleSheetId];
-
-if(!stylesheetInfo||stylesheetInfo.isDuplicate){
-return;
-}
-
-if(rule.used){
-stylesheetInfo.used.push(rule);
-}else{
-unused++;
-stylesheetInfo.unused.push(rule);
-}
-});
-
-return unused;
-}
-
-
-
-
-
-
-static determineContentPreview(content){
-let preview=content.
-slice(0,PREVIEW_LENGTH*5).
-replace(/( {2,}|\t)+/g,'  ').
-replace(/\n\s+}/g,'\n}').
-trim();
-
-if(preview.length>PREVIEW_LENGTH){
-const firstRuleStart=preview.indexOf('{');
-const firstRuleEnd=preview.indexOf('}');
-
-if(firstRuleStart===-1||firstRuleEnd===-1||
-firstRuleStart>firstRuleEnd||
-firstRuleStart>PREVIEW_LENGTH){
-
-preview=preview.slice(0,PREVIEW_LENGTH)+'...';
-}else if(firstRuleEnd<PREVIEW_LENGTH){
-
-preview=preview.slice(0,firstRuleEnd+1)+' ...';
-}else{
-
-const lastSemicolonIndex=preview.slice(0,PREVIEW_LENGTH).lastIndexOf(';');
-preview=lastSemicolonIndex<firstRuleStart?
-preview.slice(0,PREVIEW_LENGTH)+'... } ...':
-preview.slice(0,lastSemicolonIndex+1)+' ... } ...';
-}
-}
-
-return preview;
-}
-
-
-
-
-
-
-static mapSheetToResult(stylesheetInfo,pageUrl){
-const numUsed=stylesheetInfo.used.length;
-const numUnused=stylesheetInfo.unused.length;
-
-if(numUsed===0&&numUnused===0||stylesheetInfo.isDuplicate){
-return null;
-}
-
-let url=stylesheetInfo.header.sourceURL;
-if(!url||url===pageUrl){
-const contentPreview=UnusedCSSRules.determineContentPreview(stylesheetInfo.content);
-url='*inline*```'+contentPreview+'```';
-}else{
-url=URL.getDisplayName(url,{preserveQuery:true});
-}
-
-
-
-const totalBytes=stylesheetInfo.networkRecord?
-stylesheetInfo.networkRecord.transferSize:
-Math.round(stylesheetInfo.content.length/3);
-
-const percentUnused=numUnused/(numUsed+numUnused);
-const wastedBytes=Math.round(percentUnused*totalBytes);
-
-return{
-url,
-numUnused,
-wastedBytes,
-wastedPercent:percentUnused*100,
-totalBytes};
-
-}
-
-
-
-
-
-
-static audit_(artifacts){
-const styles=artifacts.Styles;
-const usage=artifacts.CSSUsage;
-const pageUrl=artifacts.URL.finalUrl;
-const networkRecords=artifacts.networkRecords[Audit.DEFAULT_PASS];
-
-const indexedSheets=UnusedCSSRules.indexStylesheetsById(styles,networkRecords);
-UnusedCSSRules.countUnusedRules(usage,indexedSheets);
-const results=Object.keys(indexedSheets).map(sheetId=>{
-return UnusedCSSRules.mapSheetToResult(indexedSheets[sheetId],pageUrl);
-}).filter(sheet=>sheet&&sheet.wastedBytes>1024);
-
-return{
-results,
-tableHeadings:{
-url:'URL',
-numUnused:'Unused Rules',
-totalKb:'Original',
-potentialSavings:'Potential Savings'}};
-
-
-}}
-
-
-module.exports=UnusedCSSRules;
-
-},{"../../lib/url-shim":25,"./byte-efficiency-audit":4}],"../audits/byte-efficiency/uses-optimized-images":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/bypass":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('./byte-efficiency-audit');
-const URL=require('../../lib/url-shim');
+/**
+ * @fileoverview Ensures each page has at least one mechanism for a user to bypass navigation
+ * and jump straight to the content.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-const IGNORE_THRESHOLD_IN_BYTES=2048;
-const TOTAL_WASTED_BYTES_THRESHOLD=1000*1024;
-const JPEG_ALREADY_OPTIMIZED_THRESHOLD_IN_BYTES=25*1024;
-const WEBP_ALREADY_OPTIMIZED_THRESHOLD_IN_BYTES=100*1024;
+const AxeAudit = require('./axe-audit');
 
-class UsesOptimizedImages extends Audit{
-
-
-
-static get meta(){
-return{
-category:'Images',
-name:'uses-optimized-images',
-description:'Unoptimized images',
-informative:true,
-helpText:'Images should be optimized to save network bytes. '+
-'The following images could have smaller file sizes when compressed with '+
-'[WebP](https://developers.google.com/speed/webp/) or JPEG at 80 quality. '+
-'[Learn more about image optimization](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/image-optimization).',
-requiredArtifacts:['OptimizedImages','networkRecords']};
-
+class Bypass extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'bypass',
+      description: 'The page contains a heading, skip link, or landmark region.',
+      helpText: 'Adding ways to bypass repetitive content lets keyboard users navigate the page ' +
+          'more efficiently.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = Bypass;
 
-
-
-
-
-static computeSavings(image,type){
-const bytes=image.originalSize-image[type+'Size'];
-const percent=100*bytes/image.originalSize;
-return{bytes,percent};
-}
-
-
-
-
-
-
-static audit_(artifacts){
-const images=artifacts.OptimizedImages;
-
-const failedImages=[];
-let totalWastedBytes=0;
-let hasAllEfficientImages=true;
-
-const results=images.reduce((results,image)=>{
-if(image.failed){
-failedImages.push(image);
-return results;
-}else if(image.originalSize<Math.max(IGNORE_THRESHOLD_IN_BYTES,image.webpSize)){
-return results;
-}
-
-const url=URL.getDisplayName(image.url,{preserveQuery:true});
-const webpSavings=UsesOptimizedImages.computeSavings(image,'webp');
-
-if(webpSavings.bytes>WEBP_ALREADY_OPTIMIZED_THRESHOLD_IN_BYTES){
-hasAllEfficientImages=false;
-}else if(webpSavings.bytes<IGNORE_THRESHOLD_IN_BYTES){
-return results;
-}
-
-let jpegSavingsLabel;
-if(/(jpeg|bmp)/.test(image.mimeType)){
-const jpegSavings=UsesOptimizedImages.computeSavings(image,'jpeg');
-if(jpegSavings.bytes>JPEG_ALREADY_OPTIMIZED_THRESHOLD_IN_BYTES){
-hasAllEfficientImages=false;
-}
-if(jpegSavings.bytes>IGNORE_THRESHOLD_IN_BYTES){
-jpegSavingsLabel=this.toSavingsString(jpegSavings.bytes,jpegSavings.percent);
-}
-}
-
-totalWastedBytes+=webpSavings.bytes;
-
-results.push({
-url,
-preview:{url:image.url,mimeType:image.mimeType},
-totalBytes:image.originalSize,
-wastedBytes:webpSavings.bytes,
-webpSavings:this.toSavingsString(webpSavings.bytes,webpSavings.percent),
-jpegSavings:jpegSavingsLabel});
-
-return results;
-},[]);
-
-let debugString;
-if(failedImages.length){
-const urls=failedImages.map(image=>URL.getDisplayName(image.url));
-debugString=`Lighthouse was unable to decode some of your images: ${urls.join(', ')}`;
-}
-
-return{
-passes:hasAllEfficientImages&&totalWastedBytes<TOTAL_WASTED_BYTES_THRESHOLD,
-debugString,
-results,
-tableHeadings:{
-preview:'',
-url:'URL',
-totalKb:'Original',
-webpSavings:'WebP Savings',
-jpegSavings:'JPEG Savings'}};
-
-
-}}
-
-
-module.exports=UsesOptimizedImages;
-
-},{"../../lib/url-shim":25,"./byte-efficiency-audit":4}],"../audits/byte-efficiency/uses-responsive-images":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/color-contrast":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('./byte-efficiency-audit');
-const URL=require('../../lib/url-shim');
+/**
+ * @fileoverview Ensures the contrast between foreground and background colors meets
+ * WCAG 2 AA contrast ratio thresholds.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-const IGNORE_THRESHOLD_IN_BYTES=2048;
-const WASTEFUL_THRESHOLD_IN_BYTES=25*1024;
+const AxeAudit = require('./axe-audit');
 
-class UsesResponsiveImages extends Audit{
-
-
-
-static get meta(){
-return{
-category:'Images',
-name:'uses-responsive-images',
-description:'Oversized Images',
-informative:true,
-helpText:
-'Image sizes served should be based on the device display size to save network bytes. '+
-'Learn more about [responsive images](https://developers.google.com/web/fundamentals/design-and-ui/media/images) '+
-'and [client hints](https://developers.google.com/web/updates/2015/09/automating-resource-selection-with-client-hints).',
-requiredArtifacts:['ImageUsage','ContentWidth','networkRecords']};
-
+class ColorContrast extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'color-contrast',
+      description: 'Background and foreground colors have a sufficient contrast ratio.',
+      helpText: 'Low-contrast text is difficult or impossible for many users to read. ' +
+          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/contrast-ratio).',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = ColorContrast;
 
-
-
-
-
-static computeWaste(image,DPR){
-const url=URL.getDisplayName(image.src,{preserveQuery:true});
-const actualPixels=image.naturalWidth*image.naturalHeight;
-const usedPixels=image.clientWidth*image.clientHeight*Math.pow(DPR,2);
-const wastedRatio=1-usedPixels/actualPixels;
-const totalBytes=image.networkRecord.resourceSize;
-const wastedBytes=Math.round(totalBytes*wastedRatio);
-
-if(!Number.isFinite(wastedRatio)){
-return new Error(`Invalid image sizing information ${url}`);
-}
-
-return{
-url,
-preview:{
-url:image.networkRecord.url,
-mimeType:image.networkRecord.mimeType},
-
-totalBytes,
-wastedBytes,
-wastedPercent:100*wastedRatio,
-isWasteful:wastedBytes>WASTEFUL_THRESHOLD_IN_BYTES};
-
-}
-
-
-
-
-
-
-static audit_(artifacts){
-const images=artifacts.ImageUsage;
-const contentWidth=artifacts.ContentWidth;
-
-let debugString;
-const DPR=contentWidth.devicePixelRatio;
-const resultsMap=images.reduce((results,image)=>{
-
-if(!image.networkRecord||image.networkRecord.mimeType==='image/svg+xml'){
-return results;
-}
-
-const processed=UsesResponsiveImages.computeWaste(image,DPR);
-if(processed instanceof Error){
-debugString=processed.message;
-return results;
-}
-
-
-const existing=results.get(processed.preview.url);
-if(!existing||existing.wastedBytes>processed.wastedBytes){
-results.set(processed.preview.url,processed);
-}
-
-return results;
-},new Map());
-
-const results=Array.from(resultsMap.values()).
-filter(item=>item.wastedBytes>IGNORE_THRESHOLD_IN_BYTES);
-return{
-debugString,
-passes:!results.find(item=>item.isWasteful),
-results,
-tableHeadings:{
-preview:'',
-url:'URL',
-totalKb:'Original',
-potentialSavings:'Potential Savings'}};
-
-
-}}
-
-
-module.exports=UsesResponsiveImages;
-
-},{"../../lib/url-shim":25,"./byte-efficiency-audit":4}],"../audits/cache-start-url":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/definition-list":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
+/**
+ * @fileoverview Ensures `<dl>` elements are structured correctly.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class CacheStartUrl extends Audit{
+const AxeAudit = require('./axe-audit');
 
-
-
-static get meta(){
-return{
-category:'Manifest',
-name:'cache-start-url',
-description:'Cache contains start_url from manifest (alpha)',
-requiredArtifacts:['CacheContents','Manifest','URL']};
-
+class DefinitionList extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'definition-list',
+      description: '`<dl>`\'s contain only properly-ordered `<dt>` and `<dd>` groups, `<script>` ' +
+          'or <template> elements.',
+      helpText: 'When definition lists are not properly marked up screen readers may produce ' +
+          'confusing or inaccurate output.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = DefinitionList;
 
-
-
-
-static audit(artifacts){
-if(!artifacts.Manifest||!artifacts.Manifest.value){
-
-return CacheStartUrl.generateAuditResult({
-rawValue:false});
-
-}
-
-const manifest=artifacts.Manifest.value;
-if(!(manifest.start_url&&manifest.start_url.value)){
-return CacheStartUrl.generateAuditResult({
-rawValue:false,
-debugString:'start_url not present in Manifest'});
-
-}
-
-
-const startURL=manifest.start_url.value;
-
-const altStartURL=startURL.
-replace(/\?utm_([^=]*)=([^&]|$)*/,'').
-replace(/\?$/,'');
-
-
-
-
-
-
-const cacheContents=artifacts.CacheContents;
-const cacheHasStartUrl=cacheContents.find(req=>{
-return startURL===req||altStartURL===req;
-});
-
-return CacheStartUrl.generateAuditResult({
-rawValue:cacheHasStartUrl!==undefined});
-
-}}
-
-
-module.exports=CacheStartUrl;
-
-},{"./audit":3}],"../audits/content-width":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/dlitem":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
+/**
+ * @fileoverview Ensures that all child <dd> and <dt> elements have a <dl> as a parent.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class ContentWidth extends Audit{
+const AxeAudit = require('./axe-audit');
 
-
-
-static get meta(){
-return{
-category:'Mobile Friendly',
-name:'content-width',
-description:'Content is sized correctly for the viewport',
-helpText:'If the width of your app\'s content doesn\'t match the width '+
-'of the viewport, your app might not be optimized for mobile screens. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/content-sized-correctly-for-viewport).',
-requiredArtifacts:['ContentWidth']};
-
+class DLItem extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'dlitem',
+      description: 'Definition list items are wrapped in `<dl>` elements.',
+      helpText: 'Definition list items (<dt> and/or <dd>) wrapped in parent <dl> elements enable ' +
+          'screen readers to properly announce content.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = DLItem;
 
-
-
-
-static audit(artifacts){
-const scrollWidth=artifacts.ContentWidth.scrollWidth;
-const viewportWidth=artifacts.ContentWidth.viewportWidth;
-const widthsMatch=scrollWidth===viewportWidth;
-
-return ContentWidth.generateAuditResult({
-rawValue:widthsMatch,
-debugString:this.createDebugString(widthsMatch,artifacts.ContentWidth)});
-
-}
-
-static createDebugString(match,artifact){
-if(match){
-return'';
-}
-
-return'The content scroll size is '+artifact.scrollWidth+'px, '+
-'whereas the viewport size is '+artifact.viewportWidth+'px.';
-}}
-
-
-module.exports=ContentWidth;
-
-},{"./audit":3}],"../audits/critical-request-chains":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/document-title":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
-const Formatter=require('../report/formatter');
+/**
+ * @fileoverview Ensures that each HTML document contains a `<title>`.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class CriticalRequestChains extends Audit{
+const AxeAudit = require('./axe-audit');
 
-
-
-static get meta(){
-return{
-category:'Performance',
-name:'critical-request-chains',
-description:'Critical Request Chains',
-informative:true,
-optimalValue:0,
-helpText:'The Critical Request Chains below show you what resources are '+
-'required for first render of this page. Improve page load by reducing '+
-'the length of chains, reducing the download size of resources, or '+
-'deferring the download of unnecessary resources. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/critical-request-chains).',
-requiredArtifacts:['networkRecords']};
-
+class DocumentTitle extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'document-title',
+      description: 'Document has a `<title>` element.',
+      helpText: 'Screen reader users use page titles to get an overview of the contents of ' +
+          'the page.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
-static _traverse(tree,cb){
-function walk(node,depth,startTime,transferSize=0){
-const children=Object.keys(node);
-if(children.length===0){
-return;
-}
-children.forEach(id=>{
-const child=node[id];
-if(!startTime){
-startTime=child.request.startTime;
-}
+module.exports = DocumentTitle;
 
-
-cb({
-depth,
-id,
-node:child,
-chainDuration:(child.request.endTime-startTime)*1000,
-chainTransferSize:transferSize+child.request.transferSize});
-
-
-
-walk(child.children,depth+1,startTime);
-},'');
-}
-
-walk(tree,0);
-}
-
-
-
-
-
-static _getLongestChain(tree){
-const longest={
-duration:0,
-length:0,
-transferSize:0};
-
-CriticalRequestChains._traverse(tree,opts=>{
-const duration=opts.chainDuration;
-if(duration>longest.duration){
-longest.duration=duration;
-longest.transferSize=opts.chainTransferSize;
-longest.length=opts.depth;
-}
-});
-
-longest.length++;
-return longest;
-}
-
-
-
-
-
-
-static audit(artifacts){
-const networkRecords=artifacts.networkRecords[Audit.DEFAULT_PASS];
-return artifacts.requestCriticalRequestChains(networkRecords).then(chains=>{
-let chainCount=0;
-function walk(node,depth){
-const children=Object.keys(node);
-
-
-
-if(children.length===0){
-chainCount++;
-}
-
-children.forEach(id=>{
-const child=node[id];
-walk(child.children,depth+1);
-},'');
-}
-
-
-const initialNavKey=Object.keys(chains)[0];
-const initialNavChildren=initialNavKey&&chains[initialNavKey].children;
-if(initialNavChildren&&Object.keys(initialNavChildren).length>0){
-walk(initialNavChildren,0);
-}
-
-return CriticalRequestChains.generateAuditResult({
-rawValue:chainCount<=this.meta.optimalValue,
-displayValue:chainCount,
-optimalValue:this.meta.optimalValue,
-extendedInfo:{
-formatter:Formatter.SUPPORTED_FORMATS.CRITICAL_REQUEST_CHAINS,
-value:{
-chains,
-longestChain:CriticalRequestChains._getLongestChain(chains)}}});
-
-
-
-});
-}}
-
-
-module.exports=CriticalRequestChains;
-
-},{"../report/formatter":27,"./audit":3}],"../audits/deprecations":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/duplicate-id":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
+/**
+ * @fileoverview Ensures every id attribute value is unique.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
+const AxeAudit = require('./axe-audit');
 
-
-
-
-
-const Audit=require('./audit');
-const Formatter=require('../report/formatter');
-
-class Deprecations extends Audit{
-
-
-
-static get meta(){
-return{
-category:'Deprecations',
-name:'deprecations',
-description:'Avoids deprecated APIs',
-helpText:'Deprecated APIs will eventually be removed from the browser. '+
-'[Learn more](https://www.chromestatus.com/features#deprecated).',
-requiredArtifacts:['ChromeConsoleMessages']};
-
+class DuplicateId extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'duplicate-id',
+      description: '`[id]` attributes on the page are unique.',
+      helpText: 'Unique `id=""` attributes help ensure assistive technologies do not overlook ' +
+          'elements with the same id.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = DuplicateId;
 
-
-
-
-static audit(artifacts){
-const entries=artifacts.ChromeConsoleMessages;
-
-const deprecations=entries.filter(log=>log.entry.source==='deprecation').
-map(log=>{
-
-const label=log.entry.lineNumber?`line: ${log.entry.lineNumber}`:'line: ???';
-const url=log.entry.url||'Unable to determine URL';
-return Object.assign({
-label,
-url,
-code:log.entry.text},
-log.entry);
-});
-
-let displayValue='';
-if(deprecations.length>1){
-displayValue=`${deprecations.length} warnings found`;
-}else if(deprecations.length===1){
-displayValue=`${deprecations.length} warning found`;
-}
-
-return Deprecations.generateAuditResult({
-rawValue:deprecations.length===0,
-displayValue,
-extendedInfo:{
-formatter:Formatter.SUPPORTED_FORMATS.URL_LIST,
-value:deprecations}});
-
-
-}}
-
-
-module.exports=Deprecations;
-
-},{"../report/formatter":27,"./audit":3}],"../audits/dobetterweb/appcache-manifest":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/frame-title":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('../audit');
+/**
+ * @fileoverview Ensures `<iframe>` and `<frame>` elements contain a non-empty title attribute.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class AppCacheManifestAttr extends Audit{
+const AxeAudit = require('./axe-audit');
 
-
-
-
-static get meta(){
-return{
-category:'Offline',
-name:'appcache-manifest',
-description:'Avoids Application Cache',
-helpText:'Application Cache has been [deprecated](https://html.spec.whatwg.org/multipage/browsers.html#offline) by [Service Workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers). Consider implementing an offline solution using the [Cache Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Cache).',
-requiredArtifacts:['AppCacheManifest']};
-
+class FrameTitle extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'frame-title',
+      description: '`<frame>` or `<iframe>` elements have a title.',
+      helpText: 'Screen reader users rely on a frame title to describe the contents of the frame.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = FrameTitle;
 
-
-
-
-static audit(artifacts){
-const usingAppcache=artifacts.AppCacheManifest!==null;
-const debugString=usingAppcache?
-`Found <html manifest="${artifacts.AppCacheManifest}">.`:'';
-
-return AppCacheManifestAttr.generateAuditResult({
-rawValue:!usingAppcache,
-debugString});
-
-}}
-
-
-module.exports=AppCacheManifestAttr;
-
-},{"../audit":3}],"../audits/dobetterweb/dom-size":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/html-has-lang":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('../audit');
-const TracingProcessor=require('../../lib/traces/tracing-processor');
-const Formatter=require('../../report/formatter');
+/**
+ * @fileoverview Ensures every HTML document has a `lang` attribute.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-const MAX_DOM_NODES=1500;
-const MAX_DOM_TREE_WIDTH=60;
-const MAX_DOM_TREE_DEPTH=32;
+const AxeAudit = require('./axe-audit');
 
-
-const SCORING_POINT_OF_DIMINISHING_RETURNS=2400;
-const SCORING_MEDIAN=3000;
-
-class DOMSize extends Audit{
-static get MAX_DOM_NODES(){
-return MAX_DOM_NODES;
+class HTMLHasLang extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'html-has-lang',
+      description: '`<html>` element has a `[lang]` attribute.',
+      helpText: 'The `lang` attribute is useful for multilingual screen reader users who may ' +
+          'prefer a language other than the default.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = HTMLHasLang;
 
-
-
-static get meta(){
-return{
-category:'Performance',
-name:'dom-size',
-description:'Avoids an excessive DOM size',
-optimalValue:DOMSize.MAX_DOM_NODES.toLocaleString()+' nodes',
-helpText:'Browser engineers recommend pages contain fewer than '+
-`~${DOMSize.MAX_DOM_NODES.toLocaleString()} DOM nodes. The sweet spot is a tree depth < `+
-`${MAX_DOM_TREE_DEPTH} elements and fewer than ${MAX_DOM_TREE_WIDTH} `+
-'children/parent element. A large DOM can increase memory, cause longer '+
-'[style calculations](https://developers.google.com/web/fundamentals/performance/rendering/reduce-the-scope-and-complexity-of-style-calculations), '+
-'and produce costly [layout reflows](https://developers.google.com/speed/articles/reflow). [Learn more](https://developers.google.com/web/fundamentals/performance/rendering/).',
-requiredArtifacts:['DOMStats']};
-
-}
-
-
-
-
-
-static audit(artifacts){
-const stats=artifacts.DOMStats;
-
-
-
-
-
-
-
-const depthSnippet=stats.depth.pathToElement.reduce((str,curr,i)=>{
-return`${str}\n`+'  '.repeat(i)+`${curr} >`;
-},'').replace(/>$/g,'').trim();
-const widthSnippet='Element with most children:\n'+
-stats.width.pathToElement[stats.width.pathToElement.length-1];
-
-
-
-
-
-const distribution=TracingProcessor.getLogNormalDistribution(
-SCORING_MEDIAN,SCORING_POINT_OF_DIMINISHING_RETURNS);
-let score=100*distribution.computeComplementaryPercentile(stats.totalDOMNodes);
-
-
-score=Math.max(0,Math.min(100,score));
-
-const cards=[{
-title:'Total DOM Nodes',
-value:stats.totalDOMNodes.toLocaleString(),
-target:`< ${MAX_DOM_NODES.toLocaleString()} nodes`},
-{
-title:'DOM Depth',
-value:stats.depth.max.toLocaleString(),
-snippet:depthSnippet,
-target:`< ${MAX_DOM_TREE_DEPTH.toLocaleString()}`},
-{
-title:'Maximum Children',
-value:stats.width.max.toLocaleString(),
-snippet:widthSnippet,
-target:`< ${MAX_DOM_TREE_WIDTH.toLocaleString()} nodes`}];
-
-
-return DOMSize.generateAuditResult({
-rawValue:stats.totalDOMNodes,
-optimalValue:this.meta.optimalValue,
-score:Math.round(score),
-displayValue:`${stats.totalDOMNodes.toLocaleString()} nodes`,
-extendedInfo:{
-formatter:Formatter.SUPPORTED_FORMATS.CARDS,
-value:cards}});
-
-
-}}
-
-
-
-module.exports=DOMSize;
-
-},{"../../lib/traces/tracing-processor":24,"../../report/formatter":27,"../audit":3}],"../audits/dobetterweb/external-anchors-use-rel-noopener":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/html-lang-valid":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const URL=require('../../lib/url-shim');
-const Audit=require('../audit');
-const Formatter=require('../../report/formatter');
+/**
+ * @fileoverview Ensures the lang attribute of the <html> element has a valid value.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class ExternalAnchorsUseRelNoopenerAudit extends Audit{
+const AxeAudit = require('./axe-audit');
 
-
-
-static get meta(){
-return{
-category:'Performance',
-name:'external-anchors-use-rel-noopener',
-description:'Opens external anchors using `rel="noopener"`',
-helpText:'Open new tabs using `rel="noopener"` to improve performance and '+
-'prevent security vulnerabilities. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/noopener).',
-requiredArtifacts:['URL','AnchorsWithNoRelNoopener']};
-
+class HTMLLangValid extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'html-lang-valid',
+      description: '`<html>` element has a valid value for its `[lang]` attribute.',
+      helpText: 'Specifying a valid [BCP 47 language](https://www.w3.org/International/questions/qa-choosing-language-tags#question) ' +
+          'helps screen readers announce text properly.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = HTMLLangValid;
 
-
-
-
-static audit(artifacts){
-let debugString;
-const pageHost=new URL(artifacts.URL.finalUrl).host;
-
-
-
-
-const failingAnchors=artifacts.AnchorsWithNoRelNoopener.
-filter(anchor=>{
-try{
-return anchor.href===''||new URL(anchor.href).host!==pageHost;
-}catch(err){
-debugString='Lighthouse was unable to determine the destination '+
-'of some anchor tags. If they are not used as hyperlinks, '+
-'consider removing the _blank target.';
-return true;
-}
-}).
-map(anchor=>{
-return{
-url:'<a'+(
-anchor.href?` href="${anchor.href}"`:'')+(
-anchor.target?` target="${anchor.target}"`:'')+(
-anchor.rel?` rel="${anchor.rel}"`:'')+'>'};
-
-});
-
-return ExternalAnchorsUseRelNoopenerAudit.generateAuditResult({
-rawValue:failingAnchors.length===0,
-extendedInfo:{
-formatter:Formatter.SUPPORTED_FORMATS.URL_LIST,
-value:failingAnchors},
-
-debugString});
-
-}}
-
-
-module.exports=ExternalAnchorsUseRelNoopenerAudit;
-
-},{"../../lib/url-shim":25,"../../report/formatter":27,"../audit":3}],"../audits/dobetterweb/geolocation-on-start":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/image-alt":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('../audit');
-const Formatter=require('../../report/formatter');
+/**
+ * @fileoverview Ensures <img> elements have alternate text or a role of none or presentation.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class GeolocationOnStart extends Audit{
+const AxeAudit = require('./axe-audit');
 
-
-
-static get meta(){
-return{
-category:'UX',
-name:'geolocation-on-start',
-description:'Avoids requesting the geolocation permission on page load',
-helpText:'Users are mistrustful of or confused by sites that request their '+
-'location without context. Consider tying the request to user gestures instead. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/geolocation-on-load).',
-requiredArtifacts:['GeolocationOnStart']};
-
+class ImageAlt extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'image-alt',
+      description: 'Image elements have `[alt]` attributes.',
+      helpText: 'Informative elements should aim for short, descriptive alternate text. ' +
+          'Decorative elements can be ignored with an empty alt attributes, e.g. [alt=""].' +
+          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/alt-attribute).',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = ImageAlt;
 
-
-
-
-static audit(artifacts){
-const results=artifacts.GeolocationOnStart.map(err=>{
-return Object.assign({
-label:`line: ${err.line}, col: ${err.col}`},
-err);
-});
-
-return GeolocationOnStart.generateAuditResult({
-rawValue:results.length===0,
-extendedInfo:{
-formatter:Formatter.SUPPORTED_FORMATS.URL_LIST,
-value:results}});
-
-
-}}
-
-
-
-module.exports=GeolocationOnStart;
-
-},{"../../report/formatter":27,"../audit":3}],"../audits/dobetterweb/link-blocking-first-paint":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/input-image-alt":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('../audit');
-const URL=require('../../lib/url-shim');
-const Formatter=require('../../report/formatter');
+/**
+ * @fileoverview Ensures `<input type="image">` elements have alternate text.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class LinkBlockingFirstPaintAudit extends Audit{
+const AxeAudit = require('./axe-audit');
 
-
-
-
-static get meta(){
-return{
-category:'Performance',
-name:'link-blocking-first-paint',
-description:'Render-blocking Stylesheets',
-informative:true,
-helpText:'Link elements are blocking the first paint of your page. Consider '+
-'inlining critical links and deferring non-critical ones. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/blocking-resources).',
-requiredArtifacts:['TagsBlockingFirstPaint']};
-
+class InputImageAlt extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'input-image-alt',
+      description: '`<input type="image">` elements have `[alt]` text.',
+      helpText: 'When an image is being used as an `<input>` button, providing alternative text ' +
+          'can help screen reader users understand the purpose of the button.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = InputImageAlt;
 
-
-
-
-
-static computeAuditResultForTags(artifacts,tagFilter){
-const artifact=artifacts.TagsBlockingFirstPaint;
-
-const filtered=artifact.filter(item=>item.tag.tagName===tagFilter);
-
-const startTime=filtered.reduce((t,item)=>Math.min(t,item.startTime),Number.MAX_VALUE);
-let endTime=0;
-
-const results=filtered.map(item=>{
-endTime=Math.max(item.endTime,endTime);
-
-return{
-url:URL.getDisplayName(item.tag.url),
-totalKb:`${Math.round(item.transferSize/1024)} KB`,
-totalMs:`${Math.round((item.endTime-startTime)*1000)}ms`};
-
-});
-
-const delayTime=Math.round((endTime-startTime)*1000);
-let displayValue='';
-if(results.length>1){
-displayValue=`${results.length} resources delayed first paint by ${delayTime}ms`;
-}else if(results.length===1){
-displayValue=`${results.length} resource delayed first paint by ${delayTime}ms`;
-}
-
-return{
-displayValue,
-rawValue:results.length===0,
-extendedInfo:{
-formatter:Formatter.SUPPORTED_FORMATS.TABLE,
-value:{
-results,
-tableHeadings:{
-url:'URL',
-totalKb:'Size (KB)',
-totalMs:'Delayed Paint By (ms)'}}}};
-
-
-
-
-}
-
-
-
-
-
-static audit(artifacts){
-const result=LinkBlockingFirstPaintAudit.computeAuditResultForTags(artifacts,'LINK');
-return LinkBlockingFirstPaintAudit.generateAuditResult(result);
-}}
-
-
-module.exports=LinkBlockingFirstPaintAudit;
-
-},{"../../lib/url-shim":25,"../../report/formatter":27,"../audit":3}],"../audits/dobetterweb/no-console-time":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/label":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const URL=require('../../lib/url-shim');
-const Audit=require('../audit');
-const Formatter=require('../../report/formatter');
+/**
+ * @fileoverview Ensures every form element has a label.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class NoConsoleTimeAudit extends Audit{
+const AxeAudit = require('./axe-audit');
 
-
-
-
-static get meta(){
-return{
-category:'JavaScript',
-name:'no-console-time',
-description:'Avoids `console.time()` in its own scripts',
-helpText:'Consider using `performance.mark()` and `performance.measure()` '+
-'from the User Timing API instead. They provide high-precision timestamps, '+
-'independent of the system clock, and are integrated in the Chrome DevTools Timeline. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/console-time).',
-requiredArtifacts:['URL','ConsoleTimeUsage']};
-
+class Label extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'label',
+      description: 'Form elements have associated labels.',
+      helpText: 'Labels ensure that form controls are announced properly by assistive ' +
+          'technologies, like screen readers. [Learn ' +
+          'more](https://developers.google.com/web/tools/lighthouse/audits/form-labels).',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = Label;
 
-
-
-
-static audit(artifacts){
-let debugString;
-
-const results=artifacts.ConsoleTimeUsage.filter(err=>{
-if(err.isEval){
-return!!err.url;
-}
-
-if(URL.isValid(err.url)){
-return URL.hostsMatch(artifacts.URL.finalUrl,err.url);
-}
-
-
-
-debugString=URL.INVALID_URL_DEBUG_STRING;
-return true;
-});
-
-return NoConsoleTimeAudit.generateAuditResult({
-rawValue:results.length===0,
-extendedInfo:{
-formatter:Formatter.SUPPORTED_FORMATS.TABLE,
-value:{
-results,
-tableHeadings:{url:'URL',lineCol:'Line/Col',isEval:'Eval\'d?'}}},
-
-
-debugString});
-
-}}
-
-
-module.exports=NoConsoleTimeAudit;
-
-},{"../../lib/url-shim":25,"../../report/formatter":27,"../audit":3}],"../audits/dobetterweb/no-datenow":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/layout-table":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const URL=require('../../lib/url-shim');
-const Audit=require('../audit');
-const Formatter=require('../../report/formatter');
+/**
+ * @fileoverview Ensures presentational `<table>` elements do not use `<th>`, `<caption>` elements
+ * or the summary attribute.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class NoDateNowAudit extends Audit{
+const AxeAudit = require('./axe-audit');
 
-
-
-
-static get meta(){
-return{
-category:'JavaScript',
-name:'no-datenow',
-description:'Avoids `Date.now()` in its own scripts',
-helpText:'Consider using `performance.now()` from the User Timing API '+
-'instead. It provides high-precision timestamps, independent of the system '+
-'clock. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/date-now).',
-requiredArtifacts:['URL','DateNowUse']};
-
+class LayoutTable extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'layout-table',
+      description: 'Presentational `<table>` elements avoid using `<th>`, `<caption>` or the ' +
+          '`[summary]` attribute.',
+      helpText: 'The presence of `<th>`, `<caption>` or the `summary` attribute on a ' +
+          'presentational table may produce a confusing experince for a screen reader user as ' +
+          'these elements usually indicates a data table.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = LayoutTable;
 
-
-
-
-static audit(artifacts){
-let debugString;
-
-const results=artifacts.DateNowUse.filter(err=>{
-if(err.isEval){
-return!!err.url;
-}
-
-if(URL.isValid(err.url)){
-return URL.hostsMatch(artifacts.URL.finalUrl,err.url);
-}
-
-
-
-debugString=URL.INVALID_URL_DEBUG_STRING;
-return true;
-});
-
-return NoDateNowAudit.generateAuditResult({
-rawValue:results.length===0,
-extendedInfo:{
-formatter:Formatter.SUPPORTED_FORMATS.TABLE,
-value:{
-results,
-tableHeadings:{url:'URL',lineCol:'Line/Col',isEval:'Eval\'d?'}}},
-
-
-debugString});
-
-}}
-
-
-module.exports=NoDateNowAudit;
-
-},{"../../lib/url-shim":25,"../../report/formatter":27,"../audit":3}],"../audits/dobetterweb/no-document-write":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/link-name":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('../audit');
-const Formatter=require('../../report/formatter');
+/**
+ * @fileoverview Ensures links have discernible text.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class NoDocWriteAudit extends Audit{
+const AxeAudit = require('./axe-audit');
 
-
-
-
-static get meta(){
-return{
-category:'Performance',
-name:'no-document-write',
-description:'Avoids `document.write()`',
-helpText:'For users on slow connections, external scripts dynamically injected via '+
-'`document.write()` can delay page load by tens of seconds. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/document-write).',
-requiredArtifacts:['DocWriteUse']};
-
+class LinkName extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'link-name',
+      description: 'Links have a discernable name.',
+      helpText: 'Link text (and alternate text for images, when used as links) that is ' +
+          'discernible, not duplicated, and focusable improves the navigating experience for ' +
+          'screen reader users.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = LinkName;
 
-
-
-
-static audit(artifacts){
-const results=artifacts.DocWriteUse.map(err=>{
-return Object.assign({
-label:`line: ${err.line}, col: ${err.col}`},
-err);
-});
-
-return NoDocWriteAudit.generateAuditResult({
-rawValue:results.length===0,
-extendedInfo:{
-formatter:Formatter.SUPPORTED_FORMATS.URL_LIST,
-value:results}});
-
-
-}}
-
-
-module.exports=NoDocWriteAudit;
-
-},{"../../report/formatter":27,"../audit":3}],"../audits/dobetterweb/no-mutation-events":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/listitem":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const URL=require('../../lib/url-shim');
-const Audit=require('../audit');
-const EventHelpers=require('../../lib/event-helpers');
-const Formatter=require('../../report/formatter');
+/**
+ * @fileoverview Ensures every HTML document has a `lang` attribute.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class NoMutationEventsAudit extends Audit{
+const AxeAudit = require('./axe-audit');
 
-static get MUTATION_EVENTS(){
-return[
-'DOMAttrModified',
-'DOMAttributeNameChanged',
-'DOMCharacterDataModified',
-'DOMElementNameChanged',
-'DOMNodeInserted',
-'DOMNodeInsertedIntoDocument',
-'DOMNodeRemoved',
-'DOMNodeRemovedFromDocument',
-'DOMSubtreeModified'];
-
+class ListItem extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'listitem',
+      description: 'List items (`<li>`) are contained within `<ul>` or `<ol>` parent elements.',
+      helpText: 'Screen readers require list items (`<li>`) to be contained within a ' +
+          'parent `<ul>` or `<ol>` to be announced properly',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = ListItem;
 
-
-
-static get meta(){
-return{
-category:'JavaScript',
-name:'no-mutation-events',
-description:'Avoids Mutation Events in its own scripts',
-helpText:'Mutation Events are deprecated and harm performance. Consider using Mutation '+
-'Observers instead. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/mutation-events).',
-requiredArtifacts:['URL','EventListeners']};
-
-}
-
-
-
-
-
-static audit(artifacts){
-let debugString;
-const listeners=artifacts.EventListeners;
-
-const results=listeners.filter(loc=>{
-const isMutationEvent=this.MUTATION_EVENTS.includes(loc.type);
-let sameHost=URL.hostsMatch(artifacts.URL.finalUrl,loc.url);
-
-if(!URL.isValid(loc.url)){
-sameHost=true;
-debugString=URL.INVALID_URL_DEBUG_STRING;
-}
-
-return sameHost&&isMutationEvent;
-}).map(EventHelpers.addFormattedCodeSnippet);
-
-const groupedResults=EventHelpers.groupCodeSnippetsByLocation(results);
-
-return NoMutationEventsAudit.generateAuditResult({
-rawValue:groupedResults.length===0,
-extendedInfo:{
-formatter:Formatter.SUPPORTED_FORMATS.TABLE,
-value:{
-results:groupedResults,
-tableHeadings:{url:'URL',lineCol:'Line/Col',type:'Event',code:'Snippet'}}},
-
-
-debugString});
-
-}}
-
-
-module.exports=NoMutationEventsAudit;
-
-},{"../../lib/event-helpers":17,"../../lib/url-shim":25,"../../report/formatter":27,"../audit":3}],"../audits/dobetterweb/no-old-flexbox":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/list":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('../audit');
-const URL=require('../../lib/url-shim');
-const StyleHelpers=require('../../lib/styles-helpers');
-const Formatter=require('../../report/formatter');
+/**
+ * @fileoverview Ensures that lists are structured correctly.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class NoOldFlexboxAudit extends Audit{
+const AxeAudit = require('./axe-audit');
 
-
-
-
-static get meta(){
-return{
-category:'CSS',
-name:'no-old-flexbox',
-description:'Avoids old CSS flexbox',
-helpText:'The 2009 spec of Flexbox is deprecated and is 2.3x slower than the latest '+
-'spec. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/old-flexbox).',
-requiredArtifacts:['Styles','URL']};
-
+class List extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'list',
+      description: 'Lists contain only `<li>` elements and script supporting elements ' +
+          '(`<script>` and `<template>`).',
+      helpText: 'Screen readers have a specific way of announcing lists. Ensuring proper list ' +
+          'structure aides screen reader output.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = List;
 
-
-
-
-static audit(artifacts){
-
-
-const displayPropResults=StyleHelpers.filterStylesheetsByUsage(artifacts.Styles,
-'display',StyleHelpers.addVendorPrefixes(['box','flexbox']));
-const otherPropResults=StyleHelpers.filterStylesheetsByUsage(artifacts.Styles,
-StyleHelpers.addVendorPrefixes(['box-flex','box-orient','box-flex-group']));
-
-const sheetsUsingOldFlexbox=displayPropResults.concat(otherPropResults);
-
-const pageUrl=artifacts.URL.finalUrl;
-
-const urlList=[];
-sheetsUsingOldFlexbox.forEach(sheet=>{
-sheet.parsedContent.forEach(props=>{
-const formattedStyleRule=StyleHelpers.getFormattedStyleRule(sheet.content,props);
-
-let url=sheet.header.sourceURL;
-if(!URL.isValid(url)||url===pageUrl){
-url='inline';
-}else{
-url=URL.getDisplayName(url);
-}
-
-urlList.push({
-url,
-location:formattedStyleRule.location,
-startLine:formattedStyleRule.startLine,
-pre:formattedStyleRule.styleRule});
-
-});
-});
-
-return NoOldFlexboxAudit.generateAuditResult({
-rawValue:sheetsUsingOldFlexbox.length===0,
-extendedInfo:{
-formatter:Formatter.SUPPORTED_FORMATS.TABLE,
-value:{
-results:urlList,
-tableHeadings:{
-url:'URL',startLine:'Line in the stylesheet / <style>',location:'Column start/end',
-pre:'Snippet'}}}});
-
-
-
-}}
-
-
-module.exports=NoOldFlexboxAudit;
-
-},{"../../lib/styles-helpers":22,"../../lib/url-shim":25,"../../report/formatter":27,"../audit":3}],"../audits/dobetterweb/no-websql":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/meta-refresh":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('../audit');
+/**
+ * @fileoverview Ensures `<meta http-equiv="refresh">` is not used.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class NoWebSQLAudit extends Audit{
+const AxeAudit = require('./axe-audit');
 
-
-
-
-static get meta(){
-return{
-category:'Offline',
-name:'no-websql',
-description:'Avoids WebSQL DB',
-helpText:'Web SQL is deprecated. Consider using IndexedDB instead. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/web-sql).',
-requiredArtifacts:['WebSQL']};
-
+class MetaRefresh extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'meta-refresh',
+      description: 'The document does not use `<meta http-equiv="refresh">`.',
+      helpText: 'Users do not expect a page to refresh automatically, and doing so will move ' +
+          'focus back to the top of the page. This may create a frustrating or ' +
+          'confusing experience',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = MetaRefresh;
 
-
-
-
-static audit(artifacts){
-const db=artifacts.WebSQL;
-const debugString=db?
-`Found database "${db.name}", version: ${db.version}.`:'';
-
-return NoWebSQLAudit.generateAuditResult({
-rawValue:!db,
-debugString});
-
-}}
-
-
-module.exports=NoWebSQLAudit;
-
-},{"../audit":3}],"../audits/dobetterweb/notification-on-start":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/meta-viewport":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('../audit');
-const Formatter=require('../../report/formatter');
+/**
+ * @fileoverview Ensures `<meta name="viewport">` does not disable text scaling and zooming.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class NotificationOnStart extends Audit{
+const AxeAudit = require('./axe-audit');
 
-
-
-static get meta(){
-return{
-category:'UX',
-name:'notification-on-start',
-description:'Avoids requesting the notification permission on page load',
-helpText:'Users are mistrustful of or confused by sites that request to send '+
-'notifications without context. Consider tying the request to user gestures '+
-'instead. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/notifications-on-load).',
-requiredArtifacts:['NotificationOnStart']};
-
+class MetaViewport extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'meta-viewport',
+      description: '`[user-scalable="no"]` is not used in the `<meta name="viewport">` ' +
+          'element and the `[maximum-scale]` attribute is not less than 5.',
+      helpText: 'Disabling zooming is problematic for users with low vision who rely on screen ' +
+          'magnification to properly see the contents of a web page.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = MetaViewport;
 
-
-
-
-static audit(artifacts){
-const results=artifacts.NotificationOnStart.map(err=>{
-return Object.assign({
-label:`line: ${err.line}, col: ${err.col}`},
-err);
-});
-
-return NotificationOnStart.generateAuditResult({
-rawValue:results.length===0,
-extendedInfo:{
-formatter:Formatter.SUPPORTED_FORMATS.URL_LIST,
-value:results}});
-
-
-}}
-
-
-
-module.exports=NotificationOnStart;
-
-},{"../../report/formatter":27,"../audit":3}],"../audits/dobetterweb/script-blocking-first-paint":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/object-alt":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('../audit');
-const LinkBlockingFirstPaintAudit=require('./link-blocking-first-paint');
+/**
+ * @fileoverview Ensures <object> elements have alternate text.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class ScriptBlockingFirstPaint extends Audit{
+const AxeAudit = require('./axe-audit');
 
-
-
-
-static get meta(){
-return{
-category:'Performance',
-name:'script-blocking-first-paint',
-description:'Render-blocking scripts',
-informative:true,
-helpText:'Script elements are blocking the first paint of your page. Consider inlining '+
-'critical scripts and deferring non-critical ones. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/blocking-resources).',
-requiredArtifacts:['TagsBlockingFirstPaint']};
-
+class ObjectAlt extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'object-alt',
+      description: '`<object>` elements have `[alt]` text.',
+      helpText: 'Screen readers cannot translate non-text content. Adding alt text to `<object>` ' +
+          'elements will help a screen reader convey the meaning to a user.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = ObjectAlt;
 
-
-
-
-static audit(artifacts){
-const result=LinkBlockingFirstPaintAudit.computeAuditResultForTags(artifacts,'SCRIPT');
-return ScriptBlockingFirstPaint.generateAuditResult(result);
-}}
-
-
-module.exports=ScriptBlockingFirstPaint;
-
-},{"../audit":3,"./link-blocking-first-paint":"../audits/dobetterweb/link-blocking-first-paint"}],"../audits/dobetterweb/uses-http2":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/tabindex":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const URL=require('../../lib/url-shim');
-const Audit=require('../audit');
-const Formatter=require('../../report/formatter');
+/**
+ * @fileoverview Ensures tabindex attribute values are not greater than 0.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class UsesHTTP2Audit extends Audit{
+const AxeAudit = require('./axe-audit');
 
-
-
-
-static get meta(){
-return{
-category:'Performance',
-name:'uses-http2',
-description:'Uses HTTP/2 for its own resources',
-helpText:'HTTP/2 offers many benefits over HTTP/1.1, including binary headers, '+
-'multiplexing, and server push. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/http2).',
-requiredArtifacts:['URL','networkRecords']};
-
+class TabIndex extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'tabindex',
+      description: 'No element has a `[tabindex]` value greater than 0.',
+      helpText: 'A value greater than 0 implies an explicit navigation ordering. ' +
+          'Although technically valid, this often creates frustrating experiences ' +
+          'for users who rely on assistive technologies. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/tabindex).',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = TabIndex;
 
-
-
-
-static audit(artifacts){
-const networkRecords=artifacts.networkRecords[Audit.DEFAULT_PASS];
-const finalHost=new URL(artifacts.URL.finalUrl).host;
-
-
-const resources=networkRecords.filter(record=>{
-const requestHost=new URL(record._url).host;
-const sameHost=requestHost===finalHost;
-const notH2=/HTTP\/[01][\.\d]?/i.test(record.protocol);
-return sameHost&&notH2;
-}).map(record=>{
-return{
-protocol:record.protocol,
-url:record.url};
-
-});
-
-let displayValue='';
-if(resources.length>1){
-displayValue=`${resources.length} requests were not handled over h2`;
-}else if(resources.length===1){
-displayValue=`${resources.length} request was not handled over h2`;
-}
-
-return UsesHTTP2Audit.generateAuditResult({
-rawValue:resources.length===0,
-displayValue:displayValue,
-extendedInfo:{
-formatter:Formatter.SUPPORTED_FORMATS.TABLE,
-value:{
-results:resources,
-tableHeadings:{url:'URL',protocol:'Protocol'}}}});
-
-
-
-}}
-
-
-module.exports=UsesHTTP2Audit;
-
-},{"../../lib/url-shim":25,"../../report/formatter":27,"../audit":3}],"../audits/dobetterweb/uses-passive-event-listeners":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/td-headers-attr":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const URL=require('../../lib/url-shim');
-const Audit=require('../audit');
-const EventHelpers=require('../../lib/event-helpers');
-const Formatter=require('../../report/formatter');
+/**
+ * @fileoverview Ensure that each cell in a table using the headers refers to another cell in
+ * that table
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class PassiveEventsAudit extends Audit{
+const AxeAudit = require('./axe-audit');
 
-static get SCROLL_BLOCKING_EVENTS(){
-
-return['wheel','mousewheel','touchstart','touchmove'];
+class TDHeadersAttr extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'td-headers-attr',
+      description: 'Cells in a `<table>` element that use the `[headers]` attribute only refer ' +
+          'to other cells of that same table.',
+      helpText: 'Screen readers have features to make navigating tables easier. Ensuring `<td>` ' +
+          'cells using the `[headers]` attribute only refer to other cells in the same table may ' +
+          'improve the experience for screen reader users.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = TDHeadersAttr;
 
-
-
-static get meta(){
-return{
-category:'JavaScript',
-name:'uses-passive-event-listeners',
-description:'Uses passive listeners to improve scrolling performance',
-helpText:'Consider marking your touch and wheel event listeners as `passive` '+
-'to improve your page\'s scroll performance. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/passive-event-listeners).',
-requiredArtifacts:['URL','EventListeners']};
-
-}
-
-
-
-
-
-static audit(artifacts){
-let debugString;
-const listeners=artifacts.EventListeners;
-
-
-
-const results=listeners.filter(loc=>{
-const isScrollBlocking=this.SCROLL_BLOCKING_EVENTS.includes(loc.type);
-const mentionsPreventDefault=loc.handler.description.match(
-/\.preventDefault\(\s*\)/g);
-let sameHost=URL.hostsMatch(artifacts.URL.finalUrl,loc.url);
-
-if(!URL.isValid(loc.url)){
-sameHost=true;
-debugString=URL.INVALID_URL_DEBUG_STRING;
-}
-
-return sameHost&&isScrollBlocking&&!loc.passive&&
-!mentionsPreventDefault;
-}).map(EventHelpers.addFormattedCodeSnippet);
-
-const groupedResults=EventHelpers.groupCodeSnippetsByLocation(results);
-
-return PassiveEventsAudit.generateAuditResult({
-rawValue:groupedResults.length===0,
-extendedInfo:{
-formatter:Formatter.SUPPORTED_FORMATS.TABLE,
-value:{
-results:groupedResults,
-tableHeadings:{url:'URL',lineCol:'Line/Col',type:'Type',pre:'Snippet'}}},
-
-
-debugString});
-
-}}
-
-
-module.exports=PassiveEventsAudit;
-
-},{"../../lib/event-helpers":17,"../../lib/url-shim":25,"../../report/formatter":27,"../audit":3}],"../audits/estimated-input-latency":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/th-has-data-cells":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
-const TracingProcessor=require('../lib/traces/tracing-processor');
-const Formatter=require('../report/formatter');
+/**
+ * @fileoverview Ensure that each table header in a data table refers to data cells.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
+const AxeAudit = require('./axe-audit');
 
-
-const SCORING_POINT_OF_DIMINISHING_RETURNS=50;
-const SCORING_MEDIAN=100;
-
-class EstimatedInputLatency extends Audit{
-
-
-
-static get meta(){
-return{
-category:'Performance',
-name:'estimated-input-latency',
-description:'Estimated Input Latency',
-optimalValue:SCORING_POINT_OF_DIMINISHING_RETURNS.toLocaleString()+'ms',
-helpText:'The score above is an estimate of how long your app takes to respond to user '+
-'input, in milliseconds. There is a 90% probability that a user encounters this amount '+
-'of latency, or less. 10% of the time a user can expect additional latency. If your '+
-'score is higher than Lighthouse\'s target score, users may perceive your app as '+
-'laggy. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/estimated-input-latency).',
-requiredArtifacts:['traces']};
-
+class THHasDataCells extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'th-has-data-cells',
+      description: '`<th>` elements and elements with `[role="columnheader"/"rowheader"]` have ' +
+          'data cells they describe.',
+      helpText: 'Screen readers have features to make navigating tables easier. Ensuring table ' +
+          'headers always refer to some set of cells may improve the experience for screen ' +
+          'reader users.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
-static calculate(speedline,model,trace){
+module.exports = THHasDataCells;
 
-const startTime=speedline.first;
-
-const latencyPercentiles=TracingProcessor.getRiskToResponsiveness(model,trace,startTime);
-const ninetieth=latencyPercentiles.find(result=>result.percentile===0.9);
-const rawValue=parseFloat(ninetieth.time.toFixed(1));
-
-
-
-
-
-
-
-const distribution=TracingProcessor.getLogNormalDistribution(SCORING_MEDIAN,
-SCORING_POINT_OF_DIMINISHING_RETURNS);
-const score=100*distribution.computeComplementaryPercentile(ninetieth.time);
-
-return EstimatedInputLatency.generateAuditResult({
-score:Math.round(score),
-optimalValue:this.meta.optimalValue,
-rawValue,
-displayValue:`${rawValue}ms`,
-extendedInfo:{
-value:latencyPercentiles,
-formatter:Formatter.SUPPORTED_FORMATS.NULL}});
-
-
-}
-
-
-
-
-
-
-
-static audit(artifacts){
-const trace=artifacts.traces[this.DEFAULT_PASS];
-
-const pending=[
-artifacts.requestSpeedline(trace),
-artifacts.requestTracingModel(trace)];
-
-return Promise.all(pending).then(([speedline,model])=>
-EstimatedInputLatency.calculate(speedline,model,trace));
-}}
-
-
-module.exports=EstimatedInputLatency;
-
-},{"../lib/traces/tracing-processor":24,"../report/formatter":27,"./audit":3}],"../audits/first-meaningful-paint":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/valid-lang":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
-const TracingProcessor=require('../lib/traces/tracing-processor');
-const Formatter=require('../report/formatter');
+/**
+ * @fileoverview Ensures lang attributes have valid values.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
+const AxeAudit = require('./axe-audit');
 
-
-const SCORING_POINT_OF_DIMINISHING_RETURNS=1600;
-const SCORING_MEDIAN=4000;
-
-
-class FirstMeaningfulPaint extends Audit{
-
-
-
-static get meta(){
-return{
-category:'Performance',
-name:'first-meaningful-paint',
-description:'First meaningful paint',
-optimalValue:SCORING_POINT_OF_DIMINISHING_RETURNS.toLocaleString()+'ms',
-helpText:'First meaningful paint measures when the primary content of a page is visible. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/first-meaningful-paint).',
-requiredArtifacts:['traces']};
-
+class ValidLang extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'valid-lang',
+      description: '`[lang]` attributes have a valid value.',
+      helpText: 'Specifying a valid [BCP 47 language](https://www.w3.org/International/questions/qa-choosing-language-tags#question) ' +
+          'on elements helps ensure that text is pronounced correctly by a screen reader.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = ValidLang;
 
-
-
-
-
-
-
-static audit(artifacts){
-const trace=artifacts.traces[this.DEFAULT_PASS];
-return artifacts.requestTraceOfTab(trace).then(tabTrace=>{
-if(!tabTrace.firstMeaningfulPaintEvt){
-throw new Error('No usable `firstMeaningfulPaint(Candidate)` events found in trace');
-}
-
-
-
-if(!tabTrace.navigationStartEvt){
-throw new Error('No `navigationStart` event found in trace');
-}
-
-const result=this.calculateScore({
-navigationStart:tabTrace.navigationStartEvt,
-firstMeaningfulPaint:tabTrace.firstMeaningfulPaintEvt,
-firstContentfulPaint:tabTrace.firstContentfulPaintEvt});
-
-
-return FirstMeaningfulPaint.generateAuditResult({
-score:result.score,
-rawValue:parseFloat(result.duration),
-displayValue:`${result.duration}ms`,
-debugString:result.debugString,
-optimalValue:this.meta.optimalValue,
-extendedInfo:{
-value:result.extendedInfo,
-formatter:Formatter.SUPPORTED_FORMATS.NULL}});
-
-
-});
-}
-
-static calculateScore(evts){
-const getTs=evt=>evt&&evt.ts;
-const getTiming=evt=>{
-if(!evt){
-return undefined;
-}
-const timing=(evt.ts-evts.navigationStart.ts)/1000;
-return parseFloat(timing.toFixed(3));
-};
-
-
-const extendedInfo={
-timestamps:{
-navStart:getTs(evts.navigationStart),
-fCP:getTs(evts.firstContentfulPaint),
-fMP:getTs(evts.firstMeaningfulPaint)},
-
-timings:{
-navStart:0,
-fCP:getTiming(evts.firstContentfulPaint),
-fMP:getTiming(evts.firstMeaningfulPaint)}};
-
-
-
-
-
-
-
-const firstMeaningfulPaint=getTiming(evts.firstMeaningfulPaint);
-const distribution=TracingProcessor.getLogNormalDistribution(SCORING_MEDIAN,
-SCORING_POINT_OF_DIMINISHING_RETURNS);
-let score=100*distribution.computeComplementaryPercentile(firstMeaningfulPaint);
-
-
-score=Math.min(100,score);
-score=Math.max(0,score);
-
-return{
-duration:firstMeaningfulPaint.toFixed(1),
-score:Math.round(score),
-rawValue:firstMeaningfulPaint,
-extendedInfo};
-
-}}
-
-
-module.exports=FirstMeaningfulPaint;
-
-},{"../lib/traces/tracing-processor":24,"../report/formatter":27,"./audit":3}],"../audits/is-on-https":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/video-caption":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
+/**
+ * @fileoverview Ensures `<video>` elements have closed captions.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class HTTPS extends Audit{
+const AxeAudit = require('./axe-audit');
 
-
-
-static get meta(){
-return{
-category:'Security',
-name:'is-on-https',
-description:'Uses HTTPS',
-helpText:'All sites should be protected with HTTPS, even ones that don\'t handle '+
-'sensitive data. HTTPS prevents intruders from tampering with or passively listening '+
-'in on the communications between your app and your users, and is a prerequisite for '+
-'HTTP/2 and many new web platform APIs. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/https).',
-requiredArtifacts:['HTTPS']};
-
+class VideoCaption extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'video-caption',
+      description: '`<video>` elements contain a `<track>` element with `[kind="captions"]`.',
+      helpText: 'When a video provides a caption it is easier for deaf and hearing impaired ' +
+          'users to access its information.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = VideoCaption;
 
-
-
-
-static audit(artifacts){
-return HTTPS.generateAuditResult({
-rawValue:artifacts.HTTPS.value,
-debugString:artifacts.HTTPS.debugString});
-
-}}
-
-
-module.exports=HTTPS;
-
-},{"./audit":3}],"../audits/manifest-background-color":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./axe-audit":1}],"../audits/accessibility/video-description":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
-const Formatter=require('../report/formatter');
+/**
+ * @fileoverview Ensures `<video>` elements have audio descriptions.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class ManifestBackgroundColor extends Audit{
+const AxeAudit = require('./axe-audit');
 
-
-
-static get meta(){
-return{
-category:'Manifest',
-name:'manifest-background-color',
-description:'Manifest contains `background_color`',
-helpText:'When your app launches from a user\'s homescreen, the browser '+
-'uses `background_color` to paint the background of the browser '+
-'while your app loads for a smooth transition experience. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/manifest-contains-background_color).',
-requiredArtifacts:['Manifest']};
-
+class VideoDescription extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'video-description',
+      description: '`<video>` elements contain a `<track>` element with `[kind="description"]`.',
+      helpText: 'Audio descriptions provide relevant information for videos that dialogue ' +
+          'cannot, such as facial expressions and scenes.',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
 }
 
+module.exports = VideoDescription;
 
+},{"./axe-audit":1}],"../audits/byte-efficiency/offscreen-images":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ /**
+  * @fileoverview Checks to see if images are displayed only outside of the viewport.
+  *     Images requested after TTI are not flagged as violations.
+  */
+'use strict';
 
+const Audit = require('./byte-efficiency-audit');
+const TTIAudit = require('../time-to-interactive');
+const URL = require('../../lib/url-shim');
 
+const ALLOWABLE_OFFSCREEN_X = 100;
+const ALLOWABLE_OFFSCREEN_Y = 200;
 
-static audit(artifacts){
-if(!artifacts.Manifest||!artifacts.Manifest.value){
+const IGNORE_THRESHOLD_IN_BYTES = 2048;
+const IGNORE_THRESHOLD_IN_PERCENT = 75;
 
-return ManifestBackgroundColor.generateAuditResult({
-rawValue:false});
+class OffscreenImages extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Images',
+      name: 'offscreen-images',
+      description: 'Offscreen images',
+      informative: true,
+      helpText: 'Images that are not above the fold should be lazily loaded after the page is ' +
+        'interactive. Consider using the [IntersectionObserver](https://developers.google.com/web/updates/2016/04/intersectionobserver) API.',
+      requiredArtifacts: ['ImageUsage', 'ViewportDimensions', 'traces', 'networkRecords']
+    };
+  }
 
+  /**
+   * @param {!ClientRect} imageRect
+   * @param {{innerWidth: number, innerHeight: number}} viewportDimensions
+   * @return {number}
+   */
+  static computeVisiblePixels(imageRect, viewportDimensions) {
+    const innerWidth = viewportDimensions.innerWidth;
+    const innerHeight = viewportDimensions.innerHeight;
+
+    const top = Math.max(imageRect.top, -1 * ALLOWABLE_OFFSCREEN_Y);
+    const right = Math.min(imageRect.right, innerWidth + ALLOWABLE_OFFSCREEN_X);
+    const bottom = Math.min(imageRect.bottom, innerHeight + ALLOWABLE_OFFSCREEN_Y);
+    const left = Math.max(imageRect.left, -1 * ALLOWABLE_OFFSCREEN_X);
+
+    return Math.max(right - left, 0) * Math.max(bottom - top, 0);
+  }
+
+  /**
+   * @param {!Object} image
+   * @param {{innerWidth: number, innerHeight: number}} viewportDimensions
+   * @return {?Object}
+   */
+  static computeWaste(image, viewportDimensions) {
+    const url = URL.getDisplayName(image.src, {preserveQuery: true});
+    const totalPixels = image.clientWidth * image.clientHeight;
+    const visiblePixels = this.computeVisiblePixels(image.clientRect, viewportDimensions);
+    // Treat images with 0 area as if they're offscreen. See https://github.com/GoogleChrome/lighthouse/issues/1914
+    const wastedRatio = totalPixels === 0 ? 1 : 1 - visiblePixels / totalPixels;
+    const totalBytes = image.networkRecord.resourceSize;
+    const wastedBytes = Math.round(totalBytes * wastedRatio);
+
+    if (!Number.isFinite(wastedRatio)) {
+      return new Error(`Invalid image sizing information ${url}`);
+    }
+
+    return {
+      url,
+      preview: {
+        url: image.networkRecord.url,
+        mimeType: image.networkRecord.mimeType
+      },
+      requestStartTime: image.networkRecord.startTime,
+      totalBytes,
+      wastedBytes,
+      wastedPercent: 100 * wastedRatio,
+    };
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {{results: !Array<Object>, tableHeadings: Object,
+   *     passes: boolean=, debugString: string=}}
+   */
+  static audit_(artifacts) {
+    const images = artifacts.ImageUsage;
+    const viewportDimensions = artifacts.ViewportDimensions;
+
+    let debugString;
+    const resultsMap = images.reduce((results, image) => {
+      if (!image.networkRecord) {
+        return results;
+      }
+
+      const processed = OffscreenImages.computeWaste(image, viewportDimensions);
+      if (processed instanceof Error) {
+        debugString = processed.message;
+        return results;
+      }
+
+      // If an image was used more than once, warn only about its least wasteful usage
+      const existing = results.get(processed.preview.url);
+      if (!existing || existing.wastedBytes > processed.wastedBytes) {
+        results.set(processed.preview.url, processed);
+      }
+
+      return results;
+    }, new Map());
+
+    return TTIAudit.audit(artifacts).then(ttiResult => {
+      const ttiTimestamp = ttiResult.extendedInfo.value.timestamps.timeToInteractive / 1000000;
+      const results = Array.from(resultsMap.values()).filter(item => {
+        const isWasteful = item.wastedBytes > IGNORE_THRESHOLD_IN_BYTES &&
+            item.wastedPercent > IGNORE_THRESHOLD_IN_PERCENT;
+        const loadedEarly = item.requestStartTime < ttiTimestamp;
+        return isWasteful && loadedEarly;
+      });
+      return {
+        debugString,
+        results,
+        tableHeadings: {
+          preview: '',
+          url: 'URL',
+          totalKb: 'Original',
+          potentialSavings: 'Potential Savings',
+        }
+      };
+    });
+  }
 }
 
-const manifest=artifacts.Manifest.value;
-const bgColor=manifest.background_color.value;
-return ManifestBackgroundColor.generateAuditResult({
-rawValue:!!bgColor,
-extendedInfo:{
-value:bgColor,
-formatter:Formatter.SUPPORTED_FORMATS.NULL}});
+module.exports = OffscreenImages;
+
+},{"../../lib/url-shim":25,"../time-to-interactive":"../audits/time-to-interactive","./byte-efficiency-audit":3}],"../audits/byte-efficiency/total-byte-weight":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ /**
+  * @fileoverview
+  */
+'use strict';
+
+const Audit = require('./byte-efficiency-audit');
+const Formatter = require('../../report/formatter');
+const TracingProcessor = require('../../lib/traces/tracing-processor');
+const URL = require('../../lib/url-shim');
+
+// Parameters for log-normal CDF scoring. See https://www.desmos.com/calculator/gpmjeykbwr
+// ~75th and ~90th percentiles http://httparchive.org/interesting.php?a=All&l=Feb%201%202017&s=All#bytesTotal
+const OPTIMAL_VALUE = 1600 * 1024;
+const SCORING_POINT_OF_DIMINISHING_RETURNS = 2500 * 1024;
+const SCORING_MEDIAN = 4000 * 1024;
+
+class TotalByteWeight extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Network',
+      name: 'total-byte-weight',
+      optimalValue: this.bytesToKbString(OPTIMAL_VALUE),
+      description: 'Avoids enormous network payloads',
+      informative: true,
+      helpText:
+          'Network transfer size [costs users real dollars](https://whatdoesmysitecost.com/) ' +
+          'and is [highly correlated](http://httparchive.org/interesting.php#onLoad) with long load times. ' +
+          'Try to find ways to reduce the size of required files.',
+      scoringMode: Audit.SCORING_MODES.NUMERIC,
+      requiredArtifacts: ['networkRecords']
+    };
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!Promise<!AuditResult>}
+   */
+  static audit(artifacts) {
+    const networkRecords = artifacts.networkRecords[Audit.DEFAULT_PASS];
+    return artifacts.requestNetworkThroughput(networkRecords).then(networkThroughput => {
+      let totalBytes = 0;
+      const results = networkRecords.reduce((prev, record) => {
+        // exclude data URIs since their size is reflected in other resources
+        if (record.scheme === 'data') {
+          return prev;
+        }
+
+        const result = {
+          url: URL.getDisplayName(record.url),
+          totalBytes: record.transferSize,
+          totalKb: this.bytesToKbString(record.transferSize),
+          totalMs: this.bytesToMsString(record.transferSize, networkThroughput),
+        };
+
+        totalBytes += result.totalBytes;
+        prev.push(result);
+        return prev;
+      }, []).sort((itemA, itemB) => itemB.totalBytes - itemA.totalBytes).slice(0, 10);
 
 
-}}
+      // Use the CDF of a log-normal distribution for scoring.
+      //   <= 1600KB: score≈100
+      //   4000KB: score=50
+      //   >= 9000KB: score≈0
+      const distribution = TracingProcessor.getLogNormalDistribution(
+          SCORING_MEDIAN, SCORING_POINT_OF_DIMINISHING_RETURNS);
+      const score = 100 * distribution.computeComplementaryPercentile(totalBytes);
 
+      return {
+        rawValue: totalBytes,
+        optimalValue: this.meta.optimalValue,
+        displayValue: `Total size was ${Audit.bytesToKbString(totalBytes)}`,
+        score: Math.round(Math.max(0, Math.min(score, 100))),
+        extendedInfo: {
+          formatter: Formatter.SUPPORTED_FORMATS.TABLE,
+          value: {
+            results,
+            tableHeadings: {
+              url: 'URL',
+              totalKb: 'Total Size',
+              totalMs: 'Transfer Time',
+            }
+          }
+        }
+      };
+    });
+  }
+}
 
-module.exports=ManifestBackgroundColor;
+module.exports = TotalByteWeight;
 
-},{"../report/formatter":27,"./audit":3}],"../audits/manifest-display":[function(require,module,exports){
+},{"../../lib/traces/tracing-processor":24,"../../lib/url-shim":25,"../../report/formatter":27,"./byte-efficiency-audit":3}],"../audits/byte-efficiency/unused-css-rules":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
 
+const Audit = require('./byte-efficiency-audit');
+const URL = require('../../lib/url-shim');
 
+const PREVIEW_LENGTH = 100;
 
+class UnusedCSSRules extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'CSS',
+      name: 'unused-css-rules',
+      description: 'Unused CSS rules',
+      informative: true,
+      helpText: 'Remove unused rules from stylesheets to reduce unnecessary ' +
+          'bytes consumed by network activity. ' +
+          '[Learn more](https://developers.google.com/speed/docs/insights/OptimizeCSSDelivery)',
+      requiredArtifacts: ['CSSUsage', 'Styles', 'URL', 'networkRecords']
+    };
+  }
 
+  /**
+   * @param {!Array.<{header: {styleSheetId: string}}>} styles The output of the Styles gatherer.
+   * @param {!Array<WebInspector.NetworkRequest>} networkRecords
+   * @return {!Object} A map of styleSheetId to stylesheet information.
+   */
+  static indexStylesheetsById(styles, networkRecords) {
+    const indexedNetworkRecords = networkRecords
+        .filter(record => record._resourceType && record._resourceType._name === 'stylesheet')
+        .reduce((indexed, record) => {
+          indexed[record.url] = record;
+          return indexed;
+        }, {});
+    return styles.reduce((indexed, stylesheet) => {
+      indexed[stylesheet.header.styleSheetId] = Object.assign({
+        used: [],
+        unused: [],
+        networkRecord: indexedNetworkRecords[stylesheet.header.sourceURL],
+      }, stylesheet);
+      return indexed;
+    }, {});
+  }
 
+  /**
+   * Counts the number of unused rules and adds count information to sheets.
+   * @param {!Array.<{styleSheetId: string, used: boolean}>} rules The output of the CSSUsage gatherer.
+   * @param {!Object} indexedStylesheets Stylesheet information indexed by id.
+   * @return {number} The number of unused rules.
+   */
+  static countUnusedRules(rules, indexedStylesheets) {
+    let unused = 0;
 
+    rules.forEach(rule => {
+      const stylesheetInfo = indexedStylesheets[rule.styleSheetId];
 
+      if (!stylesheetInfo || stylesheetInfo.isDuplicate) {
+        return;
+      }
 
+      if (rule.used) {
+        stylesheetInfo.used.push(rule);
+      } else {
+        unused++;
+        stylesheetInfo.unused.push(rule);
+      }
+    });
 
+    return unused;
+  }
 
+  /**
+   * Trims stylesheet content down to the first rule-set definition.
+   * @param {string} content
+   * @return {string}
+   */
+  static determineContentPreview(content) {
+    let preview = content
+        .slice(0, PREVIEW_LENGTH * 5)
+        .replace(/( {2,}|\t)+/g, '  ') // remove leading indentation if present
+        .replace(/\n\s+}/g, '\n}') // completely remove indentation of closing braces
+        .trim(); // trim the leading whitespace
 
+    if (preview.length > PREVIEW_LENGTH) {
+      const firstRuleStart = preview.indexOf('{');
+      const firstRuleEnd = preview.indexOf('}');
 
+      if (firstRuleStart === -1 || firstRuleEnd === -1
+          || firstRuleStart > firstRuleEnd
+          || firstRuleStart > PREVIEW_LENGTH) {
+        // We couldn't determine the first rule-set or it's not within the preview
+        preview = preview.slice(0, PREVIEW_LENGTH) + '...';
+      } else if (firstRuleEnd < PREVIEW_LENGTH) {
+        // The entire first rule-set fits within the preview
+        preview = preview.slice(0, firstRuleEnd + 1) + ' ...';
+      } else {
+        // The first rule-set doesn't fit within the preview, just show as many as we can
+        const lastSemicolonIndex = preview.slice(0, PREVIEW_LENGTH).lastIndexOf(';');
+        preview = lastSemicolonIndex < firstRuleStart ?
+            preview.slice(0, PREVIEW_LENGTH) + '... } ...' :
+            preview.slice(0, lastSemicolonIndex + 1) + ' ... } ...';
+      }
+    }
 
+    return preview;
+  }
 
+  /**
+   * @param {!Object} stylesheetInfo The stylesheetInfo object.
+   * @param {string} pageUrl The URL of the page, used to identify inline styles.
+   * @return {!{url: string, label: string, code: string}} The result for the URLLIST formatter.
+   */
+  static mapSheetToResult(stylesheetInfo, pageUrl) {
+    const numUsed = stylesheetInfo.used.length;
+    const numUnused = stylesheetInfo.unused.length;
 
+    if ((numUsed === 0 && numUnused === 0) || stylesheetInfo.isDuplicate) {
+      return null;
+    }
 
+    let url = stylesheetInfo.header.sourceURL;
+    if (!url || url === pageUrl) {
+      const contentPreview = UnusedCSSRules.determineContentPreview(stylesheetInfo.content);
+      url = '*inline*```' + contentPreview + '```';
+    } else {
+      url = URL.getDisplayName(url);
+    }
+
+    // If we don't know for sure how many bytes this sheet used on the network,
+    // we can guess it was roughly the size of the content gzipped.
+    const totalBytes = stylesheetInfo.networkRecord ?
+        stylesheetInfo.networkRecord.transferSize :
+        Math.round(stylesheetInfo.content.length / 3);
+
+    const percentUnused = numUnused / (numUsed + numUnused);
+    const wastedBytes = Math.round(percentUnused * totalBytes);
+
+    return {
+      url,
+      numUnused,
+      wastedBytes,
+      wastedPercent: percentUnused * 100,
+      totalBytes,
+    };
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {{results: !Array<Object>, tableHeadings: Object,
+   *     passes: boolean=, debugString: string=}}
+   */
+  static audit_(artifacts) {
+    const styles = artifacts.Styles;
+    const usage = artifacts.CSSUsage;
+    const pageUrl = artifacts.URL.finalUrl;
+    const networkRecords = artifacts.networkRecords[Audit.DEFAULT_PASS];
+
+    const indexedSheets = UnusedCSSRules.indexStylesheetsById(styles, networkRecords);
+    UnusedCSSRules.countUnusedRules(usage, indexedSheets);
+    const results = Object.keys(indexedSheets).map(sheetId => {
+      return UnusedCSSRules.mapSheetToResult(indexedSheets[sheetId], pageUrl);
+    }).filter(sheet => sheet && sheet.wastedBytes > 1024);
+
+    return {
+      results,
+      tableHeadings: {
+        url: 'URL',
+        numUnused: 'Unused Rules',
+        totalKb: 'Original',
+        potentialSavings: 'Potential Savings',
+      }
+    };
+  }
+}
+
+module.exports = UnusedCSSRules;
+
+},{"../../lib/url-shim":25,"./byte-efficiency-audit":3}],"../audits/byte-efficiency/uses-optimized-images":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * @fileoverview This audit determines if the images used are sufficiently larger
+ * than Lighthouse optimized versions of the images (as determined by the gatherer).
+ * Audit will fail if one of the conditions are met:
+ *   * There is at least one JPEG or bitmap image that was >10KB larger than canvas encoded JPEG.
+ *   * There is at least one image that would have saved more than 100KB by using WebP.
+ *   * The savings of moving all images to WebP is greater than 1MB.
+ */
+'use strict';
+
+const Audit = require('./byte-efficiency-audit');
+const URL = require('../../lib/url-shim');
+
+const IGNORE_THRESHOLD_IN_BYTES = 2048;
+const TOTAL_WASTED_BYTES_THRESHOLD = 1000 * 1024;
+const JPEG_ALREADY_OPTIMIZED_THRESHOLD_IN_BYTES = 25 * 1024;
+const WEBP_ALREADY_OPTIMIZED_THRESHOLD_IN_BYTES = 100 * 1024;
+
+class UsesOptimizedImages extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Images',
+      name: 'uses-optimized-images',
+      description: 'Unoptimized images',
+      informative: true,
+      helpText: 'Images should be optimized to save network bytes. ' +
+        'The following images could have smaller file sizes when compressed with ' +
+        '[WebP](https://developers.google.com/speed/webp/) or JPEG at 80 quality. ' +
+        '[Learn more about image optimization](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/image-optimization).',
+      requiredArtifacts: ['OptimizedImages', 'networkRecords']
+    };
+  }
+
+  /**
+   * @param {{originalSize: number, webpSize: number, jpegSize: number}} image
+   * @param {string} type
+   * @return {{bytes: number, percent: number}}
+   */
+  static computeSavings(image, type) {
+    const bytes = image.originalSize - image[type + 'Size'];
+    const percent = 100 * bytes / image.originalSize;
+    return {bytes, percent};
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {{results: !Array<Object>, tableHeadings: Object,
+   *     passes: boolean=, debugString: string=}}
+   */
+  static audit_(artifacts) {
+    const images = artifacts.OptimizedImages;
+
+    const failedImages = [];
+    let totalWastedBytes = 0;
+    let hasAllEfficientImages = true;
+
+    const results = images.reduce((results, image) => {
+      if (image.failed) {
+        failedImages.push(image);
+        return results;
+      } else if (image.originalSize < Math.max(IGNORE_THRESHOLD_IN_BYTES, image.webpSize)) {
+        return results;
+      }
+
+      const url = URL.getDisplayName(image.url);
+      const webpSavings = UsesOptimizedImages.computeSavings(image, 'webp');
+
+      if (webpSavings.bytes > WEBP_ALREADY_OPTIMIZED_THRESHOLD_IN_BYTES) {
+        hasAllEfficientImages = false;
+      } else if (webpSavings.bytes < IGNORE_THRESHOLD_IN_BYTES) {
+        return results;
+      }
+
+      let jpegSavingsLabel;
+      if (/(jpeg|bmp)/.test(image.mimeType)) {
+        const jpegSavings = UsesOptimizedImages.computeSavings(image, 'jpeg');
+        if (jpegSavings.bytes > JPEG_ALREADY_OPTIMIZED_THRESHOLD_IN_BYTES) {
+          hasAllEfficientImages = false;
+        }
+        if (jpegSavings.bytes > IGNORE_THRESHOLD_IN_BYTES) {
+          jpegSavingsLabel = this.toSavingsString(jpegSavings.bytes, jpegSavings.percent);
+        }
+      }
+
+      totalWastedBytes += webpSavings.bytes;
+
+      results.push({
+        url,
+        isCrossOrigin: !image.isSameOrigin,
+        preview: {url: image.url, mimeType: image.mimeType},
+        totalBytes: image.originalSize,
+        wastedBytes: webpSavings.bytes,
+        webpSavings: this.toSavingsString(webpSavings.bytes, webpSavings.percent),
+        jpegSavings: jpegSavingsLabel
+      });
+      return results;
+    }, []);
+
+    let debugString;
+    if (failedImages.length) {
+      const urls = failedImages.map(image => URL.getDisplayName(image.url));
+      debugString = `Lighthouse was unable to decode some of your images: ${urls.join(', ')}`;
+    }
+
+    return {
+      passes: hasAllEfficientImages && totalWastedBytes < TOTAL_WASTED_BYTES_THRESHOLD,
+      debugString,
+      results,
+      tableHeadings: {
+        preview: '',
+        url: 'URL',
+        totalKb: 'Original',
+        webpSavings: 'WebP Savings',
+        jpegSavings: 'JPEG Savings',
+      }
+    };
+  }
+}
+
+module.exports = UsesOptimizedImages;
+
+},{"../../lib/url-shim":25,"./byte-efficiency-audit":3}],"../audits/byte-efficiency/uses-request-compression":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * @fileoverview Audit a page to ensure that resources loaded with
+ * gzip/br/deflate compression.
+ */
+'use strict';
+
+const Audit = require('./byte-efficiency-audit');
+const URL = require('../../lib/url-shim');
+
+const IGNORE_THRESHOLD_IN_BYTES = 1400;
+const IGNORE_THRESHOLD_IN_PERCENT = 0.1;
+const TOTAL_WASTED_BYTES_THRESHOLD = 10 * 1024; // 10KB
+
+class ResponsesAreCompressed extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Performance',
+      name: 'uses-request-compression',
+      description: 'Compression enabled for server responses',
+      helpText: 'Text-based responses should be served with compression (gzip, deflate or brotli)' +
+        ' to minimize total network bytes.' +
+        ' [Learn more](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer).',
+      requiredArtifacts: ['ResponseCompression', 'networkRecords']
+    };
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @param {number} networkThroughput
+   * @return {!AuditResult}
+   */
+  static audit_(artifacts) {
+    const uncompressedResponses = artifacts.ResponseCompression;
+
+    let totalWastedBytes = 0;
+    const results = [];
+    uncompressedResponses.forEach(record => {
+      const originalSize = record.resourceSize;
+      const gzipSize = record.gzipSize;
+      const gzipSavings = originalSize - gzipSize;
+
+      // we require at least 10% savings off the original size AND at least 1400 bytes
+      // if the savings is smaller than either, we don't care
+      if (
+          1 - gzipSize / originalSize < IGNORE_THRESHOLD_IN_PERCENT ||
+          gzipSavings < IGNORE_THRESHOLD_IN_BYTES
+      ) {
+        return;
+      }
+
+      // remove duplicates
+      const url = URL.getDisplayName(record.url);
+      const isDuplicate = results.find(res => res.url === url &&
+        res.totalBytes === record.resourceSize);
+      if (isDuplicate) {
+        return;
+      }
+
+      totalWastedBytes += gzipSavings;
+      const totalBytes = originalSize;
+      const gzipSavingsBytes = gzipSavings;
+      const gzipSavingsPercent = 100 * gzipSavingsBytes / totalBytes;
+      results.push({
+        url,
+        totalBytes,
+        wastedBytes: gzipSavingsBytes,
+        wastedPercent: gzipSavingsPercent,
+        potentialSavings: this.toSavingsString(gzipSavingsBytes, gzipSavingsPercent),
+      });
+    });
+
+    let debugString;
+    return {
+      passes: totalWastedBytes < TOTAL_WASTED_BYTES_THRESHOLD,
+      debugString,
+      results,
+      tableHeadings: {
+        url: 'Uncompressed resource URL',
+        totalKb: 'Original',
+        potentialSavings: 'GZIP Savings',
+      }
+    };
+  }
+}
+
+module.exports = ResponsesAreCompressed;
+
+},{"../../lib/url-shim":25,"./byte-efficiency-audit":3}],"../audits/byte-efficiency/uses-responsive-images":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ /**
+  * @fileoverview Checks to see if the images used on the page are larger than
+  *   their display sizes. The audit will list all images that are larger than
+  *   their display size with DPR (a 1000px wide image displayed as a
+  *   500px high-res image on a Retina display is 100% used);
+  *   However, the audit will only fail pages that use images that have waste
+  *   beyond a particular byte threshold.
+  */
+'use strict';
+
+const Audit = require('./byte-efficiency-audit');
+const URL = require('../../lib/url-shim');
+
+const IGNORE_THRESHOLD_IN_BYTES = 2048;
+const WASTEFUL_THRESHOLD_IN_BYTES = 25 * 1024;
+
+class UsesResponsiveImages extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Images',
+      name: 'uses-responsive-images',
+      description: 'Oversized images',
+      informative: true,
+      helpText:
+        'Image sizes served should be based on the device display size to save network bytes. ' +
+        'Learn more about [responsive images](https://developers.google.com/web/fundamentals/design-and-ui/media/images) ' +
+        'and [client hints](https://developers.google.com/web/updates/2015/09/automating-resource-selection-with-client-hints).',
+      requiredArtifacts: ['ImageUsage', 'ViewportDimensions', 'networkRecords']
+    };
+  }
+
+  /**
+   * @param {!Object} image
+   * @param {number} DPR devicePixelRatio
+   * @return {?Object}
+   */
+  static computeWaste(image, DPR) {
+    const url = URL.getDisplayName(image.src);
+    const actualPixels = image.naturalWidth * image.naturalHeight;
+    const usedPixels = image.clientWidth * image.clientHeight * Math.pow(DPR, 2);
+    const wastedRatio = 1 - (usedPixels / actualPixels);
+    const totalBytes = image.networkRecord.resourceSize;
+    const wastedBytes = Math.round(totalBytes * wastedRatio);
+
+    if (!Number.isFinite(wastedRatio)) {
+      return new Error(`Invalid image sizing information ${url}`);
+    }
+
+    return {
+      url,
+      preview: {
+        url: image.networkRecord.url,
+        mimeType: image.networkRecord.mimeType
+      },
+      totalBytes,
+      wastedBytes,
+      wastedPercent: 100 * wastedRatio,
+      isWasteful: wastedBytes > WASTEFUL_THRESHOLD_IN_BYTES,
+    };
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {{results: !Array<Object>, tableHeadings: Object,
+   *     passes: boolean=, debugString: string=}}
+   */
+  static audit_(artifacts) {
+    const images = artifacts.ImageUsage;
+    const DPR = artifacts.ViewportDimensions.devicePixelRatio;
+
+    let debugString;
+    const resultsMap = images.reduce((results, image) => {
+      // TODO: give SVG a free pass until a detail per pixel metric is available
+      if (!image.networkRecord || image.networkRecord.mimeType === 'image/svg+xml') {
+        return results;
+      }
+
+      const processed = UsesResponsiveImages.computeWaste(image, DPR);
+      if (processed instanceof Error) {
+        debugString = processed.message;
+        return results;
+      }
+
+      // Don't warn about an image that was later used appropriately
+      const existing = results.get(processed.preview.url);
+      if (!existing || existing.wastedBytes > processed.wastedBytes) {
+        results.set(processed.preview.url, processed);
+      }
+
+      return results;
+    }, new Map());
+
+    const results = Array.from(resultsMap.values())
+        .filter(item => item.wastedBytes > IGNORE_THRESHOLD_IN_BYTES);
+    return {
+      debugString,
+      passes: !results.find(item => item.isWasteful),
+      results,
+      tableHeadings: {
+        preview: '',
+        url: 'URL',
+        totalKb: 'Original',
+        potentialSavings: 'Potential Savings',
+      }
+    };
+  }
+}
+
+module.exports = UsesResponsiveImages;
+
+},{"../../lib/url-shim":25,"./byte-efficiency-audit":3}],"../audits/cache-start-url":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
+const Audit = require('./audit');
 
-class ManifestDisplay extends Audit{
+class CacheStartUrl extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Manifest',
+      name: 'cache-start-url',
+      description: 'Cache contains start_url from manifest (alpha)',
+      requiredArtifacts: ['CacheContents', 'Manifest', 'URL']
+    };
+  }
 
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    if (!artifacts.Manifest || !artifacts.Manifest.value) {
+      // Page has no manifest or was invalid JSON.
+      return {
+        rawValue: false,
+      };
+    }
 
+    const manifest = artifacts.Manifest.value;
+    if (!(manifest.start_url && manifest.start_url.value)) {
+      return {
+        rawValue: false,
+        debugString: 'start_url not present in Manifest'
+      };
+    }
 
-static get meta(){
-return{
-category:'Manifest',
-name:'manifest-display',
-description:'Manifest\'s `display` property is set',
-helpText:'Set the `display` property to specify how your app '+
-'launches from the homescreen. [Learn '+
-'more](https://developers.google.com/web/tools/lighthouse/audits/manifest-has-display-set).',
-requiredArtifacts:['Manifest']};
+    // Remove any UTM strings.
+    const startURL = manifest.start_url.value;
+    /** @const {string} */
+    const altStartURL = startURL
+        .replace(/\?utm_([^=]*)=([^&]|$)*/, '')
+        .replace(/\?$/, '');
 
+    // Now find the start_url in the cacheContents. This test is less than ideal since the Service
+    // Worker can rewrite a request from the start URL to anything else in the cache, and so a TODO
+    // here would be to resolve this more completely by asking the Service Worker about the start
+    // URL. However that would also necessitate the cache contents gatherer relying on the manifest
+    // gather rather than being independent of it.
+    const cacheContents = artifacts.CacheContents;
+    const cacheHasStartUrl = cacheContents.find(req => {
+      return (startURL === req || altStartURL === req);
+    });
+
+    return {
+      rawValue: (cacheHasStartUrl !== undefined)
+    };
+  }
 }
 
+module.exports = CacheStartUrl;
 
-
-
-
-static hasRecommendedValue(val){
-return['browser','fullscreen','minimal-ui','standalone'].includes(val);
-}
-
-
-
-
-
-static audit(artifacts){
-if(!artifacts.Manifest||!artifacts.Manifest.value){
-
-return ManifestDisplay.generateAuditResult({
-rawValue:false});
-
-}
-
-const manifest=artifacts.Manifest.value;
-const displayValue=manifest.display.value;
-const hasRecommendedValue=ManifestDisplay.hasRecommendedValue(displayValue);
-
-const auditResult={
-rawValue:hasRecommendedValue,
-displayValue};
-
-if(!hasRecommendedValue){
-auditResult.debugString='Manifest display property should be set.';
-}
-return ManifestDisplay.generateAuditResult(auditResult);
-}}
-
-
-module.exports=ManifestDisplay;
-
-},{"./audit":3}],"../audits/manifest-exists":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./audit":2}],"../audits/content-width":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
+const Audit = require('./audit');
 
-class ManifestExists extends Audit{
+class ContentWidth extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Mobile Friendly',
+      name: 'content-width',
+      description: 'Content is sized correctly for the viewport',
+      helpText: 'If the width of your app\'s content doesn\'t match the width ' +
+          'of the viewport, your app might not be optimized for mobile screens. ' +
+          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/content-sized-correctly-for-viewport).',
+      requiredArtifacts: ['ViewportDimensions']
+    };
+  }
 
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    const viewportWidth = artifacts.ViewportDimensions.innerWidth;
+    const windowWidth = artifacts.ViewportDimensions.outerWidth;
+    const widthsMatch = viewportWidth === windowWidth;
 
+    return {
+      rawValue: widthsMatch,
+      debugString: this.createDebugString(widthsMatch, artifacts.ViewportDimensions)
+    };
+  }
 
-static get meta(){
-return{
-category:'Manifest',
-name:'manifest-exists',
-description:'Manifest exists',
-helpText:'The web app manifest is the technology that enables users '+
-'to add your web app to their homescreen. [Learn '+
-'more](https://developers.google.com/web/tools/lighthouse/audits/manifest-exists).',
-requiredArtifacts:['Manifest']};
+  static createDebugString(match, artifact) {
+    if (match) {
+      return '';
+    }
 
+    return 'The viewport size is ' + artifact.innerWidth + 'px, ' +
+        'whereas the window size is ' + artifact.outerWidth + 'px.';
+  }
 }
 
+module.exports = ContentWidth;
 
-
-
-
-static audit(artifacts){
-if(!artifacts.Manifest){
-
-return ManifestExists.generateAuditResult({
-rawValue:false});
-
-}
-
-return ManifestExists.generateAuditResult({
-rawValue:typeof artifacts.Manifest.value!=='undefined',
-debugString:artifacts.Manifest.debugString});
-
-}}
-
-
-module.exports=ManifestExists;
-
-},{"./audit":3}],"../audits/manifest-icons-min-144":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"./audit":2}],"../audits/critical-request-chains":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
-const icons=require('../lib/icons');
+const Audit = require('./audit');
+const Formatter = require('../report/formatter');
 
-class ManifestIconsMin144 extends Audit{
+class CriticalRequestChains extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Performance',
+      name: 'critical-request-chains',
+      description: 'Critical Request Chains',
+      informative: true,
+      optimalValue: 0,
+      helpText: 'The Critical Request Chains below show you what resources are ' +
+          'required for first render of this page. Improve page load by reducing ' +
+          'the length of chains, reducing the download size of resources, or ' +
+          'deferring the download of unnecessary resources. ' +
+          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/critical-request-chains).',
+      requiredArtifacts: ['networkRecords']
+    };
+  }
 
+  static _traverse(tree, cb) {
+    function walk(node, depth, startTime, transferSize = 0) {
+      const children = Object.keys(node);
+      if (children.length === 0) {
+        return;
+      }
+      children.forEach(id => {
+        const child = node[id];
+        if (!startTime) {
+          startTime = child.request.startTime;
+        }
 
+        // Call the callback with the info for this child.
+        cb({
+          depth,
+          id,
+          node: child,
+          chainDuration: (child.request.endTime - startTime) * 1000,
+          chainTransferSize: (transferSize + child.request.transferSize)
+        });
 
-static get meta(){
-return{
-category:'Manifest',
-name:'manifest-icons-min-144',
-description:'Manifest contains icons at least 144px',
-requiredArtifacts:['Manifest']};
+        // Carry on walking.
+        walk(child.children, depth + 1, startTime);
+      }, '');
+    }
 
+    walk(tree, 0);
+  }
+
+  /**
+   * Get stats about the longest initiator chain (as determined by time duration)
+   * @return {{duration: number, length: number, transferSize: number}}
+   */
+  static _getLongestChain(tree) {
+    const longest = {
+      duration: 0,
+      length: 0,
+      transferSize: 0
+    };
+    CriticalRequestChains._traverse(tree, opts => {
+      const duration = opts.chainDuration;
+      if (duration > longest.duration) {
+        longest.duration = duration;
+        longest.transferSize = opts.chainTransferSize;
+        longest.length = opts.depth;
+      }
+    });
+    // Always return the longest chain + 1 because the depth is zero indexed.
+    longest.length++;
+    return longest;
+  }
+
+  /**
+   * Audits the page to give a score for First Meaningful Paint.
+   * @param {!Artifacts} artifacts The artifacts from the gather phase.
+   * @return {!AuditResult} The score from the audit, ranging from 0-100.
+   */
+  static audit(artifacts) {
+    const networkRecords = artifacts.networkRecords[Audit.DEFAULT_PASS];
+    return artifacts.requestCriticalRequestChains(networkRecords).then(chains => {
+      let chainCount = 0;
+      function walk(node, depth) {
+        const children = Object.keys(node);
+
+        // Since a leaf node indicates the end of a chain, we can inspect the number
+        // of child nodes, and, if the count is zero, increment the count.
+        if (children.length === 0) {
+          chainCount++;
+        }
+
+        children.forEach(id => {
+          const child = node[id];
+          walk(child.children, depth + 1);
+        }, '');
+      }
+
+      // Account for initial navigation
+      const initialNavKey = Object.keys(chains)[0];
+      const initialNavChildren = initialNavKey && chains[initialNavKey].children;
+      if (initialNavChildren && Object.keys(initialNavChildren).length > 0) {
+        walk(initialNavChildren, 0);
+      }
+
+      return {
+        rawValue: chainCount <= this.meta.optimalValue,
+        displayValue: chainCount,
+        optimalValue: this.meta.optimalValue,
+        extendedInfo: {
+          formatter: Formatter.SUPPORTED_FORMATS.CRITICAL_REQUEST_CHAINS,
+          value: {
+            chains,
+            longestChain: CriticalRequestChains._getLongestChain(chains)
+          }
+        }
+      };
+    });
+  }
 }
 
+module.exports = CriticalRequestChains;
 
+},{"../report/formatter":27,"./audit":2}],"../audits/deprecations":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
 
+/**
+ * @fileoverview Audits a page to determine if it is calling deprecated APIs.
+ * This is done by collecting console log messages and filtering them by ones
+ * that contain deprecated API warnings sent by Chrome.
+ */
 
+const Audit = require('./audit');
+const Formatter = require('../report/formatter');
 
-static audit(artifacts){
-if(!artifacts.Manifest||!artifacts.Manifest.value){
+class Deprecations extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Deprecations',
+      name: 'deprecations',
+      description: 'Avoids deprecated APIs',
+      helpText: 'Deprecated APIs will eventually be removed from the browser. ' +
+          '[Learn more](https://www.chromestatus.com/features#deprecated).',
+      requiredArtifacts: ['ChromeConsoleMessages']
+    };
+  }
 
-return ManifestIconsMin144.generateAuditResult({
-rawValue:false});
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    const entries = artifacts.ChromeConsoleMessages;
 
+    const deprecations = entries.filter(log => log.entry.source === 'deprecation')
+        .map(log => {
+          // CSS deprecations can have missing URLs and lineNumbers. See https://crbug.com/680832.
+          const label = log.entry.lineNumber ? `line: ${log.entry.lineNumber}` : 'line: ???';
+          const url = log.entry.url || 'Unable to determine URL';
+          return Object.assign({
+            label,
+            url,
+            code: log.entry.text
+          }, log.entry);
+        });
+
+    let displayValue = '';
+    if (deprecations.length > 1) {
+      displayValue = `${deprecations.length} warnings found`;
+    } else if (deprecations.length === 1) {
+      displayValue = `${deprecations.length} warning found`;
+    }
+
+    return {
+      rawValue: deprecations.length === 0,
+      displayValue,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.URL_LIST,
+        value: deprecations
+      }
+    };
+  }
 }
 
-const manifest=artifacts.Manifest.value;
-if(icons.doExist(manifest)===false){
-return ManifestIconsMin144.generateAuditResult({
-rawValue:false});
+module.exports = Deprecations;
 
-}
+},{"../report/formatter":27,"./audit":2}],"../audits/dobetterweb/appcache-manifest":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-const matchingIcons=icons.sizeAtLeast(144,manifest);
-
-let displayValue;
-let debugString;
-if(matchingIcons.length){
-displayValue=`found sizes: ${matchingIcons.join(', ')}`;
-}else{
-debugString='No icons are at least 144px';
-}
-
-return ManifestIconsMin144.generateAuditResult({
-rawValue:!!matchingIcons.length,
-displayValue,
-debugString});
-
-}}
-
-
-module.exports=ManifestIconsMin144;
-
-
-},{"../lib/icons":18,"./audit":3}],"../audits/manifest-icons-min-192":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Audit a page to ensure it is not using the Application Cache API.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
-const icons=require('../lib/icons');
+const Audit = require('../audit');
 
-class ManifestIconsMin192 extends Audit{
+class AppCacheManifestAttr extends Audit {
 
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Offline',
+      name: 'appcache-manifest',
+      description: 'Avoids Application Cache',
+      helpText: 'Application Cache has been [deprecated](https://html.spec.whatwg.org/multipage/browsers.html#offline) by [Service Workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers). Consider implementing an offline solution using the [Cache Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Cache).',
+      requiredArtifacts: ['AppCacheManifest']
+    };
+  }
 
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    const usingAppcache = artifacts.AppCacheManifest !== null;
+    const debugString = usingAppcache ?
+        `Found <html manifest="${artifacts.AppCacheManifest}">.` : '';
 
-static get meta(){
-return{
-category:'Manifest',
-name:'manifest-icons-min-192',
-description:'Manifest contains icons at least 192px',
-helpText:'A 192px icon ensures that your app\'s icon displays well '+
-'on the homescreens of the largest mobile devices. [Learn '+
-'more](https://developers.google.com/web/tools/lighthouse/audits/manifest-contains-192px-icon).',
-requiredArtifacts:['Manifest']};
-
+    return {
+      rawValue: !usingAppcache,
+      debugString
+    };
+  }
 }
 
+module.exports = AppCacheManifestAttr;
 
+},{"../audit":2}],"../audits/dobetterweb/dom-size":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-static audit(artifacts){
-if(!artifacts.Manifest||!artifacts.Manifest.value){
-
-return ManifestIconsMin192.generateAuditResult({
-rawValue:false});
-
-}
-
-const manifest=artifacts.Manifest.value;
-if(icons.doExist(manifest)===false){
-return ManifestIconsMin192.generateAuditResult({
-rawValue:false});
-
-}
-
-const matchingIcons=icons.sizeAtLeast(192,manifest);
-
-let displayValue;
-let debugString;
-if(matchingIcons.length){
-displayValue=`found sizes: ${matchingIcons.join(', ')}`;
-}else{
-debugString='No icons are at least 192px';
-}
-
-return ManifestIconsMin192.generateAuditResult({
-rawValue:!!matchingIcons.length,
-displayValue,
-debugString});
-
-}}
-
-
-module.exports=ManifestIconsMin192;
-
-},{"../lib/icons":18,"./audit":3}],"../audits/manifest-name":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Audits a page to see how the size of DOM it creates. Stats like
+ * tree depth, # children, and total nodes are returned. The score is calculated
+ * based solely on the total number of nodes found on the page.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
+const Audit = require('../audit');
+const TracingProcessor = require('../../lib/traces/tracing-processor');
+const Formatter = require('../../report/formatter');
 
-class ManifestName extends Audit{
+const MAX_DOM_NODES = 1500;
+const MAX_DOM_TREE_WIDTH = 60;
+const MAX_DOM_TREE_DEPTH = 32;
 
+// Parameters for log-normal CDF scoring. See https://www.desmos.com/calculator/9cyxpm5qgp.
+const SCORING_POINT_OF_DIMINISHING_RETURNS = 2400;
+const SCORING_MEDIAN = 3000;
 
+class DOMSize extends Audit {
+  static get MAX_DOM_NODES() {
+    return MAX_DOM_NODES;
+  }
 
-static get meta(){
-return{
-category:'Manifest',
-name:'manifest-name',
-description:'Manifest contains `name`',
-helpText:'The `name` property identifies your app and is required. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/manifest-contains-name).',
-requiredArtifacts:['Manifest']};
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Performance',
+      name: 'dom-size',
+      description: 'Avoids an excessive DOM size',
+      optimalValue: DOMSize.MAX_DOM_NODES.toLocaleString() + ' nodes',
+      helpText: 'Browser engineers recommend pages contain fewer than ' +
+        `~${DOMSize.MAX_DOM_NODES.toLocaleString()} DOM nodes. The sweet spot is a tree depth < ` +
+        `${MAX_DOM_TREE_DEPTH} elements and fewer than ${MAX_DOM_TREE_WIDTH} ` +
+        'children/parent element. A large DOM can increase memory, cause longer ' +
+        '[style calculations](https://developers.google.com/web/fundamentals/performance/rendering/reduce-the-scope-and-complexity-of-style-calculations), ' +
+        'and produce costly [layout reflows](https://developers.google.com/speed/articles/reflow). [Learn more](https://developers.google.com/web/fundamentals/performance/rendering/).',
+      scoringMode: Audit.SCORING_MODES.NUMERIC,
+      requiredArtifacts: ['DOMStats']
+    };
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    const stats = artifacts.DOMStats;
+
+    /**
+     * html >
+     *   body >
+     *     div >
+     *       span
+     */
+    const depthSnippet = stats.depth.pathToElement.reduce((str, curr, i) => {
+      return `${str}\n` + '  '.repeat(i) + `${curr} >`;
+    }, '').replace(/>$/g, '').trim();
+    const widthSnippet = 'Element with most children:\n' +
+        stats.width.pathToElement[stats.width.pathToElement.length - 1];
+
+    // Use the CDF of a log-normal distribution for scoring.
+    //   <= 1500: score≈100
+    //   3000: score=50
+    //   >= 5970: score≈0
+    const distribution = TracingProcessor.getLogNormalDistribution(
+        SCORING_MEDIAN, SCORING_POINT_OF_DIMINISHING_RETURNS);
+    let score = 100 * distribution.computeComplementaryPercentile(stats.totalDOMNodes);
+
+    // Clamp the score to 0 <= x <= 100.
+    score = Math.max(0, Math.min(100, score));
+
+    const cards = [{
+      title: 'Total DOM Nodes',
+      value: stats.totalDOMNodes.toLocaleString(),
+      target: `< ${MAX_DOM_NODES.toLocaleString()} nodes`
+    }, {
+      title: 'DOM Depth',
+      value: stats.depth.max.toLocaleString(),
+      snippet: depthSnippet,
+      target: `< ${MAX_DOM_TREE_DEPTH.toLocaleString()}`
+    }, {
+      title: 'Maximum Children',
+      value: stats.width.max.toLocaleString(),
+      snippet: widthSnippet,
+      target: `< ${MAX_DOM_TREE_WIDTH.toLocaleString()} nodes`
+    }];
+
+    return {
+      rawValue: stats.totalDOMNodes,
+      optimalValue: this.meta.optimalValue,
+      score: Math.round(score),
+      displayValue: `${stats.totalDOMNodes.toLocaleString()} nodes`,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.CARDS,
+        value: cards
+      },
+      details: {
+        type: 'cards',
+        header: {type: 'text', text: 'View details'},
+        items: cards
+      }
+    };
+  }
 
 }
 
+module.exports = DOMSize;
 
-
-
-
-static audit(artifacts){
-if(!artifacts.Manifest||!artifacts.Manifest.value){
-
-return ManifestName.generateAuditResult({
-rawValue:false});
-
-}
-
-const manifest=artifacts.Manifest.value;
-return ManifestName.generateAuditResult({
-rawValue:!!manifest.name.value});
-
-}}
-
-
-module.exports=ManifestName;
-
-},{"./audit":3}],"../audits/manifest-short-name-length":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"../../lib/traces/tracing-processor":24,"../../report/formatter":27,"../audit":2}],"../audits/dobetterweb/external-anchors-use-rel-noopener":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
+const URL = require('../../lib/url-shim');
+const Audit = require('../audit');
+const Formatter = require('../../report/formatter');
 
-class ManifestShortNameLength extends Audit{
+class ExternalAnchorsUseRelNoopenerAudit extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Performance',
+      name: 'external-anchors-use-rel-noopener',
+      description: 'Opens external anchors using `rel="noopener"`',
+      helpText: 'Open new tabs using `rel="noopener"` to improve performance and ' +
+          'prevent security vulnerabilities. ' +
+          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/noopener).',
+      requiredArtifacts: ['URL', 'AnchorsWithNoRelNoopener']
+    };
+  }
 
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    let debugString;
+    const pageHost = new URL(artifacts.URL.finalUrl).host;
+    // Filter usages to exclude anchors that are same origin
+    // TODO: better extendedInfo for anchors with no href attribute:
+    // https://github.com/GoogleChrome/lighthouse/issues/1233
+    // https://github.com/GoogleChrome/lighthouse/issues/1345
+    const failingAnchors = artifacts.AnchorsWithNoRelNoopener
+      .filter(anchor => {
+        try {
+          return anchor.href === '' || new URL(anchor.href).host !== pageHost;
+        } catch (err) {
+          debugString = 'Lighthouse was unable to determine the destination ' +
+              'of some anchor tags. If they are not used as hyperlinks, ' +
+              'consider removing the _blank target.';
+          return true;
+        }
+      })
+      .map(anchor => {
+        return {
+          url: '<a' +
+              (anchor.href ? ` href="${anchor.href}"` : '') +
+              (anchor.target ? ` target="${anchor.target}"` : '') +
+              (anchor.rel ? ` rel="${anchor.rel}"` : '') + '>'
+        };
+      });
 
-
-static get meta(){
-return{
-category:'Manifest',
-name:'manifest-short-name-length',
-description:'Manifest\'s `short_name` won\'t be truncated when displayed on homescreen',
-helpText:'Make your app\'s `short_name` less than 12 characters to '+
-'ensure that it\'s not truncated on homescreens. [Learn '+
-'more](https://developers.google.com/web/tools/lighthouse/audits/manifest-short_name-is-not-truncated).',
-requiredArtifacts:['Manifest']};
-
+    return {
+      rawValue: failingAnchors.length === 0,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.URL_LIST,
+        value: failingAnchors
+      },
+      debugString
+    };
+  }
 }
 
+module.exports = ExternalAnchorsUseRelNoopenerAudit;
 
+},{"../../lib/url-shim":25,"../../report/formatter":27,"../audit":2}],"../audits/dobetterweb/geolocation-on-start":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-static audit(artifacts){
-if(!artifacts.Manifest||!artifacts.Manifest.value){
-
-return ManifestShortNameLength.generateAuditResult({
-rawValue:false});
-
-}
-
-
-const manifest=artifacts.Manifest.value;
-const shortNameValue=manifest.short_name.value||manifest.name.value;
-
-if(!shortNameValue){
-return ManifestShortNameLength.generateAuditResult({
-rawValue:false,
-debugString:'No short_name found in manifest.'});
-
-}
-
-
-
-
-const suggestedLength=12;
-const isShortNameShortEnough=shortNameValue.length<=suggestedLength;
-
-let debugString;
-if(!isShortNameShortEnough){
-debugString=`${suggestedLength} chars is the suggested maximum homescreen label length`;
-debugString+=` (Found: ${shortNameValue.length} chars).`;
-}
-
-return ManifestShortNameLength.generateAuditResult({
-rawValue:isShortNameShortEnough,
-debugString});
-
-}}
-
-
-module.exports=ManifestShortNameLength;
-
-},{"./audit":3}],"../audits/manifest-short-name":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Audits a page to see if it is requesting the geolocation API on
+ * page load. This is often a sign of poor user experience because it lacks context.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
+const Audit = require('../audit');
+const Formatter = require('../../report/formatter');
 
-class ManifestShortName extends Audit{
+class GeolocationOnStart extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'UX',
+      name: 'geolocation-on-start',
+      description: 'Avoids requesting the geolocation permission on page load',
+      helpText: 'Users are mistrustful of or confused by sites that request their ' +
+          'location without context. Consider tying the request to user gestures instead. ' +
+          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/geolocation-on-load).',
+      requiredArtifacts: ['GeolocationOnStart']
+    };
+  }
 
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    const results = artifacts.GeolocationOnStart.map(err => {
+      return Object.assign({
+        label: `line: ${err.line}, col: ${err.col}`
+      }, err);
+    });
 
-
-static get meta(){
-return{
-category:'Manifest',
-name:'manifest-short-name',
-description:'Manifest contains `short_name`',
-helpText:'The `short_name` property is a requirement for Add '+
-'To Homescreen. [Learn '+
-'more](https://developers.google.com/web/tools/lighthouse/audits/manifest-contains-short_name).',
-requiredArtifacts:['Manifest']};
+    return {
+      rawValue: results.length === 0,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.URL_LIST,
+        value: results
+      }
+    };
+  }
 
 }
 
+module.exports = GeolocationOnStart;
 
+},{"../../report/formatter":27,"../audit":2}],"../audits/dobetterweb/link-blocking-first-paint":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-static audit(artifacts){
-if(!artifacts.Manifest||!artifacts.Manifest.value){
-
-return ManifestShortName.generateAuditResult({
-rawValue:false});
-
-}
-
-const manifest=artifacts.Manifest.value;
-return ManifestShortName.generateAuditResult({
-
-rawValue:!!(manifest.short_name.value||manifest.name.value)});
-
-}}
-
-
-module.exports=ManifestShortName;
-
-},{"./audit":3}],"../audits/manifest-start-url":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Audit a page to see if it does not use <link> that block first paint.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
+const Audit = require('../audit');
+const URL = require('../../lib/url-shim');
+const Formatter = require('../../report/formatter');
 
-class ManifestStartUrl extends Audit{
+// Because of the way we detect blocking stylesheets, asynchronously loaded
+// CSS with link[rel=preload] and an onload handler (see https://github.com/filamentgroup/loadCSS)
+// can be falsely flagged as blocking. Therefore, ignore stylesheets that loaded fast enough
+// to possibly be non-blocking (and they have minimal impact anyway).
+const LOAD_THRESHOLD_IN_MS = 50;
 
+class LinkBlockingFirstPaintAudit extends Audit {
 
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Performance',
+      name: 'link-blocking-first-paint',
+      description: 'Render-blocking stylesheets',
+      informative: true,
+      helpText: 'Link elements are blocking the first paint of your page. Consider ' +
+          'inlining critical links and deferring non-critical ones. ' +
+          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/blocking-resources).',
+      requiredArtifacts: ['TagsBlockingFirstPaint']
+    };
+  }
 
-static get meta(){
-return{
-category:'Manifest',
-name:'manifest-start-url',
-description:'Manifest contains `start_url`',
-helpText:'Add a `start_url` to instruct the browser to launch a '+
-'specific URL whenever your app is launched from a homescreen. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/manifest-contains-start_url).',
-requiredArtifacts:['Manifest']};
+  /**
+   * @param {!Artifacts} artifacts
+   * @param {string} tagFilter The tagName to filter on
+   * @param {number=} loadThreshold Filter to resources that took at least this
+   *    many milliseconds to load.
+   * @return {!AuditResult} The object to pass to `generateAuditResult`
+   */
+  static computeAuditResultForTags(artifacts, tagFilter, loadThreshold = 0) {
+    const artifact = artifacts.TagsBlockingFirstPaint;
 
+    const filtered = artifact.filter(item => {
+      return item.tag.tagName === tagFilter &&
+        (item.endTime - item.startTime) * 1000 >= loadThreshold;
+    });
+
+    const startTime = filtered.reduce((t, item) => Math.min(t, item.startTime), Number.MAX_VALUE);
+    let endTime = 0;
+
+    const results = filtered.map(item => {
+      endTime = Math.max(item.endTime, endTime);
+
+      return {
+        url: URL.getDisplayName(item.tag.url),
+        totalKb: `${Math.round(item.transferSize / 1024)} KB`,
+        totalMs: `${Math.round((item.endTime - startTime) * 1000)}ms`
+      };
+    });
+
+    const delayTime = Math.round((endTime - startTime) * 1000);
+    let displayValue = '';
+    if (results.length > 1) {
+      displayValue = `${results.length} resources delayed first paint by ${delayTime}ms`;
+    } else if (results.length === 1) {
+      displayValue = `${results.length} resource delayed first paint by ${delayTime}ms`;
+    }
+
+    return {
+      displayValue,
+      rawValue: results.length === 0,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.TABLE,
+        value: {
+          results,
+          tableHeadings: {
+            url: 'URL',
+            totalKb: 'Size (KB)',
+            totalMs: 'Delayed Paint By (ms)'
+          }
+        }
+      }
+    };
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    return this.computeAuditResultForTags(artifacts, 'LINK', LOAD_THRESHOLD_IN_MS);
+  }
 }
 
+module.exports = LinkBlockingFirstPaintAudit;
 
+},{"../../lib/url-shim":25,"../../report/formatter":27,"../audit":2}],"../audits/dobetterweb/no-console-time":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-static audit(artifacts){
-if(!artifacts.Manifest||!artifacts.Manifest.value){
-
-return ManifestStartUrl.generateAuditResult({
-rawValue:false});
-
-}
-
-const manifest=artifacts.Manifest.value;
-return ManifestStartUrl.generateAuditResult({
-rawValue:!!manifest.start_url.value});
-
-}}
-
-
-module.exports=ManifestStartUrl;
-
-},{"./audit":3}],"../audits/manifest-theme-color":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Audit a page to see if it's using console.time()/console.timeEnd.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
+const URL = require('../../lib/url-shim');
+const Audit = require('../audit');
+const Formatter = require('../../report/formatter');
 
-class ManifestThemeColor extends Audit{
+class NoConsoleTimeAudit extends Audit {
 
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'JavaScript',
+      name: 'no-console-time',
+      description: 'Avoids `console.time()` in its own scripts',
+      helpText: 'Consider using `performance.mark()` and `performance.measure()` ' +
+          'from the User Timing API instead. They provide high-precision timestamps, ' +
+          'independent of the system clock, and are integrated in the Chrome DevTools Timeline. ' +
+          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/console-time).',
+      requiredArtifacts: ['URL', 'ConsoleTimeUsage']
+    };
+  }
 
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    let debugString;
+    // Filter usage from other hosts and keep eval'd code.
+    const results = artifacts.ConsoleTimeUsage.filter(err => {
+      if (err.isEval) {
+        return !!err.url;
+      }
 
-static get meta(){
-return{
-category:'Manifest',
-name:'manifest-theme-color',
-description:'Manifest contains `theme_color`',
-helpText:'Add a `theme_color` to set the color of the browser\'s '+
-'address bar. [Learn '+
-'more](https://developers.google.com/web/tools/lighthouse/audits/manifest-contains-theme_color).',
-requiredArtifacts:['Manifest']};
+      if (URL.isValid(err.url)) {
+        return URL.hostsMatch(artifacts.URL.finalUrl, err.url);
+      }
 
+      // If the violation doesn't have a valid url, don't filter it out, but
+      // warn the user that we don't know what the callsite is.
+      debugString = URL.INVALID_URL_DEBUG_STRING;
+      return true;
+    });
+
+    return {
+      rawValue: results.length === 0,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.TABLE,
+        value: {
+          results,
+          tableHeadings: {url: 'URL', lineCol: 'Line/Col', isEval: 'Eval\'d?'}
+        }
+      },
+      debugString
+    };
+  }
 }
 
+module.exports = NoConsoleTimeAudit;
 
+},{"../../lib/url-shim":25,"../../report/formatter":27,"../audit":2}],"../audits/dobetterweb/no-datenow":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-static audit(artifacts){
-if(!artifacts.Manifest||!artifacts.Manifest.value){
-
-return ManifestThemeColor.generateAuditResult({
-rawValue:false});
-
-}
-
-const manifest=artifacts.Manifest.value;
-return ManifestThemeColor.generateAuditResult({
-rawValue:!!manifest.theme_color.value});
-
-}}
-
-
-module.exports=ManifestThemeColor;
-
-},{"./audit":3}],"../audits/redirects-http":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Audit a page to see if it's using Date.now() (instead of a
+ * newer API like performance.now()).
+ */
 
 'use strict';
 
-const Audit=require('./audit');
+const URL = require('../../lib/url-shim');
+const Audit = require('../audit');
+const Formatter = require('../../report/formatter');
 
-class RedirectsHTTP extends Audit{
+class NoDateNowAudit extends Audit {
 
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'JavaScript',
+      name: 'no-datenow',
+      description: 'Avoids `Date.now()` in its own scripts',
+      helpText: 'Consider using `performance.now()` from the User Timing API ' +
+          'instead. It provides high-precision timestamps, independent of the system ' +
+          'clock. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/date-now).',
+      requiredArtifacts: ['URL', 'DateNowUse']
+    };
+  }
 
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    let debugString;
+    // Filter usage from other hosts and keep eval'd code.
+    const results = artifacts.DateNowUse.filter(err => {
+      if (err.isEval) {
+        return !!err.url;
+      }
 
-static get meta(){
-return{
-category:'Security',
-name:'redirects-http',
-description:'Redirects HTTP traffic to HTTPS',
-helpText:'If you\'ve already set up HTTPS, make sure that you redirect all HTTP traffic '+
-'to HTTPS. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/http-redirects-to-https).',
-requiredArtifacts:['HTTPRedirect']};
+      if (URL.isValid(err.url)) {
+        return URL.hostsMatch(artifacts.URL.finalUrl, err.url);
+      }
 
+      // If the violation doesn't have a valid url, don't filter it out, but
+      // warn the user that we don't know what the callsite is.
+      debugString = URL.INVALID_URL_DEBUG_STRING;
+      return true;
+    });
+
+    return {
+      rawValue: results.length === 0,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.TABLE,
+        value: {
+          results,
+          tableHeadings: {url: 'URL', lineCol: 'Line/Col', isEval: 'Eval\'d?'}
+        }
+      },
+      debugString
+    };
+  }
 }
 
+module.exports = NoDateNowAudit;
 
+},{"../../lib/url-shim":25,"../../report/formatter":27,"../audit":2}],"../audits/dobetterweb/no-document-write":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-static audit(artifacts){
-return RedirectsHTTP.generateAuditResult({
-rawValue:artifacts.HTTPRedirect.value,
-debugString:artifacts.HTTPRedirect.debugString});
-
-}}
-
-
-module.exports=RedirectsHTTP;
-
-},{"./audit":3}],"../audits/screenshots":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Audit a page to see if it's using document.write()
+ */
 
 'use strict';
 
-const Audit=require('./audit');
-const Formatter=require('../report/formatter');
+const Audit = require('../audit');
+const Formatter = require('../../report/formatter');
 
-class Screenshots extends Audit{
+class NoDocWriteAudit extends Audit {
 
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Performance',
+      name: 'no-document-write',
+      description: 'Avoids `document.write()`',
+      helpText: 'For users on slow connections, external scripts dynamically injected via ' +
+          '`document.write()` can delay page load by tens of seconds. ' +
+          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/document-write).',
+      requiredArtifacts: ['DocWriteUse']
+    };
+  }
 
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    const results = artifacts.DocWriteUse.map(err => {
+      return Object.assign({
+        label: `line: ${err.line}, col: ${err.col}`
+      }, err);
+    });
 
-static get meta(){
-return{
-category:'Performance',
-name:'screenshots',
-description:'Screenshots of all captured frames',
-requiredArtifacts:['traces']};
-
+    return {
+      rawValue: results.length === 0,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.URL_LIST,
+        value: results
+      }
+    };
+  }
 }
 
+module.exports = NoDocWriteAudit;
 
+},{"../../report/formatter":27,"../audit":2}],"../audits/dobetterweb/no-mutation-events":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-static audit(artifacts){
-const trace=artifacts.traces[this.DEFAULT_PASS];
-
-return artifacts.requestScreenshots(trace).then(screenshots=>{
-return Screenshots.generateAuditResult({
-rawValue:screenshots.length||0,
-extendedInfo:{
-formatter:Formatter.SUPPORTED_FORMATS.NULL,
-value:screenshots}});
-
-
-});
-}}
-
-
-module.exports=Screenshots;
-
-},{"../report/formatter":27,"./audit":3}],"../audits/service-worker":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Audit a page to see if it is using Mutation Events (and suggest
+ *     MutationObservers instead).
+ */
 
 'use strict';
 
-const URL=require('../lib/url-shim');
-const Audit=require('./audit');
+const URL = require('../../lib/url-shim');
+const Audit = require('../audit');
+const EventHelpers = require('../../lib/event-helpers');
+const Formatter = require('../../report/formatter');
 
+class NoMutationEventsAudit extends Audit {
 
+  static get MUTATION_EVENTS() {
+    return [
+      'DOMAttrModified',
+      'DOMAttributeNameChanged',
+      'DOMCharacterDataModified',
+      'DOMElementNameChanged',
+      'DOMNodeInserted',
+      'DOMNodeInsertedIntoDocument',
+      'DOMNodeRemoved',
+      'DOMNodeRemovedFromDocument',
+      'DOMSubtreeModified'
+    ];
+  }
 
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'JavaScript',
+      name: 'no-mutation-events',
+      description: 'Avoids Mutation Events in its own scripts',
+      helpText: 'Mutation Events are deprecated and harm performance. Consider using Mutation ' +
+          'Observers instead. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/mutation-events).',
+      requiredArtifacts: ['URL', 'EventListeners']
+    };
+  }
 
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    let debugString;
+    const listeners = artifacts.EventListeners;
 
+    const results = listeners.filter(loc => {
+      const isMutationEvent = this.MUTATION_EVENTS.includes(loc.type);
+      let sameHost = URL.hostsMatch(artifacts.URL.finalUrl, loc.url);
 
-function getActivatedServiceWorker(versions,url){
-const origin=new URL(url).origin;
-return versions.find(v=>v.status==='activated'&&new URL(v.scriptURL).origin===origin);
+      if (!URL.isValid(loc.url)) {
+        sameHost = true;
+        debugString = URL.INVALID_URL_DEBUG_STRING;
+      }
+
+      return sameHost && isMutationEvent;
+    }).map(EventHelpers.addFormattedCodeSnippet);
+
+    const groupedResults = EventHelpers.groupCodeSnippetsByLocation(results);
+
+    return {
+      rawValue: groupedResults.length === 0,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.TABLE,
+        value: {
+          results: groupedResults,
+          tableHeadings: {url: 'URL', lineCol: 'Line/Col', type: 'Event', code: 'Snippet'}
+        }
+      },
+      debugString
+    };
+  }
 }
 
-class ServiceWorker extends Audit{
+module.exports = NoMutationEventsAudit;
 
+},{"../../lib/event-helpers":17,"../../lib/url-shim":25,"../../report/formatter":27,"../audit":2}],"../audits/dobetterweb/no-old-flexbox":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-static get meta(){
-return{
-category:'Offline',
-name:'service-worker',
-description:'Registers a Service Worker',
-helpText:'The service worker is the technology that enables your app to use many '+
-'Progressive Web App features, such as offline, add to homescreen, and push '+
-'notifications. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/registered-service-worker).',
-requiredArtifacts:['URL','ServiceWorker']};
-
-}
-
-
-
-
-
-static audit(artifacts){
-
-
-const version=getActivatedServiceWorker(
-artifacts.ServiceWorker.versions,artifacts.URL.finalUrl);
-
-return ServiceWorker.generateAuditResult({
-rawValue:!!version});
-
-}}
-
-
-module.exports=ServiceWorker;
-
-},{"../lib/url-shim":25,"./audit":3}],"../audits/speed-index-metric":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Audit a page to see if it is using the obsolete
+ *     `display: box` flexbox.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
-const TracingProcessor=require('../lib/traces/tracing-processor');
-const Formatter=require('../report/formatter');
+const Audit = require('../audit');
+const URL = require('../../lib/url-shim');
+const StyleHelpers = require('../../lib/styles-helpers');
+const Formatter = require('../../report/formatter');
 
+class NoOldFlexboxAudit extends Audit {
 
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'CSS',
+      name: 'no-old-flexbox',
+      description: 'Avoids old CSS flexbox',
+      helpText: 'The 2009 spec of Flexbox is deprecated and is 2.3x slower than the latest ' +
+          'spec. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/old-flexbox).',
+      requiredArtifacts: ['Styles', 'URL']
+    };
+  }
 
-const SCORING_POINT_OF_DIMINISHING_RETURNS=1250;
-const SCORING_MEDIAN=5500;
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    // https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/
+    // (e.g. box-flex, box-orient, box-flex-group, display: flexbox (2011 version))
+    const displayPropResults = StyleHelpers.filterStylesheetsByUsage(artifacts.Styles,
+        'display', StyleHelpers.addVendorPrefixes(['box', 'flexbox']));
+    const otherPropResults = StyleHelpers.filterStylesheetsByUsage(artifacts.Styles,
+        StyleHelpers.addVendorPrefixes(['box-flex', 'box-orient', 'box-flex-group']));
 
-class SpeedIndexMetric extends Audit{
+    const sheetsUsingOldFlexbox = displayPropResults.concat(otherPropResults);
 
+    const pageUrl = artifacts.URL.finalUrl;
 
+    const urlList = [];
+    sheetsUsingOldFlexbox.forEach(sheet => {
+      sheet.parsedContent.forEach(props => {
+        const formattedStyleRule = StyleHelpers.getFormattedStyleRule(sheet.content, props);
 
-static get meta(){
-return{
-category:'Performance',
-name:'speed-index-metric',
-description:'Perceptual Speed Index',
-helpText:'Speed Index shows how quickly the contents of a page are visibly populated. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/speed-index).',
-optimalValue:SCORING_POINT_OF_DIMINISHING_RETURNS.toLocaleString(),
-requiredArtifacts:['traces']};
+        let url = sheet.header.sourceURL;
+        if (!URL.isValid(url) || url === pageUrl) {
+          url = 'inline';
+        } else {
+          url = URL.getDisplayName(url);
+        }
 
+        urlList.push({
+          url,
+          location: formattedStyleRule.location,
+          startLine: formattedStyleRule.startLine,
+          pre: formattedStyleRule.styleRule
+        });
+      });
+    });
+
+    return {
+      rawValue: sheetsUsingOldFlexbox.length === 0,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.TABLE,
+        value: {
+          results: urlList,
+          tableHeadings: {
+            url: 'URL', startLine: 'Line in the stylesheet / <style>', location: 'Column start/end',
+            pre: 'Snippet'}
+        }
+      }
+    };
+  }
 }
 
+module.exports = NoOldFlexboxAudit;
 
+},{"../../lib/styles-helpers":22,"../../lib/url-shim":25,"../../report/formatter":27,"../audit":2}],"../audits/dobetterweb/no-websql":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-
-
-static audit(artifacts){
-const trace=artifacts.traces[this.DEFAULT_PASS];
-
-
-return artifacts.requestSpeedline(trace).then(speedline=>{
-if(speedline.frames.length===0){
-throw new Error('Trace unable to find visual progress frames.');
-}
-
-if(speedline.speedIndex===0){
-throw new Error('Error in Speedline calculating Speed Index (speedIndex of 0).');
-}
-
-
-
-
-
-
-
-const distribution=TracingProcessor.getLogNormalDistribution(SCORING_MEDIAN,
-SCORING_POINT_OF_DIMINISHING_RETURNS);
-let score=100*distribution.computeComplementaryPercentile(speedline.perceptualSpeedIndex);
-
-
-score=Math.min(100,score);
-score=Math.max(0,score);
-
-const extendedInfo={
-timings:{
-firstVisualChange:speedline.first,
-visuallyComplete:speedline.complete,
-speedIndex:speedline.speedIndex,
-perceptualSpeedIndex:speedline.perceptualSpeedIndex},
-
-timestamps:{
-firstVisualChange:(speedline.first+speedline.beginning)*1000,
-visuallyComplete:(speedline.complete+speedline.beginning)*1000,
-speedIndex:(speedline.speedIndex+speedline.beginning)*1000,
-perceptualSpeedIndex:(speedline.perceptualSpeedIndex+speedline.beginning)*1000},
-
-frames:speedline.frames.map(frame=>{
-return{
-timestamp:frame.getTimeStamp(),
-progress:frame.getPerceptualProgress()};
-
-})};
-
-
-return SpeedIndexMetric.generateAuditResult({
-score:Math.round(score),
-rawValue:Math.round(speedline.perceptualSpeedIndex),
-optimalValue:this.meta.optimalValue,
-extendedInfo:{
-formatter:Formatter.SUPPORTED_FORMATS.SPEEDLINE,
-value:extendedInfo}});
-
-
-});
-}}
-
-
-module.exports=SpeedIndexMetric;
-
-},{"../lib/traces/tracing-processor":24,"../report/formatter":27,"./audit":3}],"../audits/theme-color-meta":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Audit a page to ensure that it does not open a database using
+ * the WebSQL API.
+ */
 
 'use strict';
 
-const validColor=require('../lib/web-inspector').Color.parse;
-const Audit=require('./audit');
+const Audit = require('../audit');
 
-class ThemeColor extends Audit{
+class NoWebSQLAudit extends Audit {
 
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Offline',
+      name: 'no-websql',
+      description: 'Avoids WebSQL DB',
+      helpText: 'Web SQL is deprecated. Consider using IndexedDB instead. ' +
+          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/web-sql).',
+      requiredArtifacts: ['WebSQL']
+    };
+  }
 
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    const db = artifacts.WebSQL;
+    const debugString = (db ?
+        `Found database "${db.name}", version: ${db.version}.` : '');
 
-static get meta(){
-return{
-category:'HTML',
-name:'theme-color-meta',
-description:'Has a `<meta name="theme-color">` tag',
-requiredArtifacts:['ThemeColor']};
-
+    return {
+      rawValue: !db,
+      debugString
+    };
+  }
 }
 
+module.exports = NoWebSQLAudit;
 
+},{"../audit":2}],"../audits/dobetterweb/notification-on-start":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-static audit(artifacts){
-const themeColorMeta=artifacts.ThemeColor;
-if(themeColorMeta===null){
-return ThemeColor.generateAuditResult({
-rawValue:false});
-
-}
-
-if(!validColor(themeColorMeta)){
-return ThemeColor.generateAuditResult({
-displayValue:themeColorMeta,
-rawValue:false,
-debugString:'The theme-color meta tag did not contain a valid CSS color.'});
-
-}
-
-return ThemeColor.generateAuditResult({
-displayValue:themeColorMeta,
-rawValue:true});
-
-}}
-
-
-module.exports=ThemeColor;
-
-},{"../lib/web-inspector":26,"./audit":3}],"../audits/time-to-interactive":[function(require,module,exports){
-
-
-
-
-
-
-
+/**
+ * @fileoverview Audits a page to see if it is requesting usage of the notification API on
+ * page load. This is often a sign of poor user experience because it lacks context.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
-const TracingProcessor=require('../lib/traces/tracing-processor');
-const FMPMetric=require('./first-meaningful-paint');
-const Formatter=require('../report/formatter');
+const Audit = require('../audit');
+const Formatter = require('../../report/formatter');
 
+class NotificationOnStart extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'UX',
+      name: 'notification-on-start',
+      description: 'Avoids requesting the notification permission on page load',
+      helpText: 'Users are mistrustful of or confused by sites that request to send ' +
+          'notifications without context. Consider tying the request to user gestures ' +
+          'instead. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/notifications-on-load).',
+      requiredArtifacts: ['NotificationOnStart']
+    };
+  }
 
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    const results = artifacts.NotificationOnStart.map(err => {
+      return Object.assign({
+        label: `line: ${err.line}, col: ${err.col}`
+      }, err);
+    });
 
-const SCORING_POINT_OF_DIMINISHING_RETURNS=1700;
-const SCORING_MEDIAN=5000;
-
-const SCORING_TARGET=5000;
-
-class TTIMetric extends Audit{
-
-
-
-static get meta(){
-return{
-category:'Performance',
-name:'time-to-interactive',
-description:'Time To Interactive (alpha)',
-helpText:'Time to Interactive identifies the time at which your app appears to be ready '+
-'enough to interact with. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/time-to-interactive).',
-optimalValue:SCORING_TARGET.toLocaleString()+'ms',
-requiredArtifacts:['traces']};
+    return {
+      rawValue: results.length === 0,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.URL_LIST,
+        value: results
+      }
+    };
+  }
 
 }
 
+module.exports = NotificationOnStart;
 
+},{"../../report/formatter":27,"../audit":2}],"../audits/dobetterweb/script-blocking-first-paint":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-static audit(artifacts){
-const trace=artifacts.traces[Audit.DEFAULT_PASS];
-
-
-const pending=[
-artifacts.requestSpeedline(trace),
-FMPMetric.audit(artifacts),
-artifacts.requestTracingModel(trace)];
-
-return Promise.all(pending).then(([speedline,fmpResult,model])=>{
-const endOfTraceTime=model.bounds.max;
-const fmpTiming=fmpResult.rawValue;
-const fmpResultExt=fmpResult.extendedInfo.value;
-
-
-
-const fMPtsInMS=fmpResultExt.timestamps.fMP/1000;
-const navStartTsInMS=fmpResultExt.timestamps.navStart/1000;
-
-
-let visuallyReadyTiming=0;
-if(speedline.frames){
-const eightyFivePctVC=speedline.frames.find(frame=>{
-return frame.getTimeStamp()>=fMPtsInMS&&frame.getProgress()>=85;
-});
-if(eightyFivePctVC){
-visuallyReadyTiming=eightyFivePctVC.getTimeStamp()-navStartTsInMS;
-}
-}
-
-
-let startTime=Math.max(fmpTiming,visuallyReadyTiming)-50;
-let endTime;
-let currentLatency=Infinity;
-const percentiles=[0.9];
-const threshold=50;
-const foundLatencies=[];
-
-
-while(currentLatency>threshold){
-
-startTime+=50;
-endTime=startTime+500;
-
-if(endTime>endOfTraceTime){
-throw new Error('Entire trace was found to be busy.');
-}
-
-const latencies=TracingProcessor.getRiskToResponsiveness(
-model,trace,startTime,endTime,percentiles);
-const estLatency=latencies[0].time;
-foundLatencies.push({
-estLatency:estLatency,
-startTime:startTime.toFixed(1)});
-
-
-
-currentLatency=estLatency;
-}
-
-const timeToInteractive=startTime;
-
-
-
-
-
-const distribution=TracingProcessor.getLogNormalDistribution(SCORING_MEDIAN,
-SCORING_POINT_OF_DIMINISHING_RETURNS);
-let score=100*distribution.computeComplementaryPercentile(startTime);
-
-
-score=Math.min(100,score);
-score=Math.max(0,score);
-score=Math.round(score);
-
-const extendedInfo={
-timings:{
-fMP:parseFloat(fmpTiming.toFixed(3)),
-visuallyReady:parseFloat(visuallyReadyTiming.toFixed(3)),
-timeToInteractive:parseFloat(startTime.toFixed(3))},
-
-timestamps:{
-fMP:fMPtsInMS*1000,
-visuallyReady:(visuallyReadyTiming+navStartTsInMS)*1000,
-timeToInteractive:(timeToInteractive+navStartTsInMS)*1000},
-
-expectedLatencyAtTTI:parseFloat(currentLatency.toFixed(3)),
-foundLatencies};
-
-
-return TTIMetric.generateAuditResult({
-score,
-rawValue:parseFloat(timeToInteractive.toFixed(1)),
-displayValue:`${parseFloat(timeToInteractive.toFixed(1))}ms`,
-optimalValue:this.meta.optimalValue,
-extendedInfo:{
-value:extendedInfo,
-formatter:Formatter.SUPPORTED_FORMATS.NULL}});
-
-
-});
-}}
-
-
-module.exports=TTIMetric;
-
-},{"../lib/traces/tracing-processor":24,"../report/formatter":27,"./audit":3,"./first-meaningful-paint":"../audits/first-meaningful-paint"}],"../audits/user-timings":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Audit a page to see if it does not use sync <script> in <head>.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
-const Formatter=require('../report/formatter');
+const Audit = require('../audit');
+const LinkBlockingFirstPaintAudit = require('./link-blocking-first-paint');
 
-class UserTimings extends Audit{
+class ScriptBlockingFirstPaint extends Audit {
 
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Performance',
+      name: 'script-blocking-first-paint',
+      description: 'Render-blocking scripts',
+      informative: true,
+      helpText: 'Script elements are blocking the first paint of your page. Consider inlining ' +
+          'critical scripts and deferring non-critical ones. ' +
+          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/blocking-resources).',
+      requiredArtifacts: ['TagsBlockingFirstPaint']
+    };
+  }
 
-
-static get meta(){
-return{
-category:'Performance',
-name:'user-timings',
-description:'User Timing marks and measures',
-helpText:'Consider instrumenting your app with the User Timing API to create custom, '+
-'real-world measurements of key user experiences. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/user-timing).',
-requiredArtifacts:['traces'],
-informative:true};
-
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    const result = LinkBlockingFirstPaintAudit.computeAuditResultForTags(artifacts, 'SCRIPT');
+    return result;
+  }
 }
 
+module.exports = ScriptBlockingFirstPaint;
 
+},{"../audit":2,"./link-blocking-first-paint":"../audits/dobetterweb/link-blocking-first-paint"}],"../audits/dobetterweb/uses-http2":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-static filterTrace(tabTrace){
-const userTimings=[];
-const measuresStartTimes={};
-
-
-
-
-
-tabTrace.processEvents.filter(evt=>{
-if(!evt.cat.includes('blink.user_timing')){
-return false;
-}
-
-
-
-return evt.name!=='requestStart'&&
-evt.name!=='navigationStart'&&
-evt.name!=='paintNonDefaultBackgroundColor'&&
-evt.args.frame===undefined;
-}).
-forEach(ut=>{
-
-if(ut.ph==='R'||ut.ph.toUpperCase()==='I'){
-userTimings.push({
-name:ut.name,
-isMark:true,
-args:ut.args,
-startTime:ut.ts});
-
-
-
-}else if(ut.ph.toLowerCase()==='b'){
-measuresStartTimes[ut.name]=ut.ts;
-
-
-}else if(ut.ph.toLowerCase()==='e'){
-userTimings.push({
-name:ut.name,
-isMark:false,
-args:ut.args,
-startTime:measuresStartTimes[ut.name],
-endTime:ut.ts});
-
-}
-});
-
-
-userTimings.forEach(ut=>{
-ut.startTime=(ut.startTime-tabTrace.navigationStartEvt.ts)/1000;
-if(!ut.isMark){
-ut.endTime=(ut.endTime-tabTrace.navigationStartEvt.ts)/1000;
-ut.duration=ut.endTime-ut.startTime;
-}
-});
-
-return userTimings;
-}
-
-
-
-
-static get blacklistedPrefixes(){
-return['goog_'];
-}
-
-
-
-
-
-
-static excludeBlacklisted(timing){
-return UserTimings.blacklistedPrefixes.every(prefix=>!timing.name.startsWith(prefix));
-}
-
-
-
-
-
-static audit(artifacts){
-const trace=artifacts.traces[Audit.DEFAULT_PASS];
-return artifacts.requestTraceOfTab(trace).then(tabTrace=>{
-const userTimings=this.filterTrace(tabTrace).filter(UserTimings.excludeBlacklisted);
-
-return UserTimings.generateAuditResult({
-rawValue:true,
-displayValue:userTimings.length,
-extendedInfo:{
-formatter:Formatter.SUPPORTED_FORMATS.USER_TIMINGS,
-value:userTimings}});
-
-
-});
-}}
-
-
-module.exports=UserTimings;
-
-},{"../report/formatter":27,"./audit":3}],"../audits/viewport":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Audit a page to ensure that resource loaded over its own
+ * origin are over the http/2 protocol.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
-const Parser=require('metaviewport-parser');
+const URL = require('../../lib/url-shim');
+const Audit = require('../audit');
+const Formatter = require('../../report/formatter');
 
-class Viewport extends Audit{
+class UsesHTTP2Audit extends Audit {
 
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Performance',
+      name: 'uses-http2',
+      description: 'Uses HTTP/2 for its own resources',
+      helpText: 'HTTP/2 offers many benefits over HTTP/1.1, including binary headers, ' +
+          'multiplexing, and server push. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/http2).',
+      requiredArtifacts: ['URL', 'networkRecords']
+    };
+  }
 
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    const networkRecords = artifacts.networkRecords[Audit.DEFAULT_PASS];
+    const finalHost = new URL(artifacts.URL.finalUrl).host;
 
-static get meta(){
-return{
-category:'Mobile Friendly',
-name:'viewport',
-description:'Has a `<meta name="viewport">` tag with `width` or `initial-scale`',
-helpText:'Add a viewport meta tag to optimize your app for mobile screens. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/has-viewport-meta-tag).',
-requiredArtifacts:['Viewport']};
+    // Filter requests that are on the same host as the page and not over h2.
+    const resources = networkRecords.filter(record => {
+      const requestHost = new URL(record._url).host;
+      const sameHost = requestHost === finalHost;
+      const notH2 = /HTTP\/[01][\.\d]?/i.test(record.protocol);
+      return sameHost && notH2;
+    }).map(record => {
+      return {
+        protocol: record.protocol,
+        url: record.url // .url is a getter and not copied over for the assign.
+      };
+    });
 
+    let displayValue = '';
+    if (resources.length > 1) {
+      displayValue = `${resources.length} requests were not handled over h2`;
+    } else if (resources.length === 1) {
+      displayValue = `${resources.length} request was not handled over h2`;
+    }
+
+    return {
+      rawValue: resources.length === 0,
+      displayValue: displayValue,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.TABLE,
+        value: {
+          results: resources,
+          tableHeadings: {url: 'URL', protocol: 'Protocol'}
+        }
+      }
+    };
+  }
 }
 
+module.exports = UsesHTTP2Audit;
 
+},{"../../lib/url-shim":25,"../../report/formatter":27,"../audit":2}],"../audits/dobetterweb/uses-passive-event-listeners":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-static audit(artifacts){
-if(artifacts.Viewport===null){
-return Viewport.generateAuditResult({
-debugString:'No viewport meta tag found',
-rawValue:false});
-
-}
-
-let debugString='';
-const parsedProps=Parser.parseMetaViewPortContent(artifacts.Viewport);
-
-if(Object.keys(parsedProps.unknownProperties).length){
-debugString+=`Invalid properties found: ${JSON.stringify(parsedProps.unknownProperties)}. `;
-}
-if(Object.keys(parsedProps.invalidValues).length){
-debugString+=`Invalid values found: ${JSON.stringify(parsedProps.invalidValues)}. `;
-}
-debugString=debugString.trim();
-
-const viewportProps=parsedProps.validProperties;
-const hasMobileViewport=viewportProps.width||viewportProps['initial-scale'];
-
-return Viewport.generateAuditResult({
-rawValue:!!hasMobileViewport,
-debugString});
-
-}}
-
-
-module.exports=Viewport;
-
-},{"./audit":3,"metaviewport-parser":270}],"../audits/without-javascript":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Audit a page to see if it is using passive event listeners on
+ * scroll-blocking touch and wheel event listeners.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
+const URL = require('../../lib/url-shim');
+const Audit = require('../audit');
+const EventHelpers = require('../../lib/event-helpers');
+const Formatter = require('../../report/formatter');
 
-class WithoutJavaScript extends Audit{
+class PassiveEventsAudit extends Audit {
 
+  static get SCROLL_BLOCKING_EVENTS() {
+    // See https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
+    return ['wheel', 'mousewheel', 'touchstart', 'touchmove'];
+  }
 
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'JavaScript',
+      name: 'uses-passive-event-listeners',
+      description: 'Uses passive listeners to improve scrolling performance',
+      helpText: 'Consider marking your touch and wheel event listeners as `passive` ' +
+          'to improve your page\'s scroll performance. ' +
+          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/passive-event-listeners).',
+      requiredArtifacts: ['URL', 'EventListeners']
+    };
+  }
 
-static get meta(){
-return{
-category:'JavaScript',
-name:'without-javascript',
-description:'Contains some content when JavaScript is not available',
-helpText:'Your app should display some content when JavaScript is disabled, even if it\'s '+
-'just a warning to the user that JavaScript is required to use the app. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/no-js).',
-requiredArtifacts:['HTMLWithoutJavaScript']};
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    let debugString;
+    const listeners = artifacts.EventListeners;
 
+    // Flags all touch and wheel listeners that 1) are from same host
+    // 2) are not passive 3) do not call preventDefault()
+    const results = listeners.filter(loc => {
+      const isScrollBlocking = this.SCROLL_BLOCKING_EVENTS.includes(loc.type);
+      const mentionsPreventDefault = loc.handler.description.match(
+            /\.preventDefault\(\s*\)/g);
+      let sameHost = URL.hostsMatch(artifacts.URL.finalUrl, loc.url);
+
+      if (!URL.isValid(loc.url)) {
+        sameHost = true;
+        debugString = URL.INVALID_URL_DEBUG_STRING;
+      }
+
+      return sameHost && isScrollBlocking && !loc.passive &&
+             !mentionsPreventDefault;
+    }).map(EventHelpers.addFormattedCodeSnippet);
+
+    const groupedResults = EventHelpers.groupCodeSnippetsByLocation(results);
+
+    return {
+      rawValue: groupedResults.length === 0,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.TABLE,
+        value: {
+          results: groupedResults,
+          tableHeadings: {url: 'URL', lineCol: 'Line/Col', type: 'Type', pre: 'Snippet'}
+        }
+      },
+      debugString
+    };
+  }
 }
 
+module.exports = PassiveEventsAudit;
 
+},{"../../lib/event-helpers":17,"../../lib/url-shim":25,"../../report/formatter":27,"../audit":2}],"../audits/estimated-input-latency":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
 
+const Audit = require('./audit');
+const TracingProcessor = require('../lib/traces/tracing-processor');
+const Formatter = require('../report/formatter');
 
+// Parameters (in ms) for log-normal CDF scoring. To see the curve:
+// https://www.desmos.com/calculator/srv0hqhf7d
+const SCORING_POINT_OF_DIMINISHING_RETURNS = 50;
+const SCORING_MEDIAN = 100;
 
-static audit(artifacts){
-const artifact=artifacts.HTMLWithoutJavaScript;
+class EstimatedInputLatency extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Performance',
+      name: 'estimated-input-latency',
+      description: 'Estimated Input Latency',
+      optimalValue: SCORING_POINT_OF_DIMINISHING_RETURNS.toLocaleString() + 'ms',
+      helpText: 'The score above is an estimate of how long your app takes to respond to user ' +
+          'input, in milliseconds. There is a 90% probability that a user encounters this amount ' +
+          'of latency, or less. 10% of the time a user can expect additional latency. If your ' +
+          'score is higher than Lighthouse\'s target score, users may perceive your app as ' +
+          'laggy. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/estimated-input-latency).',
+      scoringMode: Audit.SCORING_MODES.NUMERIC,
+      requiredArtifacts: ['traces']
+    };
+  }
 
-if(artifact.value.trim()===''){
-return WithoutJavaScript.generateAuditResult({
-rawValue:false,
-debugString:'The page body should render some content if its scripts are not available.'});
+  static calculate(tabTrace, model, trace) {
+    const startTime = tabTrace.timings.firstMeaningfulPaint;
+    if (!startTime) {
+      throw new Error('No firstMeaningfulPaint event found in trace');
+    }
 
+    const latencyPercentiles = TracingProcessor.getRiskToResponsiveness(model, trace, startTime);
+    const ninetieth = latencyPercentiles.find(result => result.percentile === 0.9);
+    const rawValue = parseFloat(ninetieth.time.toFixed(1));
+
+    // Use the CDF of a log-normal distribution for scoring.
+    //  10th Percentile ≈ 58ms
+    //  25th Percentile ≈ 75ms
+    //  Median = 100ms
+    //  75th Percentile ≈ 133ms
+    //  95th Percentile ≈ 199ms
+    const distribution = TracingProcessor.getLogNormalDistribution(SCORING_MEDIAN,
+        SCORING_POINT_OF_DIMINISHING_RETURNS);
+    const score = 100 * distribution.computeComplementaryPercentile(ninetieth.time);
+
+    return {
+      score: Math.round(score),
+      optimalValue: this.meta.optimalValue,
+      rawValue,
+      displayValue: `${rawValue}ms`,
+      extendedInfo: {
+        value: latencyPercentiles,
+        formatter: Formatter.SUPPORTED_FORMATS.NULL
+      }
+    };
+  }
+
+  /**
+   * Audits the page to estimate input latency.
+   * @see https://github.com/GoogleChrome/lighthouse/issues/28
+   * @param {!Artifacts} artifacts The artifacts from the gather phase.
+   * @return {!Promise<!AuditResult>} The score from the audit, ranging from 0-100.
+   */
+  static audit(artifacts) {
+    const trace = artifacts.traces[this.DEFAULT_PASS];
+
+    const pending = [
+      artifacts.requestTraceOfTab(trace),
+      artifacts.requestTracingModel(trace)
+    ];
+    return Promise.all(pending).then(([tabTrace, model]) => {
+      return EstimatedInputLatency.calculate(tabTrace, model, trace);
+    });
+  }
 }
 
-return WithoutJavaScript.generateAuditResult({
-rawValue:true});
+module.exports = EstimatedInputLatency;
 
-}}
-
-
-module.exports=WithoutJavaScript;
-
-},{"./audit":3}],"../audits/works-offline":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"../lib/traces/tracing-processor":24,"../report/formatter":27,"./audit":2}],"../audits/first-meaningful-paint":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Audit=require('./audit');
+const Audit = require('./audit');
+const TracingProcessor = require('../lib/traces/tracing-processor');
+const Formatter = require('../report/formatter');
 
-class WorksOffline extends Audit{
+// Parameters (in ms) for log-normal CDF scoring. To see the curve:
+// https://www.desmos.com/calculator/joz3pqttdq
+const SCORING_POINT_OF_DIMINISHING_RETURNS = 1600;
+const SCORING_MEDIAN = 4000;
 
 
+class FirstMeaningfulPaint extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Performance',
+      name: 'first-meaningful-paint',
+      description: 'First meaningful paint',
+      optimalValue: SCORING_POINT_OF_DIMINISHING_RETURNS.toLocaleString() + 'ms',
+      helpText: 'First meaningful paint measures when the primary content of a page is visible. ' +
+          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/first-meaningful-paint).',
+      scoringMode: Audit.SCORING_MODES.NUMERIC,
+      requiredArtifacts: ['traces']
+    };
+  }
 
-static get meta(){
-return{
-category:'Offline',
-name:'works-offline',
-description:'Responds with a 200 when offline',
-helpText:'If you\'re building a Progressive Web App, consider using a service worker so '+
-'that your app can work offline. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/http-200-when-offline).',
-requiredArtifacts:['Offline']};
+  /**
+   * Audits the page to give a score for First Meaningful Paint.
+   * @see https://github.com/GoogleChrome/lighthouse/issues/26
+   * @see https://docs.google.com/document/d/1BR94tJdZLsin5poeet0XoTW60M0SjvOJQttKT-JK8HI/view
+   * @param {!Artifacts} artifacts The artifacts from the gather phase.
+   * @return {!Promise<!AuditResult>} The score from the audit, ranging from 0-100.
+   */
+  static audit(artifacts) {
+    const trace = artifacts.traces[this.DEFAULT_PASS];
+    return artifacts.requestTraceOfTab(trace).then(tabTrace => {
+      if (!tabTrace.firstMeaningfulPaintEvt) {
+        throw new Error('No usable `firstMeaningfulPaint(Candidate)` events found in trace');
+      }
 
+      // navigationStart is currently essential to FMP calculation.
+      // see: https://github.com/GoogleChrome/lighthouse/issues/753
+      if (!tabTrace.navigationStartEvt) {
+        throw new Error('No `navigationStart` event found in trace');
+      }
+
+      const result = this.calculateScore({
+        navigationStart: tabTrace.navigationStartEvt,
+        firstMeaningfulPaint: tabTrace.firstMeaningfulPaintEvt,
+        firstContentfulPaint: tabTrace.firstContentfulPaintEvt
+      });
+
+      return {
+        score: result.score,
+        rawValue: parseFloat(result.duration),
+        displayValue: `${result.duration}ms`,
+        debugString: result.debugString,
+        optimalValue: this.meta.optimalValue,
+        extendedInfo: {
+          value: result.extendedInfo,
+          formatter: Formatter.SUPPORTED_FORMATS.NULL
+        }
+      };
+    });
+  }
+
+  static calculateScore(evts) {
+    const getTs = evt => evt && evt.ts;
+    const getTiming = evt => {
+      if (!evt) {
+        return undefined;
+      }
+      const timing = (evt.ts - evts.navigationStart.ts) / 1000;
+      return parseFloat(timing.toFixed(3));
+    };
+
+    // Expose the raw, unchanged monotonic timestamps from the trace, along with timing durations
+    const extendedInfo = {
+      timestamps: {
+        navStart: getTs(evts.navigationStart),
+        fCP: getTs(evts.firstContentfulPaint),
+        fMP: getTs(evts.firstMeaningfulPaint)
+      },
+      timings: {
+        navStart: 0,
+        fCP: getTiming(evts.firstContentfulPaint),
+        fMP: getTiming(evts.firstMeaningfulPaint)
+      }
+    };
+
+    // Use the CDF of a log-normal distribution for scoring.
+    //   < 1100ms: score≈100
+    //   4000ms: score=50
+    //   >= 14000ms: score≈0
+    const firstMeaningfulPaint = getTiming(evts.firstMeaningfulPaint);
+    const distribution = TracingProcessor.getLogNormalDistribution(SCORING_MEDIAN,
+        SCORING_POINT_OF_DIMINISHING_RETURNS);
+    let score = 100 * distribution.computeComplementaryPercentile(firstMeaningfulPaint);
+
+    // Clamp the score to 0 <= x <= 100.
+    score = Math.min(100, score);
+    score = Math.max(0, score);
+
+    return {
+      duration: firstMeaningfulPaint.toFixed(1),
+      score: Math.round(score),
+      rawValue: firstMeaningfulPaint,
+      extendedInfo
+    };
+  }
 }
 
+module.exports = FirstMeaningfulPaint;
 
+},{"../lib/traces/tracing-processor":24,"../report/formatter":27,"./audit":2}],"../audits/is-on-https":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
 
+const Audit = require('./audit');
+const Formatter = require('../report/formatter');
+const URL = require('../lib/url-shim');
 
+const SECURE_SCHEMES = ['data', 'https', 'wss'];
+const SECURE_DOMAINS = ['localhost', '127.0.0.1'];
 
-static audit(artifacts){
-return WorksOffline.generateAuditResult({
-rawValue:artifacts.Offline===200});
+class HTTPS extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Security',
+      name: 'is-on-https',
+      description: 'Uses HTTPS',
+      helpText: 'All sites should be protected with HTTPS, even ones that don\'t handle ' +
+          'sensitive data. HTTPS prevents intruders from tampering with or passively listening ' +
+          'in on the communications between your app and your users, and is a prerequisite for ' +
+          'HTTP/2 and many new web platform APIs. ' +
+          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/https).',
+      requiredArtifacts: ['networkRecords']
+    };
+  }
 
-}}
+  /**
+   * @param {{scheme: string, domain: string}} record
+   * @return {boolean}
+   */
+  static isSecureRecord(record) {
+    return SECURE_SCHEMES.includes(record.scheme) || SECURE_DOMAINS.includes(record.domain);
+  }
 
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    const networkRecords = artifacts.networkRecords[Audit.DEFAULT_PASS];
+    const insecureRecords = networkRecords
+        .filter(record => !HTTPS.isSecureRecord(record))
+        .map(record => ({url: URL.getDisplayName(record.url, {preserveHost: true})}));
 
-module.exports=WorksOffline;
+    let displayValue = '';
+    if (insecureRecords.length > 1) {
+      displayValue = `${insecureRecords.length} insecure requests found`;
+    } else if (insecureRecords.length === 1) {
+      displayValue = `${insecureRecords.length} insecure request found`;
+    }
 
-},{"./audit":3}],"./computed/computed-artifact":[function(require,module,exports){
+    return {
+      rawValue: insecureRecords.length === 0,
+      displayValue,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.URL_LIST,
+        value: insecureRecords
+      },
+      details: {
+        type: 'list',
+        header: {type: 'text', text: 'Insecure URLs:'},
+        items: insecureRecords.map(record => ({type: 'text', text: record.url})),
+      }
+    };
+  }
+}
 
+module.exports = HTTPS;
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"../lib/url-shim":25,"../report/formatter":27,"./audit":2}],"../audits/load-fast-enough-for-pwa":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-class ComputedArtifact{
-constructor(){
-this.cache=new Map();
+/** @fileoverview
+ *  This audit evaluates if a page's load performance is fast enough for it to be considered a PWA.
+ *  We are doublechecking that the network requests were throttled (or slow on their own)
+ *  Afterwards, we report if the TTI is less than 10 seconds.
+ */
+
+const Audit = require('./audit');
+const TTIMetric = require('./time-to-interactive');
+const Emulation = require('../lib/emulation');
+
+const Formatter = require('../report/formatter');
+
+// Maximum TTI to be considered "fast" for PWA baseline checklist
+//   https://developers.google.com/web/progressive-web-apps/checklist
+const MAXIMUM_TTI = 10 * 1000;
+
+class LoadFastEnough4Pwa extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'PWA',
+      name: 'load-fast-enough-for-pwa',
+      description: 'Page load is fast enough on 3G',
+      helpText: 'Satisfied if the _Time To Interactive_ duration is shorter than _10 seconds_, as defined by the [PWA Baseline Checklist](https://developers.google.com/web/progressive-web-apps/checklist). Network throttling is required (specifically: RTT latencies >= 150 RTT are expected).',
+      requiredArtifacts: ['traces', 'networkRecords']
+    };
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    const networkRecords = artifacts.networkRecords[Audit.DEFAULT_PASS];
+    const allRequestLatencies = networkRecords.map(record => {
+      if (!record._timing) return undefined;
+      // Use DevTools' definition of Waiting latency: https://github.com/ChromeDevTools/devtools-frontend/blob/66595b8a73a9c873ea7714205b828866630e9e82/front_end/network/RequestTimingView.js#L164
+      return record._timing.receiveHeadersEnd - record._timing.sendEnd;
+    });
+
+    const latency3gMin = Emulation.settings.TYPICAL_MOBILE_THROTTLING_METRICS.latency - 10;
+    const areLatenciesAll3G = allRequestLatencies.every(val =>
+        val === undefined || val > latency3gMin);
+
+    return TTIMetric.audit(artifacts).then(ttiResult => {
+      const timeToInteractive = ttiResult.extendedInfo.value.timings.timeToInteractive;
+      const isFast = timeToInteractive < MAXIMUM_TTI;
+
+      const extendedInfo = {
+        formatter: Formatter.SUPPORTED_FORMATS.NULL,
+        value: {areLatenciesAll3G, allRequestLatencies, isFast, timeToInteractive}
+      };
+
+      if (!areLatenciesAll3G) {
+        return {
+          rawValue: false,
+          // eslint-disable-next-line max-len
+          debugString: `The Time To Interactive was found at ${ttiResult.displayValue}, however, the network request latencies were not sufficiently realistic, so the performance measurements cannot be trusted.`,
+          extendedInfo
+        };
+      }
+
+      if (!isFast) {
+        return {
+          rawValue: false,
+           // eslint-disable-next-line max-len
+          debugString: `Under 3G conditions, the Time To Interactive was at ${ttiResult.displayValue}. More details in the "Performance" section.`,
+          extendedInfo
+        };
+      }
+
+      return {
+        rawValue: true,
+        extendedInfo
+      };
+    });
+  }
 }
 
+module.exports = LoadFastEnough4Pwa;
+
+},{"../lib/emulation":16,"../report/formatter":27,"./audit":2,"./time-to-interactive":"../audits/time-to-interactive"}],"../audits/manifest-short-name-length":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+const Audit = require('./audit');
+
+class ManifestShortNameLength extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Manifest',
+      name: 'manifest-short-name-length',
+      description: 'Manifest\'s `short_name` won\'t be truncated when displayed on homescreen',
+      helpText: 'Make your app\'s `short_name` less than 12 characters to ' +
+          'ensure that it\'s not truncated on homescreens. [Learn ' +
+          'more](https://developers.google.com/web/tools/lighthouse/audits/manifest-short_name-is-not-truncated).',
+      requiredArtifacts: ['Manifest']
+    };
+  }
 
 
+  static assessManifest(manifestValues, failures) {
+    if (manifestValues.isParseFailure) {
+      failures.push(manifestValues.parseFailureReason);
+      return;
+    }
 
+    const themeColorCheck = manifestValues.allChecks.find(i => i.id === 'hasThemeColor');
+    if (!themeColorCheck.passing) {
+      failures.push(themeColorCheck.failureText);
+    }
+  }
 
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    return artifacts.requestManifestValues(artifacts.Manifest).then(manifestValues => {
+      if (manifestValues.isParseFailure) {
+        return {
+          rawValue: false
+        };
+      }
 
+      const hasShortName = manifestValues.allChecks.find(i => i.id === 'hasShortName').passing;
+      if (!hasShortName) {
+        return {
+          rawValue: false,
+          debugString: 'No short_name found in manifest.'
+        };
+      }
 
-
-
-compute_(artifact){
-throw new Error('compute_() not implemented for computed artifact '+this.name);
+      const isShortEnough = manifestValues.allChecks.find(i => i.id === 'shortNameLength').passing;
+      return {
+        rawValue: isShortEnough
+      };
+    });
+  }
 }
 
+module.exports = ManifestShortNameLength;
 
+},{"./audit":2}],"../audits/redirects-http":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
 
+const Audit = require('./audit');
 
+class RedirectsHTTP extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Security',
+      name: 'redirects-http',
+      description: 'Redirects HTTP traffic to HTTPS',
+      helpText: 'If you\'ve already set up HTTPS, make sure that you redirect all HTTP traffic ' +
+         'to HTTPS. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/http-redirects-to-https).',
+      requiredArtifacts: ['HTTPRedirect']
+    };
+  }
 
-
-
-
-request(artifact){
-if(this.cache.has(artifact)){
-return Promise.resolve(this.cache.get(artifact));
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    return {
+      rawValue: artifacts.HTTPRedirect.value,
+      debugString: artifacts.HTTPRedirect.debugString
+    };
+  }
 }
 
-return Promise.resolve().then(_=>this.compute_(artifact)).then(computedArtifact=>{
-this.cache.set(artifact,computedArtifact);
-return computedArtifact;
-});
-}}
+module.exports = RedirectsHTTP;
+
+},{"./audit":2}],"../audits/service-worker":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+const URL = require('../lib/url-shim');
+const Audit = require('./audit');
+
+class ServiceWorker extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Offline',
+      name: 'service-worker',
+      description: 'Registers a Service Worker',
+      helpText: 'The service worker is the technology that enables your app to use many ' +
+         'Progressive Web App features, such as offline, add to homescreen, and push ' +
+         'notifications. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/registered-service-worker).',
+      requiredArtifacts: ['URL', 'ServiceWorker']
+    };
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    // Find active service worker for this URL. Match against
+    // artifacts.URL.finalUrl so audit accounts for any redirects.
+    const versions = artifacts.ServiceWorker.versions;
+    const url = artifacts.URL.finalUrl;
+
+    const origin = new URL(url).origin;
+    const matchingSW = versions.filter(v => v.status === 'activated')
+        .find(v => new URL(v.scriptURL).origin === origin);
+
+    return {
+      rawValue: !!matchingSW
+    };
+  }
+}
+
+module.exports = ServiceWorker;
+
+},{"../lib/url-shim":25,"./audit":2}],"../audits/speed-index-metric":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+const Audit = require('./audit');
+const TracingProcessor = require('../lib/traces/tracing-processor');
+const Formatter = require('../report/formatter');
+
+// Parameters (in ms) for log-normal CDF scoring. To see the curve:
+// https://www.desmos.com/calculator/mdgjzchijg
+const SCORING_POINT_OF_DIMINISHING_RETURNS = 1250;
+const SCORING_MEDIAN = 5500;
+
+class SpeedIndexMetric extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Performance',
+      name: 'speed-index-metric',
+      description: 'Perceptual Speed Index',
+      helpText: 'Speed Index shows how quickly the contents of a page are visibly populated. ' +
+          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/speed-index).',
+      optimalValue: SCORING_POINT_OF_DIMINISHING_RETURNS.toLocaleString(),
+      scoringMode: Audit.SCORING_MODES.NUMERIC,
+      requiredArtifacts: ['traces']
+    };
+  }
+
+  /**
+   * Audits the page to give a score for the Speed Index.
+   * @see  https://github.com/GoogleChrome/lighthouse/issues/197
+   * @param {!Artifacts} artifacts The artifacts from the gather phase.
+   * @return {!Promise<!AuditResult>} The score from the audit, ranging from 0-100.
+   */
+  static audit(artifacts) {
+    const trace = artifacts.traces[this.DEFAULT_PASS];
+
+    // run speedline
+    return artifacts.requestSpeedline(trace).then(speedline => {
+      if (speedline.frames.length === 0) {
+        throw new Error('Trace unable to find visual progress frames.');
+      }
+
+      if (speedline.speedIndex === 0) {
+        throw new Error('Error in Speedline calculating Speed Index (speedIndex of 0).');
+      }
+
+      // Use the CDF of a log-normal distribution for scoring.
+      //  10th Percentile = 2,240
+      //  25th Percentile = 3,430
+      //  Median = 5,500
+      //  75th Percentile = 8,820
+      //  95th Percentile = 17,400
+      const distribution = TracingProcessor.getLogNormalDistribution(SCORING_MEDIAN,
+        SCORING_POINT_OF_DIMINISHING_RETURNS);
+      let score = 100 * distribution.computeComplementaryPercentile(speedline.perceptualSpeedIndex);
+
+      // Clamp the score to 0 <= x <= 100.
+      score = Math.min(100, score);
+      score = Math.max(0, score);
+
+      const extendedInfo = {
+        timings: {
+          firstVisualChange: speedline.first,
+          visuallyComplete: speedline.complete,
+          speedIndex: speedline.speedIndex,
+          perceptualSpeedIndex: speedline.perceptualSpeedIndex
+        },
+        timestamps: {
+          firstVisualChange: (speedline.first + speedline.beginning) * 1000,
+          visuallyComplete: (speedline.complete + speedline.beginning) * 1000,
+          speedIndex: (speedline.speedIndex + speedline.beginning) * 1000,
+          perceptualSpeedIndex: (speedline.perceptualSpeedIndex + speedline.beginning) * 1000
+        },
+        frames: speedline.frames.map(frame => {
+          return {
+            timestamp: frame.getTimeStamp(),
+            progress: frame.getPerceptualProgress()
+          };
+        })
+      };
+
+      return {
+        score: Math.round(score),
+        rawValue: Math.round(speedline.perceptualSpeedIndex),
+        optimalValue: this.meta.optimalValue,
+        extendedInfo: {
+          formatter: Formatter.SUPPORTED_FORMATS.SPEEDLINE,
+          value: extendedInfo
+        }
+      };
+    });
+  }
+}
+
+module.exports = SpeedIndexMetric;
+
+},{"../lib/traces/tracing-processor":24,"../report/formatter":27,"./audit":2}],"../audits/splash-screen":[function(require,module,exports){
+/**
+ * @license Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
+
+'use strict';
+
+const Audit = require('./multi-check-audit');
+
+/**
+ * @fileoverview
+ * Audits if a page is configured for a custom splash screen when launched
+ * https://github.com/GoogleChrome/lighthouse/issues/24
+ *
+ * Requirements:
+ *   * manifest is not empty
+ *   * manifest has a valid name
+ *   * manifest has a valid background_color
+ *   * manifest has a valid theme_color
+ *   * manifest contains icon that's a png and size >= 512px
+ */
+
+class SplashScreen extends Audit {
+
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'PWA',
+      name: 'splash-screen',
+      description: 'Configured for a custom splash screen',
+      helpText: 'A default splash screen will be constructed for your app, but satisfying these requirements guarantee a high-quality [splash screen](https://developers.google.com/web/updates/2015/10/splashscreen) that transitions the user from tapping the home screen icon to your app\'s first paint',
+      requiredArtifacts: ['Manifest']
+    };
+  }
+
+  static assessManifest(manifestValues, failures) {
+    if (manifestValues.isParseFailure) {
+      failures.push(manifestValues.parseFailureReason);
+      return;
+    }
+
+    const splashScreenCheckIds = [
+      'hasName',
+      'hasBackgroundColor',
+      'hasThemeColor',
+      'hasIconsAtLeast512px'
+    ];
+
+    manifestValues.allChecks
+      .filter(item => splashScreenCheckIds.includes(item.id))
+      .forEach(item => {
+        if (!item.passing) {
+          failures.push(item.failureText);
+        }
+      });
+  }
 
 
-module.exports=ComputedArtifact;
+  static audit_(artifacts) {
+    const failures = [];
+
+    return artifacts.requestManifestValues(artifacts.Manifest).then(manifestValues => {
+      SplashScreen.assessManifest(manifestValues, failures);
+
+      return {
+        failures,
+        manifestValues
+      };
+    });
+  }
+}
+
+module.exports = SplashScreen;
+
+},{"./multi-check-audit":4}],"../audits/themed-omnibox":[function(require,module,exports){
+/**
+ * @license Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
+
+'use strict';
+
+const Audit = require('./multi-check-audit');
+const validColor = require('../lib/web-inspector').Color.parse;
+
+/**
+ * @fileoverview
+ * Audits if a page is configured for a themed address bar
+ *
+ * Requirements:
+ *   * manifest is not empty
+ *   * manifest has a valid theme_color
+ *   * HTML has a valid theme-color meta
+ */
+
+class ThemedOmnibox extends Audit {
+
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'PWA',
+      name: 'themed-omnibox',
+      description: 'Address bar matches brand colors',
+      helpText: 'The browser address bar can be themed to match your site. A `theme-color` [meta tag](https://developers.google.com/web/updates/2014/11/Support-for-theme-color-in-Chrome-39-for-Android) will upgrade the address bar when a user browses the site, and the [manifest theme-color](https://developers.google.com/web/updates/2015/08/using-manifest-to-set-sitewide-theme-color) will apply the same theme site-wide once it\'s been added to homescreen.',
+      requiredArtifacts: ['Manifest', 'ThemeColor']
+    };
+  }
+
+  static assessMetaThemecolor(themeColorMeta, failures) {
+    if (themeColorMeta === null) {
+      failures.push('No `<meta name="theme-color">` tag found');
+    } else if (!validColor(themeColorMeta)) {
+      failures.push('The theme-color meta tag did not contain a valid CSS color');
+    }
+  }
+
+  static assessManifest(manifestValues, failures) {
+    if (manifestValues.isParseFailure) {
+      failures.push(manifestValues.parseFailureReason);
+      return;
+    }
+
+    const themeColorCheck = manifestValues.allChecks.find(i => i.id === 'hasThemeColor');
+    if (!themeColorCheck.passing) {
+      failures.push(themeColorCheck.failureText);
+    }
+  }
+
+  static audit_(artifacts) {
+    const failures = [];
+
+    return artifacts.requestManifestValues(artifacts.Manifest).then(manifestValues => {
+      ThemedOmnibox.assessManifest(manifestValues, failures);
+      ThemedOmnibox.assessMetaThemecolor(artifacts.ThemeColor, failures);
+
+      return {
+        failures,
+        manifestValues,
+        themeColor: artifacts.ThemeColor
+      };
+    });
+  }
+}
+
+module.exports = ThemedOmnibox;
+
+},{"../lib/web-inspector":26,"./multi-check-audit":4}],"../audits/time-to-interactive":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
+
+'use strict';
+
+const Audit = require('./audit');
+const TracingProcessor = require('../lib/traces/tracing-processor');
+const Formatter = require('../report/formatter');
+
+// Parameters (in ms) for log-normal CDF scoring. To see the curve:
+//   https://www.desmos.com/calculator/jlrx14q4w8
+const SCORING_POINT_OF_DIMINISHING_RETURNS = 1700;
+const SCORING_MEDIAN = 5000;
+// This aligns with the external TTI targets in https://goo.gl/yXqxpL
+const SCORING_TARGET = 5000;
+
+class TTIMetric extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Performance',
+      name: 'time-to-interactive',
+      description: 'Time To Interactive (alpha)',
+      helpText: 'Time to Interactive identifies the time at which your app appears to be ready ' +
+          'enough to interact with. ' +
+          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/time-to-interactive).',
+      optimalValue: SCORING_TARGET.toLocaleString() + 'ms',
+      scoringMode: Audit.SCORING_MODES.NUMERIC,
+      requiredArtifacts: ['traces']
+    };
+  }
+
+  /**
+   *
+   * @param {number} minTime
+   * @param {number} maxTime
+   * @param {{model: !Object, trace: !Object}} data
+   * @param {number=} windowSize
+   * @return {{timeInMs: number|undefined, currentLatency: number, foundLatencies: !Array}}
+   */
+  static _forwardWindowTTI(minTime, maxTime, data, windowSize = 500) {
+    // Find first window where Est Input Latency is <50ms at the 90% percentile.
+    let startTime = minTime - 50;
+    let endTime;
+    let currentLatency = Infinity;
+    const percentiles = [0.9]; // [0.75, 0.9, 0.99, 1];
+    const threshold = 50;
+    const foundLatencies = [];
+
+    // When we've found a latency that's good enough, we're good.
+    while (currentLatency > threshold) {
+      // While latency is too high, increment just 50ms and look again.
+      startTime += 50;
+      endTime = startTime + windowSize;
+      // If there's no more room in the trace to look, we're done.
+      if (endTime > maxTime) {
+        return {currentLatency, foundLatencies};
+      }
+
+      // Get our expected latency for the time window
+      const latencies = TracingProcessor.getRiskToResponsiveness(
+        data.model, data.trace, startTime, endTime, percentiles);
+      const estLatency = latencies[0].time;
+      foundLatencies.push({
+        estLatency: estLatency,
+        startTime: startTime.toFixed(1)
+      });
+
+      // Grab this latency and try the threshold again
+      currentLatency = estLatency;
+    }
+
+    return {
+      // The start of our window is our TTI
+      timeInMs: startTime,
+      currentLatency,
+      foundLatencies,
+    };
+  }
+
+  /**
+   * @param {{fmpTiming: number, visuallyReadyTiming: number, traceEndTiming: number}} times
+   * @param {{model: !Object, trace: !Object}} data
+   * @return {{timeInMs: number|undefined, currentLatency: number, foundLatencies: !Array}}
+   */
+  static findTTIAlpha(times, data) {
+    return TTIMetric._forwardWindowTTI(
+      // when screenshots are not available, visuallyReady is 0 and this falls back to fMP
+      Math.max(times.fmpTiming, times.visuallyReadyTiming),
+      times.traceEndTiming,
+      data,
+      500
+    );
+  }
+
+  /**
+   * @param {{fmpTiming: number, visuallyReadyTiming: number, traceEndTiming: number}} times
+   * @param {{model: !Object, trace: !Object}} data
+   * @return {{timeInMs: number|undefined, currentLatency: number, foundLatencies: !Array}}
+   */
+  static findTTIAlphaFMPOnly(times, data) {
+    return TTIMetric._forwardWindowTTI(
+      times.fmpTiming,
+      times.traceEndTiming,
+      data,
+      500
+    );
+  }
+
+  /**
+   * @param {{fmpTiming: number, visuallyReadyTiming: number, traceEndTiming: number}} times
+   * @param {{model: !Object, trace: !Object}} data
+   * @return {{timeInMs: number|undefined, currentLatency: number, foundLatencies: !Array}}
+   */
+  static findTTIAlphaFMPOnly5s(times, data) {
+    return TTIMetric._forwardWindowTTI(
+      times.fmpTiming,
+      times.traceEndTiming,
+      data,
+      5000
+    );
+  }
+
+  /**
+   * Identify the time the page is "interactive"
+   * @see https://docs.google.com/document/d/1oiy0_ych1v2ADhyG_QW7Ps4BNER2ShlJjx2zCbVzVyY/edit#
+   *
+   * The user thinks the page is ready - (They believe the page is done enough to start interacting with)
+   *   - Layout has stabilized & key webfonts are visible.
+   *     AKA: First meaningful paint has fired.
+   *   - Page is nearly visually complete
+   *     Visual completion is 85%
+   *
+   * The page is actually ready for user:
+   *   - domContentLoadedEventEnd has fired
+   *     Definition: HTML parsing has finished, all DOMContentLoaded handlers have run.
+   *     No risk of DCL event handlers changing the page
+   *     No surprises of inactive buttons/actions as DOM element event handlers should be bound
+   *   - The main thread is available enough to handle user input
+   *     first 500ms window where Est Input Latency is <50ms at the 90% percentile.
+   *
+   * WARNING: This metric WILL change its calculation. If you rely on its numbers now, know that they
+   * will be changing in the future to a more accurate number.
+   *
+   * @param {!Artifacts} artifacts The artifacts from the gather phase.
+   * @return {!Promise<!AuditResult>} The score from the audit, ranging from 0-100.
+   */
+  static audit(artifacts) {
+    const trace = artifacts.traces[Audit.DEFAULT_PASS];
+
+    let debugString;
+    // We start looking at Math.Max(FMP, visProgress[0.85])
+    const pending = [
+      artifacts.requestSpeedline(trace).catch(err => {
+        debugString = `Trace error: ${err.message}`;
+        return null;
+      }),
+      artifacts.requestTraceOfTab(trace),
+      artifacts.requestTracingModel(trace)
+    ];
+    return Promise.all(pending).then(([speedline, tabTrace, model]) => {
+      // frame monotonic timestamps from speedline are in ms (ts / 1000), so we'll match
+      //   https://github.com/pmdartus/speedline/blob/123f512632a/src/frame.js#L86
+      const fMPtsInMS = tabTrace.timestamps.firstMeaningfulPaint;
+      const navStartTsInMS = tabTrace.timestamps.navigationStart;
+
+      if (!fMPtsInMS) {
+        throw new Error('No firstMeaningfulPaint event found in trace');
+      }
+
+      const onLoadTiming = tabTrace.timings.onLoad;
+      const fmpTiming = tabTrace.timings.firstMeaningfulPaint;
+      const traceEndTiming = tabTrace.timings.traceEnd;
+
+      // look at speedline results for 85% starting at FMP
+      let visuallyReadyTiming = 0;
+      if (speedline && speedline.frames) {
+        const eightyFivePctVC = speedline.frames.find(frame => {
+          return frame.getTimeStamp() >= fMPtsInMS && frame.getProgress() >= 85;
+        });
+        if (eightyFivePctVC) {
+          visuallyReadyTiming = eightyFivePctVC.getTimeStamp() - navStartTsInMS;
+        }
+      }
+
+      const times = {fmpTiming, visuallyReadyTiming, traceEndTiming};
+      const data = {tabTrace, model, trace};
+      const timeToInteractive = TTIMetric.findTTIAlpha(times, data);
+      const timeToInteractiveB = TTIMetric.findTTIAlphaFMPOnly(times, data);
+      const timeToInteractiveC = TTIMetric.findTTIAlphaFMPOnly5s(times, data);
+
+      if (!timeToInteractive.timeInMs) {
+        throw new Error('Entire trace was found to be busy.');
+      }
+
+      // Use the CDF of a log-normal distribution for scoring.
+      //   < 1200ms: score≈100
+      //   5000ms: score=50
+      //   >= 15000ms: score≈0
+      const distribution = TracingProcessor.getLogNormalDistribution(SCORING_MEDIAN,
+          SCORING_POINT_OF_DIMINISHING_RETURNS);
+      let score = 100 * distribution.computeComplementaryPercentile(timeToInteractive.timeInMs);
+
+      // Clamp the score to 0 <= x <= 100.
+      score = Math.min(100, score);
+      score = Math.max(0, score);
+      score = Math.round(score);
+
+      const extendedInfo = {
+        timings: {
+          onLoad: onLoadTiming,
+          fMP: parseFloat(fmpTiming.toFixed(3)),
+          visuallyReady: parseFloat(visuallyReadyTiming.toFixed(3)),
+          timeToInteractive: parseFloat(timeToInteractive.timeInMs.toFixed(3)),
+          timeToInteractiveB: timeToInteractiveB.timeInMs,
+          timeToInteractiveC: timeToInteractiveC.timeInMs,
+          endOfTrace: traceEndTiming,
+        },
+        timestamps: {
+          onLoad: (onLoadTiming + navStartTsInMS) * 1000,
+          fMP: fMPtsInMS * 1000,
+          visuallyReady: (visuallyReadyTiming + navStartTsInMS) * 1000,
+          timeToInteractive: (timeToInteractive.timeInMs + navStartTsInMS) * 1000,
+          timeToInteractiveB: (timeToInteractiveB.timeInMs + navStartTsInMS) * 1000,
+          timeToInteractiveC: (timeToInteractiveC.timeInMs + navStartTsInMS) * 1000,
+          endOfTrace: (traceEndTiming + navStartTsInMS) * 1000,
+        },
+        latencies: {
+          timeToInteractive: timeToInteractive.foundLatencies,
+          timeToInteractiveB: timeToInteractiveB.foundLatencies,
+          timeToInteractiveC: timeToInteractiveC.foundLatencies,
+        },
+        expectedLatencyAtTTI: parseFloat(timeToInteractive.currentLatency.toFixed(3))
+      };
+
+      return {
+        score,
+        debugString,
+        rawValue: parseFloat(timeToInteractive.timeInMs.toFixed(1)),
+        displayValue: `${parseFloat(timeToInteractive.timeInMs.toFixed(1))}ms`,
+        optimalValue: this.meta.optimalValue,
+        extendedInfo: {
+          value: extendedInfo,
+          formatter: Formatter.SUPPORTED_FORMATS.NULL
+        }
+      };
+    });
+  }
+}
+
+module.exports = TTIMetric;
+
+},{"../lib/traces/tracing-processor":24,"../report/formatter":27,"./audit":2}],"../audits/user-timings":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+const Audit = require('./audit');
+const Formatter = require('../report/formatter');
+
+class UserTimings extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Performance',
+      name: 'user-timings',
+      description: 'User Timing marks and measures',
+      helpText: 'Consider instrumenting your app with the User Timing API to create custom, ' +
+          'real-world measurements of key user experiences. ' +
+          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/user-timing).',
+      requiredArtifacts: ['traces'],
+      informative: true
+    };
+  }
+
+  /**
+   * @param {!Object} tabTrace
+   * @return {!Array<!UserTimingsExtendedInfo>}
+   */
+  static filterTrace(tabTrace) {
+    const userTimings = [];
+    const measuresStartTimes = {};
+
+    // Get all blink.user_timing events
+    // The event phases we are interested in are mark and instant events (R, i, I)
+    // and duration events which correspond to measures (B, b, E, e).
+    // @see https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#
+    tabTrace.processEvents.filter(evt => {
+      if (!evt.cat.includes('blink.user_timing')) {
+        return false;
+      }
+
+      // reject these "userTiming" events that aren't really UserTiming, by nuking ones with frame data (or requestStart)
+      // https://cs.chromium.org/search/?q=trace_event.*?user_timing&sq=package:chromium&type=cs
+      return evt.name !== 'requestStart' &&
+          evt.name !== 'navigationStart' &&
+          evt.name !== 'paintNonDefaultBackgroundColor' &&
+          evt.args.frame === undefined;
+    })
+    .forEach(ut => {
+      // Mark events fall under phases R and I (or i)
+      if (ut.ph === 'R' || ut.ph.toUpperCase() === 'I') {
+        userTimings.push({
+          name: ut.name,
+          isMark: true,
+          args: ut.args,
+          startTime: ut.ts
+        });
+
+      // Beginning of measure event, keep track of this events start time
+      } else if (ut.ph.toLowerCase() === 'b') {
+        measuresStartTimes[ut.name] = ut.ts;
+
+      // End of measure event
+      } else if (ut.ph.toLowerCase() === 'e') {
+        userTimings.push({
+          name: ut.name,
+          isMark: false,
+          args: ut.args,
+          startTime: measuresStartTimes[ut.name],
+          endTime: ut.ts
+        });
+      }
+    });
+
+    // baseline the timestamps against navStart, and translate to milliseconds
+    userTimings.forEach(ut => {
+      ut.startTime = (ut.startTime - tabTrace.navigationStartEvt.ts) / 1000;
+      if (!ut.isMark) {
+        ut.endTime = (ut.endTime - tabTrace.navigationStartEvt.ts) / 1000;
+        ut.duration = ut.endTime - ut.startTime;
+      }
+    });
+
+    return userTimings;
+  }
+
+  /*
+   * @return {!Array<string>}
+   */
+  static get blacklistedPrefixes() {
+    return ['goog_'];
+  }
+
+  /**
+   * We remove mark/measures entered by third parties not of interest to the user
+   * @param {!UserTimingsExtendedInfo} artifacts
+   * @return {boolean}
+   */
+  static excludeBlacklisted(timing) {
+    return UserTimings.blacklistedPrefixes.every(prefix => !timing.name.startsWith(prefix));
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    const trace = artifacts.traces[Audit.DEFAULT_PASS];
+    return artifacts.requestTraceOfTab(trace).then(tabTrace => {
+      const userTimings = this.filterTrace(tabTrace).filter(UserTimings.excludeBlacklisted);
+
+      return {
+        rawValue: true,
+        displayValue: userTimings.length,
+        extendedInfo: {
+          formatter: Formatter.SUPPORTED_FORMATS.USER_TIMINGS,
+          value: userTimings
+        }
+      };
+    });
+  }
+}
+
+module.exports = UserTimings;
+
+},{"../report/formatter":27,"./audit":2}],"../audits/viewport":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+const Audit = require('./audit');
+const Parser = require('metaviewport-parser');
+
+class Viewport extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Mobile Friendly',
+      name: 'viewport',
+      description: 'Has a `<meta name="viewport">` tag with `width` or `initial-scale`',
+      helpText: 'Add a viewport meta tag to optimize your app for mobile screens. ' +
+          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/has-viewport-meta-tag).',
+      requiredArtifacts: ['Viewport']
+    };
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    if (artifacts.Viewport === null) {
+      return {
+        debugString: 'No viewport meta tag found',
+        rawValue: false
+      };
+    }
+
+    let debugString = '';
+    const parsedProps = Parser.parseMetaViewPortContent(artifacts.Viewport);
+
+    if (Object.keys(parsedProps.unknownProperties).length) {
+      debugString += `Invalid properties found: ${JSON.stringify(parsedProps.unknownProperties)}. `;
+    }
+    if (Object.keys(parsedProps.invalidValues).length) {
+      debugString += `Invalid values found: ${JSON.stringify(parsedProps.invalidValues)}. `;
+    }
+    debugString = debugString.trim();
+
+    const viewportProps = parsedProps.validProperties;
+    const hasMobileViewport = viewportProps.width || viewportProps['initial-scale'];
+
+    return {
+      rawValue: !!hasMobileViewport,
+      debugString
+    };
+  }
+}
+
+module.exports = Viewport;
+
+},{"./audit":2,"metaviewport-parser":299}],"../audits/webapp-install-banner":[function(require,module,exports){
+/**
+ * @license Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
+
+'use strict';
+
+const Audit = require('./multi-check-audit');
+const SWAudit = require('./service-worker');
+
+/**
+ * @fileoverview
+ * Audits if a page is configured to prompt users with the webapp install banner.
+ * https://github.com/GoogleChrome/lighthouse/issues/23#issuecomment-270453303
+ *
+ * Requirements:
+ *   * manifest is not empty
+ *   * manifest has valid start url
+ *   * manifest has a valid name
+ *   * manifest has a valid shortname
+ *   * manifest display property is standalone, minimal-ui, or fullscreen
+ *   * manifest contains icon that's a png and size >= 192px
+ *   * SW is registered, and it owns this page and the manifest's start url
+ *   * Site engagement score of 2 or higher
+
+ * This audit covers these requirements with the following exceptions:
+ *   * it doesn't consider SW controlling the starturl
+ *   * it doesn't consider the site engagement score (naturally)
+ */
+
+class WebappInstallBanner extends Audit {
+
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'PWA',
+      name: 'webapp-install-banner',
+      description: 'User can be prompted to Install the Web App',
+      helpText: 'While users can manually add your site to their homescreen, the [prompt (aka app install banner)](https://developers.google.com/web/updates/2015/03/increasing-engagement-with-app-install-banners-in-chrome-for-android) will proactively prompt the user to install the app if the various requirements are met and the user has moderate engagement with your site.',
+      requiredArtifacts: ['URL', 'ServiceWorker', 'Manifest']
+    };
+  }
+
+  static assessManifest(manifestValues, failures) {
+    if (manifestValues.isParseFailure) {
+      failures.push(manifestValues.parseFailureReason);
+      return;
+    }
+
+    const bannerCheckIds = [
+      'hasName',
+      'hasShortName',
+      'hasStartUrl',
+      'hasPWADisplayValue',
+      'hasIconsAtLeast192px'
+    ];
+    manifestValues.allChecks
+      .filter(item => bannerCheckIds.includes(item.id))
+      .forEach(item => {
+        if (!item.passing) {
+          failures.push(item.failureText);
+        }
+      });
+  }
+
+
+  static assessServiceWorker(artifacts, failures) {
+    const hasServiceWorker = SWAudit.audit(artifacts).rawValue;
+    if (!hasServiceWorker) {
+      failures.push('Site registers a Service Worker');
+    }
+  }
+
+  static audit_(artifacts) {
+    const failures = [];
+
+    return artifacts.requestManifestValues(artifacts.Manifest).then(manifestValues => {
+      WebappInstallBanner.assessManifest(manifestValues, failures);
+      WebappInstallBanner.assessServiceWorker(artifacts, failures);
+
+      return {
+        failures,
+        manifestValues
+      };
+    });
+  }
+}
+
+module.exports = WebappInstallBanner;
+
+},{"./multi-check-audit":4,"./service-worker":"../audits/service-worker"}],"../audits/without-javascript":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+const Audit = require('./audit');
+
+class WithoutJavaScript extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'JavaScript',
+      name: 'without-javascript',
+      description: 'Contains some content when JavaScript is not available',
+      helpText: 'Your app should display some content when JavaScript is disabled, even if it\'s ' +
+          'just a warning to the user that JavaScript is required to use the app. ' +
+          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/no-js).',
+      requiredArtifacts: ['HTMLWithoutJavaScript']
+    };
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    const artifact = artifacts.HTMLWithoutJavaScript;
+
+    if (artifact.value.trim() === '') {
+      return {
+        rawValue: false,
+        debugString: 'The page body should render some content if its scripts are not available.'
+      };
+    }
+
+    return {
+      rawValue: true
+    };
+  }
+}
+
+module.exports = WithoutJavaScript;
+
+},{"./audit":2}],"../audits/works-offline":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+const URL = require('../lib/url-shim');
+const Audit = require('./audit');
+
+class WorksOffline extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Offline',
+      name: 'works-offline',
+      description: 'Responds with a 200 when offline',
+      helpText: 'If you\'re building a Progressive Web App, consider using a service worker so ' +
+          'that your app can work offline. ' +
+          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/http-200-when-offline).',
+      requiredArtifacts: ['Offline', 'URL']
+    };
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    let debugString;
+    if (!URL.equalWithExcludedFragments(artifacts.URL.initialUrl, artifacts.URL.finalUrl)) {
+      debugString = 'WARNING: You may be failing this check because your test URL ' +
+          `(${artifacts.URL.initialUrl}) was redirected to "${artifacts.URL.finalUrl}". ` +
+          'Try testing the second URL directly.';
+    }
+
+    return {
+      rawValue: artifacts.Offline === 200,
+      debugString
+    };
+  }
+}
+
+module.exports = WorksOffline;
+
+},{"../lib/url-shim":25,"./audit":2}],"./computed/computed-artifact":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+class ComputedArtifact {
+  constructor() {
+    this.cache = new Map();
+  }
+
+  /* eslint-disable no-unused-vars */
+
+  /**
+   * Override to implement a computed artifact. Can return a Promise or the
+   * computed artifact itself.
+   * @param {!Object} artifact Input to computation.
+   * @throws {Error}
+   */
+  compute_(artifact) {
+    throw new Error('compute_() not implemented for computed artifact ' + this.name);
+  }
+
+  /* eslint-enable no-unused-vars */
+
+  /**
+   * Request a computed artifact, caching the result on the input artifact.
+   * @param {!OBject} artifact
+   * @return {!Promise}
+   */
+  request(artifact) {
+    if (this.cache.has(artifact)) {
+      return Promise.resolve(this.cache.get(artifact));
+    }
+
+    return Promise.resolve().then(_ => this.compute_(artifact)).then(computedArtifact => {
+      this.cache.set(artifact, computedArtifact);
+      return computedArtifact;
+    });
+  }
+}
+
+module.exports = ComputedArtifact;
 
 },{}],"./computed/critical-request-chains":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const ComputedArtifact=require('./computed-artifact');
-const WebInspector=require('../../lib/web-inspector');
+const ComputedArtifact = require('./computed-artifact');
+const WebInspector = require('../../lib/web-inspector');
 
-class CriticalRequestChains extends ComputedArtifact{
+class CriticalRequestChains extends ComputedArtifact {
 
-get name(){
-return'CriticalRequestChains';
+  get name() {
+    return 'CriticalRequestChains';
+  }
+
+  /**
+   * For now, we use network priorities as a proxy for "render-blocking"/critical-ness.
+   * It's imperfect, but there is not a higher-fidelity signal available yet.
+   * @see https://docs.google.com/document/d/1bCDuq9H1ih9iNjgzyAL0gpwNFiEP4TZS-YLRp_RuMlc
+   * @param  {any} request
+   */
+  isCritical(request) {
+    // XHRs are fetched at High priority, but we exclude them, as they are unlikely to be critical
+    const resourceTypeCategory = request._resourceType && request._resourceType._category;
+    if (resourceTypeCategory === WebInspector.resourceTypes.XHR._category) {
+      return false;
+    }
+
+    // Treat favicons as non-critical resources
+    if (request.mimeType === 'image/x-icon' ||
+        (request.parsedURL && request.parsedURL.lastPathComponent === 'favicon.ico')) {
+      return false;
+    }
+
+    return ['VeryHigh', 'High', 'Medium'].includes(request.priority());
+  }
+
+  compute_(networkRecords) {
+    // Build a map of requestID -> Node.
+    const requestIdToRequests = new Map();
+    for (const request of networkRecords) {
+      requestIdToRequests.set(request.requestId, request);
+    }
+
+    // Get all the critical requests.
+    /** @type {!Array<NetworkRequest>} */
+    const criticalRequests = networkRecords.filter(req => this.isCritical(req));
+
+    const flattenRequest = request => {
+      return {
+        url: request._url,
+        startTime: request.startTime,
+        endTime: request.endTime,
+        responseReceivedTime: request.responseReceivedTime,
+        transferSize: request.transferSize
+      };
+    };
+
+    // Create a tree of critical requests.
+    const criticalRequestChains = {};
+    for (const request of criticalRequests) {
+      // Work back from this request up to the root. If by some weird quirk we are giving request D
+      // here, which has ancestors C, B and A (where A is the root), we will build array [C, B, A]
+      // during this phase.
+      const ancestors = [];
+      let ancestorRequest = request.initiatorRequest();
+      let node = criticalRequestChains;
+      while (ancestorRequest) {
+        const ancestorIsCritical = this.isCritical(ancestorRequest);
+
+        // If the parent request isn't a high priority request it won't be in the
+        // requestIdToRequests map, and so we can break the chain here. We should also
+        // break it if we've seen this request before because this is some kind of circular
+        // reference, and that's bad.
+        if (!ancestorIsCritical || ancestors.includes(ancestorRequest.requestId)) {
+          // Set the ancestors to an empty array and unset node so that we don't add
+          // the request in to the tree.
+          ancestors.length = 0;
+          node = undefined;
+          break;
+        }
+        ancestors.push(ancestorRequest.requestId);
+        ancestorRequest = ancestorRequest.initiatorRequest();
+      }
+
+      // With the above array we can work from back to front, i.e. A, B, C, and during this process
+      // we can build out the tree for any nodes that have yet to be created.
+      let ancestor = ancestors.pop();
+      while (ancestor) {
+        const parentRequest = requestIdToRequests.get(ancestor);
+        const parentRequestId = parentRequest.requestId;
+        if (!node[parentRequestId]) {
+          node[parentRequestId] = {
+            request: flattenRequest(parentRequest),
+            children: {}
+          };
+        }
+
+        // Step to the next iteration.
+        ancestor = ancestors.pop();
+        node = node[parentRequestId].children;
+      }
+
+      if (!node) {
+        continue;
+      }
+
+      // If the node already exists, bail.
+      if (node[request.requestId]) {
+        continue;
+      }
+
+      // node should now point to the immediate parent for this request.
+      node[request.requestId] = {
+        request: flattenRequest(request),
+        children: {}
+      };
+    }
+
+    return criticalRequestChains;
+  }
 }
 
+module.exports = CriticalRequestChains;
 
-
-
-
-
-
-isCritical(request){
-
-const resourceTypeCategory=request._resourceType&&request._resourceType._category;
-if(resourceTypeCategory===WebInspector.resourceTypes.XHR._category){
-return false;
-}
-
-
-if(request.mimeType==='image/x-icon'||
-request.parsedURL&&request.parsedURL.lastPathComponent==='favicon.ico'){
-return false;
-}
-
-return['VeryHigh','High','Medium'].includes(request.priority());
-}
-
-compute_(networkRecords){
-
-const requestIdToRequests=new Map();
-for(const request of networkRecords){
-requestIdToRequests.set(request.requestId,request);
-}
-
-
-
-const criticalRequests=networkRecords.filter(req=>this.isCritical(req));
-
-const flattenRequest=request=>{
-return{
-url:request._url,
-startTime:request.startTime,
-endTime:request.endTime,
-responseReceivedTime:request.responseReceivedTime,
-transferSize:request.transferSize};
-
-};
-
-
-const criticalRequestChains={};
-for(const request of criticalRequests){
-
-
-
-const ancestors=[];
-let ancestorRequest=request.initiatorRequest();
-let node=criticalRequestChains;
-while(ancestorRequest){
-const ancestorIsCritical=this.isCritical(ancestorRequest);
-
-
-
-
-
-if(!ancestorIsCritical||ancestors.includes(ancestorRequest.requestId)){
-
-
-ancestors.length=0;
-node=undefined;
-break;
-}
-ancestors.push(ancestorRequest.requestId);
-ancestorRequest=ancestorRequest.initiatorRequest();
-}
-
-
-
-let ancestor=ancestors.pop();
-while(ancestor){
-const parentRequest=requestIdToRequests.get(ancestor);
-const parentRequestId=parentRequest.requestId;
-if(!node[parentRequestId]){
-node[parentRequestId]={
-request:flattenRequest(parentRequest),
-children:{}};
-
-}
-
-
-ancestor=ancestors.pop();
-node=node[parentRequestId].children;
-}
-
-if(!node){
-continue;
-}
-
-
-if(node[request.requestId]){
-continue;
-}
-
-
-node[request.requestId]={
-request:flattenRequest(request),
-children:{}};
-
-}
-
-return criticalRequestChains;
-}}
-
-
-module.exports=CriticalRequestChains;
-
-},{"../../lib/web-inspector":26,"./computed-artifact":"./computed/computed-artifact"}],"./computed/network-throughput":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"../../lib/web-inspector":26,"./computed-artifact":"./computed/computed-artifact"}],"./computed/manifest-values":[function(require,module,exports){
+/**
+ * @license Copyright 2016 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
 
 'use strict';
 
-const ComputedArtifact=require('./computed-artifact');
+const ComputedArtifact = require('./computed-artifact');
+const icons = require('../../lib/icons');
 
-class NetworkThroughput extends ComputedArtifact{
-get name(){
-return'NetworkThroughput';
+const PWA_DISPLAY_VALUES = ['minimal-ui', 'fullscreen', 'standalone'];
+
+// Historically, Chrome recommended 12 chars as the maximum short_name length to prevent truncation.
+// See #69 for more discussion & https://developer.chrome.com/apps/manifest/name#short_name
+const SUGGESTED_SHORTNAME_LENGTH = 12;
+
+class ManifestValues extends ComputedArtifact {
+
+  get name() {
+    return 'ManifestValues';
+  }
+
+  static get validityIds() {
+    return ['hasManifest', 'hasParseableManifest'];
+  }
+
+  static get manifestChecks() {
+    return [
+      {
+        id: 'hasStartUrl',
+        failureText: 'Manifest does not contain a `start_url`',
+        validate: manifest => !!manifest.value.start_url.value
+      },
+      {
+        id: 'hasIconsAtLeast192px',
+        failureText: 'Manifest does not have icons at least 192px',
+        validate: manifest => icons.doExist(manifest.value) &&
+            icons.sizeAtLeast(192, /** @type {!Manifest} */ (manifest.value)).length > 0
+      },
+      {
+        id: 'hasIconsAtLeast512px',
+        failureText: 'Manifest does not have icons at least 512px',
+        validate: manifest => icons.doExist(manifest.value) &&
+            icons.sizeAtLeast(512, /** @type {!Manifest} */ (manifest.value)).length > 0
+      },
+      {
+        id: 'hasPWADisplayValue',
+        failureText: 'Manifest\'s `display` value is not one of: ' + PWA_DISPLAY_VALUES.join(' | '),
+        validate: manifest => PWA_DISPLAY_VALUES.includes(manifest.value.display.value)
+      },
+      {
+        id: 'hasBackgroundColor',
+        failureText: 'Manifest does not have `background_color`',
+        validate: manifest => !!manifest.value.background_color.value
+      },
+      {
+        id: 'hasThemeColor',
+        failureText: 'Manifest does not have `theme_color`',
+        validate: manifest => !!manifest.value.theme_color.value
+      },
+      {
+        id: 'hasShortName',
+        failureText: 'Manifest does not have `short_name`',
+        validate: manifest => !!manifest.value.short_name.value
+      },
+      {
+        id: 'shortNameLength',
+        failureText: 'Manifest `short_name` will be truncated when displayed on the homescreen',
+        validate: manifest => manifest.value.short_name.value &&
+            manifest.value.short_name.value.length <= SUGGESTED_SHORTNAME_LENGTH
+      },
+      {
+        id: 'hasName',
+        failureText: 'Manifest does not have `name`',
+        validate: manifest => !!manifest.value.name.value
+      }
+    ];
+  }
+
+  /**
+   * Returns results of all manifest checks
+   * @param {Manifest} manifest
+   * @return {{isParseFailure: !boolean, parseFailureReason: ?string, allChecks: !Array}}
+   */
+  compute_(manifest) {
+    // if the manifest isn't there or is invalid json, we report that and bail
+    let parseFailureReason;
+
+    if (manifest === null) {
+      parseFailureReason = 'No manifest was fetched';
+    }
+    if (manifest && manifest.value === undefined) {
+      parseFailureReason = 'Manifest failed to parse as valid JSON';
+    }
+    if (parseFailureReason) {
+      return {
+        isParseFailure: true,
+        parseFailureReason,
+        allChecks: []
+      };
+    }
+
+    // manifest is valid, so do the rest of the checks
+    const remainingChecks = ManifestValues.manifestChecks.map(item => {
+      item.passing = item.validate(manifest);
+      return item;
+    });
+
+    return {
+      isParseFailure: false,
+      parseFailureReason,
+      allChecks: remainingChecks
+    };
+  }
+
 }
 
+module.exports = ManifestValues;
 
+},{"../../lib/icons":18,"./computed-artifact":"./computed/computed-artifact"}],"./computed/network-throughput":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
 
+const ComputedArtifact = require('./computed-artifact');
 
+class NetworkThroughput extends ComputedArtifact {
+  get name() {
+    return 'NetworkThroughput';
+  }
 
+  /**
+   * Computes the average throughput for the given records in bytes/second.
+   * Excludes data URI, failed or otherwise incomplete, and cached requests.
+   *
+   * @param {?Array<!WebInspector.NetworkRequest>} networkRecords
+   * @return {number}
+   */
+  compute_(networkRecords) {
+    if (!networkRecords || !networkRecords.length) {
+      return 0;
+    }
 
+    let totalBytes = 0;
+    const timeBoundaries = networkRecords.reduce((boundaries, record) => {
+      const scheme = record.parsedURL && record.parsedURL.scheme;
+      if (scheme === 'data' || record.failed || !record.finished ||
+          record.statusCode > 300 || !record.transferSize) {
+        return boundaries;
+      }
 
+      totalBytes += record.transferSize;
+      boundaries.push({time: record.responseReceivedTime, isStart: true});
+      boundaries.push({time: record.endTime, isStart: false});
+      return boundaries;
+    }, []).sort((a, b) => a.time - b.time);
 
-compute_(networkRecords){
-if(!networkRecords||!networkRecords.length){
-return 0;
+    let inflight = 0;
+    let currentStart = 0;
+    let totalDuration = 0;
+    timeBoundaries.forEach(boundary => {
+      if (boundary.isStart) {
+        if (inflight === 0) {
+          currentStart = boundary.time;
+        }
+        inflight++;
+      } else {
+        inflight--;
+        if (inflight === 0) {
+          totalDuration += boundary.time - currentStart;
+        }
+      }
+    });
+
+    return totalBytes / totalDuration;
+  }
 }
 
-let totalBytes=0;
-const timeBoundaries=networkRecords.reduce((boundaries,record)=>{
-const scheme=record.parsedURL&&record.parsedURL.scheme;
-if(scheme==='data'||record.failed||!record.finished||
-record.statusCode>300||!record.transferSize){
-return boundaries;
-}
-
-totalBytes+=record.transferSize;
-boundaries.push({time:record.responseReceivedTime,isStart:true});
-boundaries.push({time:record.endTime,isStart:false});
-return boundaries;
-},[]).sort((a,b)=>a.time-b.time);
-
-let inflight=0;
-let currentStart=0;
-let totalDuration=0;
-timeBoundaries.forEach(boundary=>{
-if(boundary.isStart){
-if(inflight===0){
-currentStart=boundary.time;
-}
-inflight++;
-}else{
-inflight--;
-if(inflight===0){
-totalDuration+=boundary.time-currentStart;
-}
-}
-});
-
-return totalBytes/totalDuration;
-}}
-
-
-module.exports=NetworkThroughput;
+module.exports = NetworkThroughput;
 
 },{"./computed-artifact":"./computed/computed-artifact"}],"./computed/pushed-requests":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const ComputedArtifact=require('./computed-artifact');
+const ComputedArtifact = require('./computed-artifact');
 
-class PushedRequests extends ComputedArtifact{
+class PushedRequests extends ComputedArtifact {
 
-get name(){
-return'PushedRequests';
+  get name() {
+    return 'PushedRequests';
+  }
+
+  /**
+   * Return list of network requests that were pushed.
+   * @param {!Array<!WebInspector.NetworkRequest>} records
+   * @return {!Array<!WebInspector.NetworkRequest>}
+   */
+  compute_(records) {
+    const pushedRecords = records.filter(r => r._timing && !!r._timing.pushStart);
+    return pushedRecords;
+  }
 }
 
-
-
-
-
-
-compute_(records){
-const pushedRecords=records.filter(r=>r._timing&&!!r._timing.pushStart);
-return pushedRecords;
-}}
-
-
-module.exports=PushedRequests;
+module.exports = PushedRequests;
 
 },{"./computed-artifact":"./computed/computed-artifact"}],"./computed/screenshots":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const ComputedArtifact=require('./computed-artifact');
-const DevtoolsTimelineModel=require('../../lib/traces/devtools-timeline-model');
+const ComputedArtifact = require('./computed-artifact');
+const DevtoolsTimelineModel = require('../../lib/traces/devtools-timeline-model');
 
-class ScreenshotFilmstrip extends ComputedArtifact{
+class ScreenshotFilmstrip extends ComputedArtifact {
 
-get name(){
-return'Screenshots';
+  get name() {
+    return 'Screenshots';
+  }
+
+  fetchScreenshot(frame) {
+    return frame
+      .imageDataPromise()
+      .then(data => 'data:image/jpg;base64,' + data);
+  }
+
+  /**
+   * @param {{traceEvents: !Array}} trace
+   * @return {!Promise}
+  */
+  compute_(trace) {
+    const model = new DevtoolsTimelineModel(trace.traceEvents);
+    const filmStripFrames = model.filmStripModel().frames();
+
+    const frameFetches = filmStripFrames.map(frame => this.fetchScreenshot(frame));
+    return Promise.all(frameFetches).then(images => {
+      const result = filmStripFrames.map((frame, i) => ({
+        timestamp: frame.timestamp,
+        datauri: images[i]
+      }));
+      return result;
+    });
+  }
 }
 
-fetchScreenshot(frame){
-return frame.
-imageDataPromise().
-then(data=>'data:image/jpg;base64,'+data);
-}
-
-
-
-
-
-compute_(trace){
-const model=new DevtoolsTimelineModel(trace.traceEvents);
-const filmStripFrames=model.filmStripModel().frames();
-
-const frameFetches=filmStripFrames.map(frame=>this.fetchScreenshot(frame));
-return Promise.all(frameFetches).then(images=>{
-const result=filmStripFrames.map((frame,i)=>({
-timestamp:frame.timestamp,
-datauri:images[i]}));
-
-return result;
-});
-}}
-
-
-module.exports=ScreenshotFilmstrip;
+module.exports = ScreenshotFilmstrip;
 
 },{"../../lib/traces/devtools-timeline-model":23,"./computed-artifact":"./computed/computed-artifact"}],"./computed/speedline":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
 
+const ComputedArtifact = require('./computed-artifact');
+const speedline = require('speedline');
 
+class Speedline extends ComputedArtifact {
 
+  get name() {
+    return 'Speedline';
+  }
 
+  /**
+   * @return {!Promise}
+   */
+  compute_(trace) {
+    // speedline() may throw without a promise, so we resolve immediately
+    // to get in a promise chain.
+    return Promise.resolve().then(_ => {
+      return speedline(trace.traceEvents);
+    }).then(speedlineResults => {
+      return speedlineResults;
+    });
+  }
+}
 
+module.exports = Speedline;
 
-
-
-
-
-
-
-
-
-
+},{"./computed-artifact":"./computed/computed-artifact","speedline":302}],"./computed/trace-of-tab":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const ComputedArtifact=require('./computed-artifact');
-const speedline=require('speedline');
+/**
+ * @fileoverview Singluar helper to parse a raw trace and extract the most useful data for
+ * various tools. This artifact will take a trace and then:
+ *
+ * 1. Find the TracingStartedInPage and navigationStart events of our intended tab & frame.
+ * 2. Find the firstContentfulPaint and marked firstMeaningfulPaint events
+ * 3. Isolate only the trace events from the tab's process (including all threads like compositor)
+ *      * Sort those trace events in chronological order (as order isn't guaranteed)
+ * 4. Return all those items in one handy bundle.
+ */
 
-class Speedline extends ComputedArtifact{
+const ComputedArtifact = require('./computed-artifact');
+const log = require('../../lib/log');
 
-get name(){
-return'Speedline';
+class TraceOfTab extends ComputedArtifact {
+  get name() {
+    return 'TraceOfTab';
+  }
+
+  /**
+   * @param {{traceEvents: !Array}} trace
+   * @return {!{processEvents: !Array<TraceEvent>, startedInPageEvt: TraceEvent, navigationStartEvt: TraceEvent, firstContentfulPaintEvt: TraceEvent, firstMeaningfulPaintEvt: TraceEvent}}
+  */
+  compute_(trace) {
+    // Parse the trace for our key events and sort them by timestamp.
+    const keyEvents = trace.traceEvents
+      .filter(e => {
+        return e.cat.includes('blink.user_timing') ||
+          e.cat.includes('loading') ||
+          e.cat.includes('devtools.timeline') ||
+          e.name === 'TracingStartedInPage';
+      })
+      .sort((event0, event1) => event0.ts - event1.ts);
+
+    // The first TracingStartedInPage in the trace is definitely our renderer thread of interest
+    // Beware: the tracingStartedInPage event can appear slightly after a navigationStart
+    const startedInPageEvt = keyEvents.find(e => e.name === 'TracingStartedInPage');
+    // Filter to just events matching the frame ID for sanity
+    const frameEvents = keyEvents.filter(e => e.args.frame === startedInPageEvt.args.data.page);
+
+    // Our navStart will be the last frame navigation in the trace
+    const navigationStart = frameEvents.filter(e => e.name === 'navigationStart').pop();
+    if (!navigationStart) throw new Error('navigationStart was not found in the trace');
+
+    // Find our first paint of this frame
+    const firstPaint = frameEvents.find(e => e.name === 'firstPaint' && e.ts > navigationStart.ts);
+
+    // FCP will follow at/after the FP
+    const firstContentfulPaint = frameEvents.find(
+      e => e.name === 'firstContentfulPaint' && e.ts > navigationStart.ts
+    );
+
+    // fMP will follow at/after the FP
+    let firstMeaningfulPaint = frameEvents.find(
+      e => e.name === 'firstMeaningfulPaint' && e.ts > navigationStart.ts
+    );
+
+    // If there was no firstMeaningfulPaint event found in the trace, the network idle detection
+    // may have not been triggered before Lighthouse finished tracing.
+    // In this case, we'll use the last firstMeaningfulPaintCandidate we can find.
+    // However, if no candidates were found (a bogus trace, likely), we fail.
+    if (!firstMeaningfulPaint) {
+      const fmpCand = 'firstMeaningfulPaintCandidate';
+      log.verbose('trace-of-tab', `No firstMeaningfulPaint found, falling back to last ${fmpCand}`);
+      const lastCandidate = frameEvents.filter(e => e.name === fmpCand).pop();
+      if (!lastCandidate) {
+        log.verbose('trace-of-tab', 'No `firstMeaningfulPaintCandidate` events found in trace');
+      }
+      firstMeaningfulPaint = lastCandidate;
+    }
+
+    const onLoad = keyEvents.find(e => e.name === 'MarkLoad' && e.ts > navigationStart.ts);
+
+    // subset all trace events to just our tab's process (incl threads other than main)
+    const processEvents = trace.traceEvents
+      .filter(e => e.pid === startedInPageEvt.pid)
+      .sort((event0, event1) => event0.ts - event1.ts);
+
+    const traceEnd = trace.traceEvents.reduce((max, evt) => {
+      return max.ts > evt.ts ? max : evt;
+    });
+
+    const metrics = {
+      navigationStart,
+      firstPaint,
+      firstContentfulPaint,
+      firstMeaningfulPaint,
+      traceEnd,
+      onLoad,
+    };
+
+    const timings = {};
+    const timestamps = {};
+
+    Object.keys(metrics).forEach(metric => {
+      timestamps[metric] = metrics[metric] && metrics[metric].ts / 1000;
+      timings[metric] = timestamps[metric] - timestamps.navigationStart;
+    });
+
+    return {
+      timings,
+      timestamps,
+      processEvents,
+      startedInPageEvt: startedInPageEvt,
+      navigationStartEvt: navigationStart,
+      firstPaintEvt: firstPaint,
+      firstContentfulPaintEvt: firstContentfulPaint,
+      firstMeaningfulPaintEvt: firstMeaningfulPaint,
+      onLoadEvt: onLoad,
+    };
+  }
 }
 
-
-
-
-compute_(trace){
-
-
-return Promise.resolve().then(_=>{
-return speedline(trace.traceEvents);
-}).then(speedlineResults=>{
-return speedlineResults;
-});
-}}
-
-
-module.exports=Speedline;
-
-},{"./computed-artifact":"./computed/computed-artifact","speedline":273}],"./computed/trace-of-tab":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-'use strict';
-
-
-
-
-
-
-
-
-
-
-
-
-const ComputedArtifact=require('./computed-artifact');
-const log=require('../../lib/log');
-
-class TraceOfTab extends ComputedArtifact{
-
-get name(){
-return'TraceOfTab';
-}
-
-
-
-
-
-compute_(trace){
-
-const keyEvents=trace.traceEvents.filter(e=>{
-return e.cat.includes('blink.user_timing')||e.cat.includes('loading')||
-e.name==='TracingStartedInPage';
-}).sort((event0,event1)=>event0.ts-event1.ts);
-
-
-
-const startedInPageEvt=keyEvents.find(e=>e.name==='TracingStartedInPage');
-
-const frameEvents=keyEvents.filter(e=>e.args.frame===startedInPageEvt.args.data.page);
-
-
-const firstPaint=frameEvents.find(e=>e.name==='firstPaint');
-
-const navigationStart=frameEvents.filter(e=>
-e.name==='navigationStart'&&e.ts<firstPaint.ts).pop();
-
-
-const firstContentfulPaint=frameEvents.find(e=>
-e.name==='firstContentfulPaint'&&e.ts>=firstPaint.ts);
-
-
-let firstMeaningfulPaint=frameEvents.find(e=>
-e.name==='firstMeaningfulPaint'&&e.ts>=firstPaint.ts);
-
-
-
-
-
-if(!firstMeaningfulPaint){
-const fmpCand='firstMeaningfulPaintCandidate';
-log.verbose('trace-of-tab',`No firstMeaningfulPaint found, falling back to last ${fmpCand}`);
-const lastCandidate=frameEvents.filter(e=>e.name===fmpCand).pop();
-if(!lastCandidate){
-log.verbose('trace-of-tab','No `firstMeaningfulPaintCandidate` events found in trace');
-}
-firstMeaningfulPaint=lastCandidate;
-}
-
-
-const processEvents=trace.traceEvents.filter(e=>{
-return e.pid===startedInPageEvt.pid;
-}).sort((event0,event1)=>event0.ts-event1.ts);
-
-return{
-processEvents,
-startedInPageEvt:startedInPageEvt,
-navigationStartEvt:navigationStart,
-firstPaintEvt:firstPaint,
-firstContentfulPaintEvt:firstContentfulPaint,
-firstMeaningfulPaintEvt:firstMeaningfulPaint};
-
-}}
-
-
-module.exports=TraceOfTab;
+module.exports = TraceOfTab;
 
 },{"../../lib/log":19,"./computed-artifact":"./computed/computed-artifact"}],"./computed/tracing-model":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const ComputedArtifact=require('./computed-artifact');
-const TracingProcessor=require('../../lib/traces/tracing-processor');
+const ComputedArtifact = require('./computed-artifact');
+const TracingProcessor = require('../../lib/traces/tracing-processor');
 
-class TracingModel extends ComputedArtifact{
+class TracingModel extends ComputedArtifact {
 
-get name(){
-return'TracingModel';
+  get name() {
+    return 'TracingModel';
+  }
+
+  /**
+   * Return catapult traceviewer model
+   * @param {{traceEvents: !Array}} trace
+   * @return {!TracingProcessorModel}
+   */
+  compute_(trace) {
+    const tracingProcessor = new TracingProcessor();
+    return tracingProcessor.init(trace);
+  }
+
 }
 
-
-
-
-
-
-compute_(trace){
-const tracingProcessor=new TracingProcessor();
-return tracingProcessor.init(trace);
-}}
-
-
-
-module.exports=TracingModel;
+module.exports = TracingModel;
 
 },{"../../lib/traces/tracing-processor":24,"./computed-artifact":"./computed/computed-artifact"}],"./gatherers/accessibility":[function(require,module,exports){
-(function(Buffer){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+(function (Buffer){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
+/* global document */
 
+const Gatherer = require('./gatherer');
 
-const Gatherer=require('./gatherer');
+const axe = Buffer("LyohIGFYZSB2Mi4xLjcKICogQ29weXJpZ2h0IChjKSAyMDE2IERlcXVlIFN5c3RlbXMsIEluYy4KICoKICogWW91ciB1c2Ugb2YgdGhpcyBTb3VyY2UgQ29kZSBGb3JtIGlzIHN1YmplY3QgdG8gdGhlIHRlcm1zIG9mIHRoZSBNb3ppbGxhIFB1YmxpYwogKiBMaWNlbnNlLCB2LiAyLjAuIElmIGEgY29weSBvZiB0aGUgTVBMIHdhcyBub3QgZGlzdHJpYnV0ZWQgd2l0aCB0aGlzCiAqIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uCiAqCiAqIFRoaXMgZW50aXJlIGNvcHlyaWdodCBub3RpY2UgbXVzdCBhcHBlYXIgaW4gZXZlcnkgY29weSBvZiB0aGlzIGZpbGUgeW91CiAqIGRpc3RyaWJ1dGUgb3IgaW4gYW55IGZpbGUgdGhhdCBjb250YWlucyBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGlzIHNvdXJjZQogKiBjb2RlLgogKi8KIWZ1bmN0aW9uIGEod2luZG93KXtmdW5jdGlvbiBiKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYjtyZXR1cm4gYT8oYj1heGUudXRpbHMuY2xvbmUoYSksYi5jb21tb25zPWEuY29tbW9ucyk6Yj17fSxiLnJlcG9ydGVyPWIucmVwb3J0ZXJ8fG51bGwsYi5ydWxlcz1iLnJ1bGVzfHxbXSxiLmNoZWNrcz1iLmNoZWNrc3x8W10sYi5kYXRhPU9iamVjdC5hc3NpZ24oe2NoZWNrczp7fSxydWxlczp7fX0sYi5kYXRhKSxifWZ1bmN0aW9uIGMoYSxiLGMpeyJ1c2Ugc3RyaWN0Ijt2YXIgZCxlO2ZvcihkPTAsZT1hLmxlbmd0aDtkPGU7ZCsrKWJbY10oYVtkXSl9ZnVuY3Rpb24gZChhKXt0aGlzLmJyYW5kPSJheGUiLHRoaXMuYXBwbGljYXRpb249ImF4ZUFQSSIsdGhpcy50YWdFeGNsdWRlPVsiZXhwZXJpbWVudGFsIl0sdGhpcy5kZWZhdWx0Q29uZmlnPWEsdGhpcy5faW5pdCgpfWZ1bmN0aW9uIGUoYSl7InVzZSBzdHJpY3QiO3RoaXMuaWQ9YS5pZCx0aGlzLmRhdGE9bnVsbCx0aGlzLnJlbGF0ZWROb2Rlcz1bXSx0aGlzLnJlc3VsdD1udWxsfWZ1bmN0aW9uIGYoYSl7InVzZSBzdHJpY3QiO3JldHVybiJzdHJpbmciPT10eXBlb2YgYT9uZXcgRnVuY3Rpb24oInJldHVybiAiK2ErIjsiKSgpOmF9ZnVuY3Rpb24gZyhhKXthJiYodGhpcy5pZD1hLmlkLHRoaXMuY29uZmlndXJlKGEpKX1mdW5jdGlvbiBoKGEsYil7InVzZSBzdHJpY3QiO2lmKCFheGUudXRpbHMuaXNIaWRkZW4oYikpe3ZhciBjPWF4ZS51dGlscy5maW5kQnkoYSwibm9kZSIsYik7Y3x8YS5wdXNoKHtub2RlOmIsaW5jbHVkZTpbXSxleGNsdWRlOltdfSl9fWZ1bmN0aW9uIGkoYSxiLGMpeyJ1c2Ugc3RyaWN0IjthLmZyYW1lcz1hLmZyYW1lc3x8W107dmFyIGQsZSxmPWRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoYy5zaGlmdCgpKTthOmZvcih2YXIgZz0wLGg9Zi5sZW5ndGg7ZzxoO2crKyl7ZT1mW2ddO2Zvcih2YXIgaT0wLGo9YS5mcmFtZXMubGVuZ3RoO2k8ajtpKyspaWYoYS5mcmFtZXNbaV0ubm9kZT09PWUpe2EuZnJhbWVzW2ldW2JdLnB1c2goYyk7YnJlYWsgYX1kPXtub2RlOmUsaW5jbHVkZTpbXSxleGNsdWRlOltdfSxjJiZkW2JdLnB1c2goYyksYS5mcmFtZXMucHVzaChkKX19ZnVuY3Rpb24gaihhKXsidXNlIHN0cmljdCI7aWYoYSYmIm9iamVjdCI9PT0oInVuZGVmaW5lZCI9PXR5cGVvZiBhPyJ1bmRlZmluZWQiOlgoYSkpfHxhIGluc3RhbmNlb2YgTm9kZUxpc3Qpe2lmKGEgaW5zdGFuY2VvZiBOb2RlKXJldHVybntpbmNsdWRlOlthXSxleGNsdWRlOltdfTtpZihhLmhhc093blByb3BlcnR5KCJpbmNsdWRlIil8fGEuaGFzT3duUHJvcGVydHkoImV4Y2x1ZGUiKSlyZXR1cm57aW5jbHVkZTphLmluY2x1ZGV8fFtkb2N1bWVudF0sZXhjbHVkZTphLmV4Y2x1ZGV8fFtdfTtpZihhLmxlbmd0aD09PSthLmxlbmd0aClyZXR1cm57aW5jbHVkZTphLGV4Y2x1ZGU6W119fXJldHVybiJzdHJpbmciPT10eXBlb2YgYT97aW5jbHVkZTpbYV0sZXhjbHVkZTpbXX06e2luY2x1ZGU6W2RvY3VtZW50XSxleGNsdWRlOltdfX1mdW5jdGlvbiBrKGEsYil7InVzZSBzdHJpY3QiO2Zvcih2YXIgYyxkPVtdLGU9MCxmPWFbYl0ubGVuZ3RoO2U8ZjtlKyspe2lmKGM9YVtiXVtlXSwic3RyaW5nIj09dHlwZW9mIGMpe2Q9ZC5jb25jYXQoYXhlLnV0aWxzLnRvQXJyYXkoZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbChjKSkpO2JyZWFrfSFjfHwhYy5sZW5ndGh8fGMgaW5zdGFuY2VvZiBOb2RlP2QucHVzaChjKTpjLmxlbmd0aD4xP2koYSxiLGMpOmQ9ZC5jb25jYXQoYXhlLnV0aWxzLnRvQXJyYXkoZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbChjWzBdKSkpfXJldHVybiBkLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4gYX0pfWZ1bmN0aW9uIGwoYSl7InVzZSBzdHJpY3QiO2lmKDA9PT1hLmluY2x1ZGUubGVuZ3RoKXtpZigwPT09YS5mcmFtZXMubGVuZ3RoKXt2YXIgYj1heGUudXRpbHMucmVzcG9uZGFibGUuaXNJbkZyYW1lKCk/ImZyYW1lIjoicGFnZSI7cmV0dXJuIG5ldyBFcnJvcigiTm8gZWxlbWVudHMgZm91bmQgZm9yIGluY2x1ZGUgaW4gIitiKyIgQ29udGV4dCIpfWEuZnJhbWVzLmZvckVhY2goZnVuY3Rpb24oYSxiKXtpZigwPT09YS5pbmNsdWRlLmxlbmd0aClyZXR1cm4gbmV3IEVycm9yKCJObyBlbGVtZW50cyBmb3VuZCBmb3IgaW5jbHVkZSBpbiBDb250ZXh0IG9mIGZyYW1lICIrYil9KX19ZnVuY3Rpb24gbShhKXsidXNlIHN0cmljdCI7dmFyIGI9dGhpczt0aGlzLmZyYW1lcz1bXSx0aGlzLmluaXRpYXRvcj0hYXx8ImJvb2xlYW4iIT10eXBlb2YgYS5pbml0aWF0b3J8fGEuaW5pdGlhdG9yLHRoaXMucGFnZT0hMSxhPWooYSksdGhpcy5leGNsdWRlPWEuZXhjbHVkZSx0aGlzLmluY2x1ZGU9YS5pbmNsdWRlLHRoaXMuaW5jbHVkZT1rKHRoaXMsImluY2x1ZGUiKSx0aGlzLmV4Y2x1ZGU9ayh0aGlzLCJleGNsdWRlIiksYXhlLnV0aWxzLnNlbGVjdCgiZnJhbWUsIGlmcmFtZSIsdGhpcykuZm9yRWFjaChmdW5jdGlvbihhKXtWKGEsYikmJmgoYi5mcmFtZXMsYSl9KSwxPT09dGhpcy5pbmNsdWRlLmxlbmd0aCYmdGhpcy5pbmNsdWRlWzBdPT09ZG9jdW1lbnQmJih0aGlzLnBhZ2U9ITApO3ZhciBjPWwodGhpcyk7aWYoYyBpbnN0YW5jZW9mIEVycm9yKXRocm93IGN9ZnVuY3Rpb24gbihhKXsidXNlIHN0cmljdCI7dGhpcy5pZD1hLmlkLHRoaXMucmVzdWx0PWF4ZS5jb25zdGFudHMuTkEsdGhpcy5wYWdlTGV2ZWw9YS5wYWdlTGV2ZWwsdGhpcy5pbXBhY3Q9bnVsbCx0aGlzLm5vZGVzPVtdfWZ1bmN0aW9uIG8oYSxiKXsidXNlIHN0cmljdCI7dGhpcy5fYXVkaXQ9Yix0aGlzLmlkPWEuaWQsdGhpcy5zZWxlY3Rvcj1hLnNlbGVjdG9yfHwiKiIsdGhpcy5leGNsdWRlSGlkZGVuPSJib29sZWFuIiE9dHlwZW9mIGEuZXhjbHVkZUhpZGRlbnx8YS5leGNsdWRlSGlkZGVuLHRoaXMuZW5hYmxlZD0iYm9vbGVhbiIhPXR5cGVvZiBhLmVuYWJsZWR8fGEuZW5hYmxlZCx0aGlzLnBhZ2VMZXZlbD0iYm9vbGVhbiI9PXR5cGVvZiBhLnBhZ2VMZXZlbCYmYS5wYWdlTGV2ZWwsdGhpcy5hbnk9YS5hbnl8fFtdLHRoaXMuYWxsPWEuYWxsfHxbXSx0aGlzLm5vbmU9YS5ub25lfHxbXSx0aGlzLnRhZ3M9YS50YWdzfHxbXSxhLm1hdGNoZXMmJih0aGlzLm1hdGNoZXM9ZihhLm1hdGNoZXMpKX1mdW5jdGlvbiBwKGEpeyJ1c2Ugc3RyaWN0IjtyZXR1cm4gYXhlLnV0aWxzLmdldEFsbENoZWNrcyhhKS5tYXAoZnVuY3Rpb24oYil7dmFyIGM9YS5fYXVkaXQuY2hlY2tzW2IuaWR8fGJdO3JldHVybiBjJiYiZnVuY3Rpb24iPT10eXBlb2YgYy5hZnRlcj9jOm51bGx9KS5maWx0ZXIoQm9vbGVhbil9ZnVuY3Rpb24gcShhLGIpeyJ1c2Ugc3RyaWN0Ijt2YXIgYz1bXTtyZXR1cm4gYS5mb3JFYWNoKGZ1bmN0aW9uKGEpe3ZhciBkPWF4ZS51dGlscy5nZXRBbGxDaGVja3MoYSk7ZC5mb3JFYWNoKGZ1bmN0aW9uKGEpe2EuaWQ9PT1iJiZjLnB1c2goYSl9KX0pLGN9ZnVuY3Rpb24gcihhKXsidXNlIHN0cmljdCI7cmV0dXJuIGEuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBhLmZpbHRlcmVkIT09ITB9KX1mdW5jdGlvbiBzKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1bImFueSIsImFsbCIsIm5vbmUiXSxjPWEubm9kZXMuZmlsdGVyKGZ1bmN0aW9uKGEpe3ZhciBjPTA7cmV0dXJuIGIuZm9yRWFjaChmdW5jdGlvbihiKXthW2JdPXIoYVtiXSksYys9YVtiXS5sZW5ndGh9KSxjPjB9KTtyZXR1cm4gYS5wYWdlTGV2ZWwmJmMubGVuZ3RoJiYoYz1bYy5yZWR1Y2UoZnVuY3Rpb24oYSxjKXtpZihhKXJldHVybiBiLmZvckVhY2goZnVuY3Rpb24oYil7YVtiXS5wdXNoLmFwcGx5KGFbYl0sY1tiXSl9KSxhfSldKSxjfWZ1bmN0aW9uIHQoYSxiKXsidXNlIHN0cmljdCI7aWYoIWF4ZS5fYXVkaXQpdGhyb3cgbmV3IEVycm9yKCJObyBhdWRpdCBjb25maWd1cmVkIik7dmFyIGM9YXhlLnV0aWxzLnF1ZXVlKCksZD1bXTtPYmplY3Qua2V5cyhheGUucGx1Z2lucykuZm9yRWFjaChmdW5jdGlvbihhKXtjLmRlZmVyKGZ1bmN0aW9uKGIpe3ZhciBjPWZ1bmN0aW9uKGEpe2QucHVzaChhKSxiKCl9O3RyeXtheGUucGx1Z2luc1thXS5jbGVhbnVwKGIsYyl9Y2F0Y2goZSl7YyhlKX19KX0pLGF4ZS51dGlscy50b0FycmF5KGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoImZyYW1lLCBpZnJhbWUiKSkuZm9yRWFjaChmdW5jdGlvbihhKXtjLmRlZmVyKGZ1bmN0aW9uKGIsYyl7cmV0dXJuIGF4ZS51dGlscy5zZW5kQ29tbWFuZFRvRnJhbWUoYSx7Y29tbWFuZDoiY2xlYW51cC1wbHVnaW4ifSxiLGMpfSl9KSxjLnRoZW4oZnVuY3Rpb24oYyl7MD09PWQubGVuZ3RoP2EoYyk6YihkKX0pWyJjYXRjaCJdKGIpfWZ1bmN0aW9uIHUoYSl7InVzZSBzdHJpY3QiO3ZhciBiO2lmKGI9YXhlLl9hdWRpdCwhYil0aHJvdyBuZXcgRXJyb3IoIk5vIGF1ZGl0IGNvbmZpZ3VyZWQiKTthLnJlcG9ydGVyJiYoImZ1bmN0aW9uIj09dHlwZW9mIGEucmVwb3J0ZXJ8fCRbYS5yZXBvcnRlcl0pJiYoYi5yZXBvcnRlcj1hLnJlcG9ydGVyKSxhLmNoZWNrcyYmYS5jaGVja3MuZm9yRWFjaChmdW5jdGlvbihhKXtiLmFkZENoZWNrKGEpfSksYS5ydWxlcyYmYS5ydWxlcy5mb3JFYWNoKGZ1bmN0aW9uKGEpe2IuYWRkUnVsZShhKX0pLCJ1bmRlZmluZWQiIT10eXBlb2YgYS5icmFuZGluZz9iLnNldEJyYW5kaW5nKGEuYnJhbmRpbmcpOmIuX2NvbnN0cnVjdEhlbHBVcmxzKCksYS50YWdFeGNsdWRlJiYoYi50YWdFeGNsdWRlPWEudGFnRXhjbHVkZSl9ZnVuY3Rpb24gdihhLGIsYyl7InVzZSBzdHJpY3QiO3ZhciBkPWMsZT1mdW5jdGlvbihhKXthIGluc3RhbmNlb2YgRXJyb3I9PSExJiYoYT1uZXcgRXJyb3IoYSkpLGMoYSl9LGY9YSYmYS5jb250ZXh0fHx7fTtmLmluY2x1ZGUmJiFmLmluY2x1ZGUubGVuZ3RoJiYoZi5pbmNsdWRlPVtkb2N1bWVudF0pO3ZhciBnPWEmJmEub3B0aW9uc3x8e307c3dpdGNoKGEuY29tbWFuZCl7Y2FzZSJydWxlcyI6cmV0dXJuIHkoZixnLGQsZSk7Y2FzZSJjbGVhbnVwLXBsdWdpbiI6cmV0dXJuIHQoZCxlKTtkZWZhdWx0OmlmKGF4ZS5fYXVkaXQmJmF4ZS5fYXVkaXQuY29tbWFuZHMmJmF4ZS5fYXVkaXQuY29tbWFuZHNbYS5jb21tYW5kXSlyZXR1cm4gYXhlLl9hdWRpdC5jb21tYW5kc1thLmNvbW1hbmRdKGEsYyl9fWZ1bmN0aW9uIHcoYSl7InVzZSBzdHJpY3QiO3RoaXMuX3J1bj1hLnJ1bix0aGlzLl9jb2xsZWN0PWEuY29sbGVjdCx0aGlzLl9yZWdpc3RyeT17fSxhLmNvbW1hbmRzLmZvckVhY2goZnVuY3Rpb24oYSl7YXhlLl9hdWRpdC5yZWdpc3RlckNvbW1hbmQoYSl9KX1mdW5jdGlvbiB4KCl7InVzZSBzdHJpY3QiO3ZhciBhPWF4ZS5fYXVkaXQ7aWYoIWEpdGhyb3cgbmV3IEVycm9yKCJObyBhdWRpdCBjb25maWd1cmVkIik7YS5yZXNldFJ1bGVzQW5kQ2hlY2tzKCl9ZnVuY3Rpb24geShhLGIsYyxkKXsidXNlIHN0cmljdCI7YT1uZXcgbShhKTt2YXIgZT1heGUudXRpbHMucXVldWUoKSxmPWF4ZS5fYXVkaXQ7YS5mcmFtZXMubGVuZ3RoJiZlLmRlZmVyKGZ1bmN0aW9uKGMsZCl7YXhlLnV0aWxzLmNvbGxlY3RSZXN1bHRzRnJvbUZyYW1lcyhhLGIsInJ1bGVzIixudWxsLGMsZCl9KSxlLmRlZmVyKGZ1bmN0aW9uKGMsZCl7Zi5ydW4oYSxiLGMsZCl9KSxlLnRoZW4oZnVuY3Rpb24oZSl7dHJ5e3ZhciBnPWF4ZS51dGlscy5tZXJnZVJlc3VsdHMoZS5tYXAoZnVuY3Rpb24oYSl7cmV0dXJue3Jlc3VsdHM6YX19KSk7YS5pbml0aWF0b3ImJihnPWYuYWZ0ZXIoZyxiKSxnLmZvckVhY2goYXhlLnV0aWxzLnB1Ymxpc2hNZXRhRGF0YSksZz1nLm1hcChheGUudXRpbHMuZmluYWxpemVSdWxlUmVzdWx0KSk7dHJ5e2MoZyl9Y2F0Y2goaCl7YXhlLmxvZyhoKX19Y2F0Y2goaCl7ZChoKX19KVsiY2F0Y2giXShkKX1mdW5jdGlvbiB6KGEpeyJ1c2Ugc3RyaWN0Ijtzd2l0Y2goITApe2Nhc2Uic3RyaW5nIj09dHlwZW9mIGE6Y2FzZSBBcnJheS5pc0FycmF5KGEpOmNhc2UgTm9kZSYmYSBpbnN0YW5jZW9mIE5vZGU6Y2FzZSBOb2RlTGlzdCYmYSBpbnN0YW5jZW9mIE5vZGVMaXN0OnJldHVybiEwO2Nhc2Uib2JqZWN0IiE9PSgidW5kZWZpbmVkIj09dHlwZW9mIGE/InVuZGVmaW5lZCI6WChhKSk6cmV0dXJuITE7Y2FzZSB2b2lkIDAhPT1hLmluY2x1ZGU6Y2FzZSB2b2lkIDAhPT1hLmV4Y2x1ZGU6Y2FzZSJudW1iZXIiPT10eXBlb2YgYS5sZW5ndGg6cmV0dXJuITA7ZGVmYXVsdDpyZXR1cm4hMX19ZnVuY3Rpb24gQShhLGIsYyl7InVzZSBzdHJpY3QiO3ZhciBkPW5ldyBUeXBlRXJyb3IoImF4ZS5ydW4gYXJndW1lbnRzIGFyZSBpbnZhbGlkIik7aWYoIXooYSkpe2lmKHZvaWQgMCE9PWMpdGhyb3cgZDtjPWIsYj1hLGE9ZG9jdW1lbnR9aWYoIm9iamVjdCIhPT0oInVuZGVmaW5lZCI9PXR5cGVvZiBiPyJ1bmRlZmluZWQiOlgoYikpKXtpZih2b2lkIDAhPT1jKXRocm93IGQ7Yz1iLGI9e319aWYoImZ1bmN0aW9uIiE9dHlwZW9mIGMmJnZvaWQgMCE9PWMpdGhyb3cgZDtyZXR1cm57Y29udGV4dDphLG9wdGlvbnM6YixjYWxsYmFjazpjfHxffX1mdW5jdGlvbiBCKGEsYil7InVzZSBzdHJpY3QiO1siYW55IiwiYWxsIiwibm9uZSJdLmZvckVhY2goZnVuY3Rpb24oYyl7QXJyYXkuaXNBcnJheShhW2NdKSYmYVtjXS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuIEFycmF5LmlzQXJyYXkoYS5yZWxhdGVkTm9kZXMpfSkuZm9yRWFjaChmdW5jdGlvbihhKXthLnJlbGF0ZWROb2Rlcz1hLnJlbGF0ZWROb2Rlcy5tYXAoZnVuY3Rpb24oYSl7dmFyIGM9e2h0bWw6YS5zb3VyY2UsdGFyZ2V0OmEuc2VsZWN0b3J9O3JldHVybiBiJiYoYy54cGF0aD1hLnhwYXRoKSxjfSl9KX0pfWZ1bmN0aW9uIEMoYSxiKXtyZXR1cm4gY2EucmVkdWNlKGZ1bmN0aW9uKGMsZCl7cmV0dXJuIGNbZF09KGFbZF18fFtdKS5tYXAoZnVuY3Rpb24oYSl7cmV0dXJuIGIoYSxkKX0pLGN9LHt9KX1mdW5jdGlvbiBEKGEsYixjKXt2YXIgZD1PYmplY3QuYXNzaWduKHt9LGIpO2Qubm9kZXM9KGRbY118fFtdKS5jb25jYXQoKSxheGUuY29uc3RhbnRzLnJlc3VsdEdyb3Vwcy5mb3JFYWNoKGZ1bmN0aW9uKGEpe2RlbGV0ZSBkW2FdfSksYVtjXS5wdXNoKGQpfWZ1bmN0aW9uIEUoYSxiLGMpeyJ1c2Ugc3RyaWN0Ijt2YXIgZD13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShhLG51bGwpLGU9ITE7cmV0dXJuISFkJiYoYi5mb3JFYWNoKGZ1bmN0aW9uKGEpe2QuZ2V0UHJvcGVydHlWYWx1ZShhLnByb3BlcnR5KT09PWEudmFsdWUmJihlPSEwKX0pLCEhZXx8IShhLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCk9PT1jLnRvVXBwZXJDYXNlKCl8fCFhLnBhcmVudE5vZGUpJiZFKGEucGFyZW50Tm9kZSxiLGMpKX1mdW5jdGlvbiBGKGEsYil7InVzZSBzdHJpY3QiO3JldHVybiBuZXcgRXJyb3IoYSsiOiAiK2F4ZS51dGlscy5nZXRTZWxlY3RvcihiKSl9ZnVuY3Rpb24gRyhhLGIsYyxkLGUsZil7InVzZSBzdHJpY3QiO3ZhciBnPWF4ZS51dGlscy5xdWV1ZSgpLGg9YS5mcmFtZXM7aC5mb3JFYWNoKGZ1bmN0aW9uKGUpe3ZhciBmPXtvcHRpb25zOmIsY29tbWFuZDpjLHBhcmFtZXRlcjpkLGNvbnRleHQ6e2luaXRpYXRvcjohMSxwYWdlOmEucGFnZSxpbmNsdWRlOmUuaW5jbHVkZXx8W10sZXhjbHVkZTplLmV4Y2x1ZGV8fFtdfX07Zy5kZWZlcihmdW5jdGlvbihhLGIpe3ZhciBjPWUubm9kZTtheGUudXRpbHMuc2VuZENvbW1hbmRUb0ZyYW1lKGMsZixmdW5jdGlvbihiKXtyZXR1cm4gYj9hKHtyZXN1bHRzOmIsZnJhbWVFbGVtZW50OmMsZnJhbWU6YXhlLnV0aWxzLmdldFNlbGVjdG9yKGMpfSk6dm9pZCBhKG51bGwpfSxiKX0pfSksZy50aGVuKGZ1bmN0aW9uKGEpe2UoYXhlLnV0aWxzLm1lcmdlUmVzdWx0cyhhKSl9KVsiY2F0Y2giXShmKX1mdW5jdGlvbiBIKGEsYil7InVzZSBzdHJpY3QiO2lmKGI9Ynx8MzAwLGEubGVuZ3RoPmIpe3ZhciBjPWEuaW5kZXhPZigiPiIpO2E9YS5zdWJzdHJpbmcoMCxjKzEpfXJldHVybiBhfWZ1bmN0aW9uIEkoYSl7InVzZSBzdHJpY3QiO3ZhciBiPWEub3V0ZXJIVE1MO3JldHVybiBifHwiZnVuY3Rpb24iIT10eXBlb2YgWE1MU2VyaWFsaXplcnx8KGI9KG5ldyBYTUxTZXJpYWxpemVyKS5zZXJpYWxpemVUb1N0cmluZyhhKSksSChifHwiIil9ZnVuY3Rpb24gSihhLGIpeyJ1c2Ugc3RyaWN0IjtiPWJ8fHt9LHRoaXMuc2VsZWN0b3I9Yi5zZWxlY3Rvcnx8W2F4ZS51dGlscy5nZXRTZWxlY3RvcihhKV0sdGhpcy54cGF0aD1iLnhwYXRofHxbYXhlLnV0aWxzLmdldFhwYXRoKGEpXSx0aGlzLnNvdXJjZT12b2lkIDAhPT1iLnNvdXJjZT9iLnNvdXJjZTpJKGEpLHRoaXMuZWxlbWVudD1hfWZ1bmN0aW9uIEsoYSl7InVzZSBzdHJpY3QiO3ZhciBiPTEsYz1hLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCk7Zm9yKGE9YS5wcmV2aW91c0VsZW1lbnRTaWJsaW5nO2E7KWEubm9kZU5hbWUudG9VcHBlckNhc2UoKT09PWMmJmIrKyxhPWEucHJldmlvdXNFbGVtZW50U2libGluZztyZXR1cm4gYn1mdW5jdGlvbiBMKGEsYil7InVzZSBzdHJpY3QiO3ZhciBjLGQsZT1hLnBhcmVudE5vZGUuY2hpbGRyZW47aWYoIWUpcmV0dXJuITE7dmFyIGY9ZS5sZW5ndGg7Zm9yKGM9MDtjPGY7YysrKWlmKGQ9ZVtjXSxkIT09YSYmYXhlLnV0aWxzLm1hdGNoZXNTZWxlY3RvcihkLGIpKXJldHVybiEwO3JldHVybiExfWZ1bmN0aW9uIE0oYSxiKXt2YXIgYyxkO2lmKCFhKXJldHVybltdO2lmKCFiJiY5PT09YS5ub2RlVHlwZSlyZXR1cm4gYj1be3N0cjoiaHRtbCJ9XTtpZihiPWJ8fFtdLGEucGFyZW50Tm9kZSYmYS5wYXJlbnROb2RlIT09YSYmKGI9TShhLnBhcmVudE5vZGUsYikpLGEucHJldmlvdXNTaWJsaW5nKXtkPTEsYz1hLnByZXZpb3VzU2libGluZztkbyAxPT09Yy5ub2RlVHlwZSYmYy5ub2RlTmFtZT09PWEubm9kZU5hbWUmJmQrKyxjPWMucHJldmlvdXNTaWJsaW5nO3doaWxlKGMpOzE9PT1kJiYoZD1udWxsKX1lbHNlIGlmKGEubmV4dFNpYmxpbmcpe2M9YS5uZXh0U2libGluZztkbyAxPT09Yy5ub2RlVHlwZSYmYy5ub2RlTmFtZT09PWEubm9kZU5hbWU/KGQ9MSxjPW51bGwpOihkPW51bGwsYz1jLnByZXZpb3VzU2libGluZyk7d2hpbGUoYyl9aWYoMT09PWEubm9kZVR5cGUpe3ZhciBlPXt9O2Uuc3RyPWEubm9kZU5hbWUudG9Mb3dlckNhc2UoKSxhLmdldEF0dHJpYnV0ZSYmYS5nZXRBdHRyaWJ1dGUoImlkIikmJjE9PT1hLm93bmVyRG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgiIyIrYXhlLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEuaWQpKS5sZW5ndGgmJihlLmlkPWEuZ2V0QXR0cmlidXRlKCJpZCIpKSxkPjEmJihlLmNvdW50PWQpLGIucHVzaChlKX1yZXR1cm4gYn1mdW5jdGlvbiBOKGEpe3JldHVybiBhLnJlZHVjZShmdW5jdGlvbihhLGIpe3JldHVybiBiLmlkPyIvIitiLnN0cisiW0BpZD0nIitiLmlkKyInXSI6YSsoIi8iK2Iuc3RyKSsoYi5jb3VudD4wPyJbIitiLmNvdW50KyJdIjoiIil9LCIiKX1mdW5jdGlvbiBPKGEpeyJ1c2Ugc3RyaWN0IjtpZihkYSYmZGEucGFyZW50Tm9kZSlyZXR1cm4gdm9pZCAwPT09ZGEuc3R5bGVTaGVldD9kYS5hcHBlbmRDaGlsZChkb2N1bWVudC5jcmVhdGVUZXh0Tm9kZShhKSk6ZGEuc3R5bGVTaGVldC5jc3NUZXh0Kz1hLGRhO2lmKGEpe3ZhciBiPWRvY3VtZW50LmhlYWR8fGRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCJoZWFkIilbMF07cmV0dXJuIGRhPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInN0eWxlIiksZGEudHlwZT0idGV4dC9jc3MiLHZvaWQgMD09PWRhLnN0eWxlU2hlZXQ/ZGEuYXBwZW5kQ2hpbGQoZG9jdW1lbnQuY3JlYXRlVGV4dE5vZGUoYSkpOmRhLnN0eWxlU2hlZXQuY3NzVGV4dD1hLGIuYXBwZW5kQ2hpbGQoZGEpLGRhfX1mdW5jdGlvbiBQKGEsYixjKXsidXNlIHN0cmljdCI7dmFyIGQ9YXhlLnV0aWxzLmdldFhwYXRoKGIpLGU9e2VsZW1lbnQ6YixzZWxlY3RvcjpjLHhwYXRoOmR9O2EuZm9yRWFjaChmdW5jdGlvbihhKXthLm5vZGU9YXhlLnV0aWxzLkRxRWxlbWVudC5mcm9tRnJhbWUoYS5ub2RlLGUpO3ZhciBiPWF4ZS51dGlscy5nZXRBbGxDaGVja3MoYSk7Yi5sZW5ndGgmJmIuZm9yRWFjaChmdW5jdGlvbihhKXthLnJlbGF0ZWROb2Rlcz1hLnJlbGF0ZWROb2Rlcy5tYXAoZnVuY3Rpb24oYSl7cmV0dXJuIGF4ZS51dGlscy5EcUVsZW1lbnQuZnJvbUZyYW1lKGEsZSl9KX0pfSl9ZnVuY3Rpb24gUShhLGIpeyJ1c2Ugc3RyaWN0Ijtmb3IodmFyIGMsZCxlPWJbMF0ubm9kZSxmPTAsZz1hLmxlbmd0aDtmPGc7ZisrKWlmKGQ9YVtmXS5ub2RlLGM9YXhlLnV0aWxzLm5vZGVTb3J0ZXIoZC5lbGVtZW50LGUuZWxlbWVudCksYz4wfHwwPT09YyYmZS5zZWxlY3Rvci5sZW5ndGg8ZC5zZWxlY3Rvci5sZW5ndGgpcmV0dXJuIHZvaWQgYS5zcGxpY2UuYXBwbHkoYSxbZiwwXS5jb25jYXQoYikpO2EucHVzaC5hcHBseShhLGIpfWZ1bmN0aW9uIFIoYSl7InVzZSBzdHJpY3QiO3JldHVybiBhJiZhLnJlc3VsdHM/QXJyYXkuaXNBcnJheShhLnJlc3VsdHMpP2EucmVzdWx0cy5sZW5ndGg/YS5yZXN1bHRzOm51bGw6W2EucmVzdWx0c106bnVsbH1mdW5jdGlvbiBTKGEsYil7InVzZSBzdHJpY3QiO3JldHVybiBmdW5jdGlvbihjKXt2YXIgZD1hW2MuaWRdfHx7fSxlPWQubWVzc2FnZXN8fHt9LGY9T2JqZWN0LmFzc2lnbih7fSxkKTtkZWxldGUgZi5tZXNzYWdlcyxmLm1lc3NhZ2U9Yy5yZXN1bHQ9PT1iP2UucGFzczplLmZhaWwsYXhlLnV0aWxzLmV4dGVuZE1ldGFEYXRhKGMsZil9fWZ1bmN0aW9uIFQoYSxiKXsidXNlIHN0cmljdCI7dmFyIGMsZCxlLGY9YXhlLl9hdWRpdCYmYXhlLl9hdWRpdC50YWdFeGNsdWRlP2F4ZS5fYXVkaXQudGFnRXhjbHVkZTpbXTtyZXR1cm4gYi5pbmNsdWRlfHxiLmV4Y2x1ZGU/KGM9Yi5pbmNsdWRlfHxbXSxjPUFycmF5LmlzQXJyYXkoYyk/YzpbY10sZD1iLmV4Y2x1ZGV8fFtdLGQ9QXJyYXkuaXNBcnJheShkKT9kOltkXSxkPWQuY29uY2F0KGYuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBjLmluZGV4T2YoYSk9PT0tMX0pKSk6KGM9QXJyYXkuaXNBcnJheShiKT9iOltiXSxkPWYuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBjLmluZGV4T2YoYSk9PT0tMX0pKSxlPWMuc29tZShmdW5jdGlvbihiKXtyZXR1cm4gYS50YWdzLmluZGV4T2YoYikhPT0tMX0pLCEhKGV8fDA9PT1jLmxlbmd0aCYmYS5lbmFibGVkIT09ITEpJiZkLmV2ZXJ5KGZ1bmN0aW9uKGIpe3JldHVybiBhLnRhZ3MuaW5kZXhPZihiKT09PS0xfSl9ZnVuY3Rpb24gVShhKXsidXNlIHN0cmljdCI7cmV0dXJuIGEuc29ydChmdW5jdGlvbihhLGIpe3JldHVybiBheGUudXRpbHMuY29udGFpbnMoYSxiKT8xOi0xfSlbMF19ZnVuY3Rpb24gVihhLGIpeyJ1c2Ugc3RyaWN0Ijt2YXIgYz1iLmluY2x1ZGUmJlUoYi5pbmNsdWRlLmZpbHRlcihmdW5jdGlvbihiKXtyZXR1cm4gYXhlLnV0aWxzLmNvbnRhaW5zKGIsYSl9KSksZD1iLmV4Y2x1ZGUmJlUoYi5leGNsdWRlLmZpbHRlcihmdW5jdGlvbihiKXtyZXR1cm4gYXhlLnV0aWxzLmNvbnRhaW5zKGIsYSl9KSk7cmV0dXJuISEoIWQmJmN8fGQmJmF4ZS51dGlscy5jb250YWlucyhkLGMpKX1mdW5jdGlvbiBXKGEsYixjKXsidXNlIHN0cmljdCI7Zm9yKHZhciBkPTAsZT1iLmxlbmd0aDtkPGU7ZCsrKWEuaW5kZXhPZihiW2RdKT09PS0xJiZWKGJbZF0sYykmJmEucHVzaChiW2RdKX12YXIgZG9jdW1lbnQ9d2luZG93LmRvY3VtZW50LFg9ImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmInN5bWJvbCI9PXR5cGVvZiBTeW1ib2wuaXRlcmF0b3I/ZnVuY3Rpb24oYSl7cmV0dXJuIHR5cGVvZiBhfTpmdW5jdGlvbihhKXtyZXR1cm4gYSYmImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmYS5jb25zdHJ1Y3Rvcj09PVN5bWJvbCYmYSE9PVN5bWJvbC5wcm90b3R5cGU/InN5bWJvbCI6dHlwZW9mIGF9LGF4ZT1heGV8fHt9O2F4ZS52ZXJzaW9uPSIyLjEuNyIsImZ1bmN0aW9uIj09dHlwZW9mIGRlZmluZSYmZGVmaW5lLmFtZCYmZGVmaW5lKFtdLGZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO3JldHVybiBheGV9KSwib2JqZWN0Ij09PSgidW5kZWZpbmVkIj09dHlwZW9mIG1vZHVsZT8idW5kZWZpbmVkIjpYKG1vZHVsZSkpJiZtb2R1bGUuZXhwb3J0cyYmImZ1bmN0aW9uIj09dHlwZW9mIGEudG9TdHJpbmcmJihheGUuc291cmNlPSIoIithLnRvU3RyaW5nKCkrIikodGhpcywgdGhpcy5kb2N1bWVudCk7Iixtb2R1bGUuZXhwb3J0cz1heGUpLCJmdW5jdGlvbiI9PXR5cGVvZiB3aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZSYmKHdpbmRvdy5heGU9YXhlKTt2YXIgY29tbW9ucyx1dGlscz1heGUudXRpbHM9e30sWT17fSxYPSJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJiJzeW1ib2wiPT10eXBlb2YgU3ltYm9sLml0ZXJhdG9yP2Z1bmN0aW9uKGEpe3JldHVybiB0eXBlb2YgYX06ZnVuY3Rpb24oYSl7cmV0dXJuIGEmJiJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJmEuY29uc3RydWN0b3I9PT1TeW1ib2wmJmEhPT1TeW1ib2wucHJvdG90eXBlPyJzeW1ib2wiOnR5cGVvZiBhfTtkLnByb3RvdHlwZS5faW5pdD1mdW5jdGlvbigpe3ZhciBhPWIodGhpcy5kZWZhdWx0Q29uZmlnKTtheGUuY29tbW9ucz1jb21tb25zPWEuY29tbW9ucyx0aGlzLnJlcG9ydGVyPWEucmVwb3J0ZXIsdGhpcy5jb21tYW5kcz17fSx0aGlzLnJ1bGVzPVtdLHRoaXMuY2hlY2tzPXt9LGMoYS5ydWxlcyx0aGlzLCJhZGRSdWxlIiksYyhhLmNoZWNrcyx0aGlzLCJhZGRDaGVjayIpLHRoaXMuZGF0YT17fSx0aGlzLmRhdGEuY2hlY2tzPWEuZGF0YSYmYS5kYXRhLmNoZWNrc3x8e30sdGhpcy5kYXRhLnJ1bGVzPWEuZGF0YSYmYS5kYXRhLnJ1bGVzfHx7fSx0aGlzLmRhdGEuZmFpbHVyZVN1bW1hcmllcz1hLmRhdGEmJmEuZGF0YS5mYWlsdXJlU3VtbWFyaWVzfHx7fSx0aGlzLl9jb25zdHJ1Y3RIZWxwVXJscygpfSxkLnByb3RvdHlwZS5yZWdpc3RlckNvbW1hbmQ9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3RoaXMuY29tbWFuZHNbYS5pZF09YS5jYWxsYmFja30sZC5wcm90b3R5cGUuYWRkUnVsZT1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7YS5tZXRhZGF0YSYmKHRoaXMuZGF0YS5ydWxlc1thLmlkXT1hLm1ldGFkYXRhKTt2YXIgYj10aGlzLmdldFJ1bGUoYS5pZCk7Yj9iLmNvbmZpZ3VyZShhKTp0aGlzLnJ1bGVzLnB1c2gobmV3IG8oYSx0aGlzKSl9LGQucHJvdG90eXBlLmFkZENoZWNrPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1hLm1ldGFkYXRhOyJvYmplY3QiPT09KCJ1bmRlZmluZWQiPT10eXBlb2YgYj8idW5kZWZpbmVkIjpYKGIpKSYmKHRoaXMuZGF0YS5jaGVja3NbYS5pZF09Yiwib2JqZWN0Ij09PVgoYi5tZXNzYWdlcykmJk9iamVjdC5rZXlzKGIubWVzc2FnZXMpLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4gYi5tZXNzYWdlcy5oYXNPd25Qcm9wZXJ0eShhKSYmInN0cmluZyI9PXR5cGVvZiBiLm1lc3NhZ2VzW2FdfSkuZm9yRWFjaChmdW5jdGlvbihhKXswPT09Yi5tZXNzYWdlc1thXS5pbmRleE9mKCJmdW5jdGlvbiIpJiYoYi5tZXNzYWdlc1thXT1uZXcgRnVuY3Rpb24oInJldHVybiAiK2IubWVzc2FnZXNbYV0rIjsiKSgpKX0pKSx0aGlzLmNoZWNrc1thLmlkXT90aGlzLmNoZWNrc1thLmlkXS5jb25maWd1cmUoYSk6dGhpcy5jaGVja3NbYS5pZF09bmV3IGcoYSl9LGQucHJvdG90eXBlLnJ1bj1mdW5jdGlvbihhLGIsYyxkKXsidXNlIHN0cmljdCI7dGhpcy52YWxpZGF0ZU9wdGlvbnMoYik7dmFyIGU9YXhlLnV0aWxzLnF1ZXVlKCk7dGhpcy5ydWxlcy5mb3JFYWNoKGZ1bmN0aW9uKGMpe2F4ZS51dGlscy5ydWxlU2hvdWxkUnVuKGMsYSxiKSYmZS5kZWZlcihmdW5jdGlvbihkLGUpe2MucnVuKGEsYixkLGZ1bmN0aW9uKGEpe2lmKGIuZGVidWcpZShhKTtlbHNle3ZhciBmPU9iamVjdC5hc3NpZ24obmV3IG4oYykse3Jlc3VsdDpheGUuY29uc3RhbnRzLkNBTlRURUxMLGRlc2NyaXB0aW9uOiJBbiBlcnJvciBvY2N1cmVkIHdoaWxlIHJ1bm5pbmcgdGhpcyBydWxlIixtZXNzYWdlOmEubWVzc2FnZSxoZWxwOmEuc3RhY2t8fGEubWVzc2FnZSxlcnJvcjphfSk7ZChmKX19KX0pfSksZS50aGVuKGZ1bmN0aW9uKGEpe2MoYS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuISFhfSkpfSlbImNhdGNoIl0oZCl9LGQucHJvdG90eXBlLmFmdGVyPWZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO3ZhciBjPXRoaXMucnVsZXM7cmV0dXJuIGEubWFwKGZ1bmN0aW9uKGEpe3ZhciBkPWF4ZS51dGlscy5maW5kQnkoYywiaWQiLGEuaWQpO3JldHVybiBkLmFmdGVyKGEsYil9KX0sZC5wcm90b3R5cGUuZ2V0UnVsZT1mdW5jdGlvbihhKXtyZXR1cm4gdGhpcy5ydWxlcy5maW5kKGZ1bmN0aW9uKGIpe3JldHVybiBiLmlkPT09YX0pfSxkLnByb3RvdHlwZS52YWxpZGF0ZU9wdGlvbnM9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPXRoaXM7aWYoIm9iamVjdCI9PT1YKGEucnVuT25seSkpe3ZhciBjPWEucnVuT25seTtpZigicnVsZSI9PT1jLnR5cGUmJkFycmF5LmlzQXJyYXkoYy52YWx1ZSkpYy52YWx1ZS5mb3JFYWNoKGZ1bmN0aW9uKGEpe2lmKCFiLmdldFJ1bGUoYSkpdGhyb3cgbmV3IEVycm9yKCJ1bmtub3duIHJ1bGUgYCIrYSsiYCBpbiBvcHRpb25zLnJ1bk9ubHkiKX0pO2Vsc2UgaWYoQXJyYXkuaXNBcnJheShjLnZhbHVlKSYmYy52YWx1ZS5sZW5ndGg+MCl7dmFyIGQ9W10uY29uY2F0KGMudmFsdWUpO2lmKGIucnVsZXMuZm9yRWFjaChmdW5jdGlvbihhKXt2YXIgYixjLGU7aWYoZClmb3IoYz0wLGU9YS50YWdzLmxlbmd0aDtjPGU7YysrKWI9ZC5pbmRleE9mKGEudGFnc1tjXSksYiE9PS0xJiZkLnNwbGljZShiLDEpfSksMCE9PWQubGVuZ3RoKXRocm93IG5ldyBFcnJvcigiY291bGQgbm90IGZpbmQgdGFncyBgIitkLmpvaW4oImAsIGAiKSsiYCIpfX1yZXR1cm4ib2JqZWN0Ij09PVgoYS5ydWxlcykmJk9iamVjdC5rZXlzKGEucnVsZXMpLmZvckVhY2goZnVuY3Rpb24oYSl7aWYoIWIuZ2V0UnVsZShhKSl0aHJvdyBuZXcgRXJyb3IoInVua25vd24gcnVsZSBgIithKyJgIGluIG9wdGlvbnMucnVsZXMiKX0pLGF9LGQucHJvdG90eXBlLnNldEJyYW5kaW5nPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjthJiZhLmhhc093blByb3BlcnR5KCJicmFuZCIpJiZhLmJyYW5kJiYic3RyaW5nIj09dHlwZW9mIGEuYnJhbmQmJih0aGlzLmJyYW5kPWEuYnJhbmQpLGEmJmEuaGFzT3duUHJvcGVydHkoImFwcGxpY2F0aW9uIikmJmEuYXBwbGljYXRpb24mJiJzdHJpbmciPT10eXBlb2YgYS5hcHBsaWNhdGlvbiYmKHRoaXMuYXBwbGljYXRpb249YS5hcHBsaWNhdGlvbiksdGhpcy5fY29uc3RydWN0SGVscFVybHMoKX0sZC5wcm90b3R5cGUuX2NvbnN0cnVjdEhlbHBVcmxzPWZ1bmN0aW9uKCl7dmFyIGE9dGhpcyxiPWF4ZS52ZXJzaW9uLnN1YnN0cmluZygwLGF4ZS52ZXJzaW9uLmxhc3RJbmRleE9mKCIuIikpO3RoaXMucnVsZXMuZm9yRWFjaChmdW5jdGlvbihjKXthLmRhdGEucnVsZXNbYy5pZF09YS5kYXRhLnJ1bGVzW2MuaWRdfHx7fSxhLmRhdGEucnVsZXNbYy5pZF0uaGVscFVybD0iaHR0cHM6Ly9kZXF1ZXVuaXZlcnNpdHkuY29tL3J1bGVzLyIrYS5icmFuZCsiLyIrYisiLyIrYy5pZCsiP2FwcGxpY2F0aW9uPSIrYS5hcHBsaWNhdGlvbn0pfSxkLnByb3RvdHlwZS5yZXNldFJ1bGVzQW5kQ2hlY2tzPWZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO3RoaXMuX2luaXQoKX0sZy5wcm90b3R5cGUuZW5hYmxlZD0hMCxnLnByb3RvdHlwZS5ydW49ZnVuY3Rpb24oYSxiLGMsZCl7InVzZSBzdHJpY3QiO2I9Ynx8e307dmFyIGY9Yi5oYXNPd25Qcm9wZXJ0eSgiZW5hYmxlZCIpP2IuZW5hYmxlZDp0aGlzLmVuYWJsZWQsZz1iLm9wdGlvbnN8fHRoaXMub3B0aW9ucztpZihmKXt2YXIgaCxpPW5ldyBlKHRoaXMpLGo9YXhlLnV0aWxzLmNoZWNrSGVscGVyKGksYyxkKTt0cnl7aD10aGlzLmV2YWx1YXRlLmNhbGwoaixhLGcpfWNhdGNoKGspe3JldHVybiB2b2lkIGQoayl9ai5pc0FzeW5jfHwoaS5yZXN1bHQ9aCxzZXRUaW1lb3V0KGZ1bmN0aW9uKCl7YyhpKX0sMCkpfWVsc2UgYyhudWxsKX0sZy5wcm90b3R5cGUuY29uZmlndXJlPWZ1bmN0aW9uKGEpe3ZhciBiPXRoaXM7WyJvcHRpb25zIiwiZW5hYmxlZCJdLmZpbHRlcihmdW5jdGlvbihiKXtyZXR1cm4gYS5oYXNPd25Qcm9wZXJ0eShiKX0pLmZvckVhY2goZnVuY3Rpb24oYyl7cmV0dXJuIGJbY109YVtjXX0pLFsiZXZhbHVhdGUiLCJhZnRlciJdLmZpbHRlcihmdW5jdGlvbihiKXtyZXR1cm4gYS5oYXNPd25Qcm9wZXJ0eShiKX0pLmZvckVhY2goZnVuY3Rpb24oYyl7cmV0dXJuIGJbY109ZihhW2NdKX0pfTt2YXIgWD0iZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiYic3ltYm9sIj09dHlwZW9mIFN5bWJvbC5pdGVyYXRvcj9mdW5jdGlvbihhKXtyZXR1cm4gdHlwZW9mIGF9OmZ1bmN0aW9uKGEpe3JldHVybiBhJiYiZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiZhLmNvbnN0cnVjdG9yPT09U3ltYm9sJiZhIT09U3ltYm9sLnByb3RvdHlwZT8ic3ltYm9sIjp0eXBlb2YgYX07by5wcm90b3R5cGUubWF0Y2hlcz1mdW5jdGlvbigpeyJ1c2Ugc3RyaWN0IjtyZXR1cm4hMH0sby5wcm90b3R5cGUuZ2F0aGVyPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1heGUudXRpbHMuc2VsZWN0KHRoaXMuc2VsZWN0b3IsYSk7cmV0dXJuIHRoaXMuZXhjbHVkZUhpZGRlbj9iLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4hYXhlLnV0aWxzLmlzSGlkZGVuKGEpfSk6Yn0sby5wcm90b3R5cGUucnVuQ2hlY2tzPWZ1bmN0aW9uKGEsYixjLGQsZSl7InVzZSBzdHJpY3QiO3ZhciBmPXRoaXMsZz1heGUudXRpbHMucXVldWUoKTt0aGlzW2FdLmZvckVhY2goZnVuY3Rpb24oYSl7dmFyIGQ9Zi5fYXVkaXQuY2hlY2tzW2EuaWR8fGFdLGU9YXhlLnV0aWxzLmdldENoZWNrT3B0aW9uKGQsZi5pZCxjKTtnLmRlZmVyKGZ1bmN0aW9uKGEsYyl7ZC5ydW4oYixlLGEsYyl9KX0pLGcudGhlbihmdW5jdGlvbihiKXtiPWIuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBhfSksZCh7dHlwZTphLHJlc3VsdHM6Yn0pfSlbImNhdGNoIl0oZSl9LG8ucHJvdG90eXBlLnJ1bj1mdW5jdGlvbihhLGIsYyxkKXsidXNlIHN0cmljdCI7dmFyIGUsZj10aGlzLmdhdGhlcihhKSxnPWF4ZS51dGlscy5xdWV1ZSgpLGg9dGhpcztlPW5ldyBuKHRoaXMpLGYuZm9yRWFjaChmdW5jdGlvbihhKXtoLm1hdGNoZXMoYSkmJmcuZGVmZXIoZnVuY3Rpb24oYyxkKXt2YXIgZj1heGUudXRpbHMucXVldWUoKTtmLmRlZmVyKGZ1bmN0aW9uKGMsZCl7aC5ydW5DaGVja3MoImFueSIsYSxiLGMsZCl9KSxmLmRlZmVyKGZ1bmN0aW9uKGMsZCl7aC5ydW5DaGVja3MoImFsbCIsYSxiLGMsZCl9KSxmLmRlZmVyKGZ1bmN0aW9uKGMsZCl7aC5ydW5DaGVja3MoIm5vbmUiLGEsYixjLGQpfSksZi50aGVuKGZ1bmN0aW9uKGIpe2lmKGIubGVuZ3RoKXt2YXIgZD0hMSxmPXt9O2IuZm9yRWFjaChmdW5jdGlvbihhKXt2YXIgYj1hLnJlc3VsdHMuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBhfSk7ZlthLnR5cGVdPWIsYi5sZW5ndGgmJihkPSEwKX0pLGQmJihmLm5vZGU9bmV3IGF4ZS51dGlscy5EcUVsZW1lbnQoYSksZS5ub2Rlcy5wdXNoKGYpKX1jKCl9KVsiY2F0Y2giXShkKX0pfSksZy50aGVuKGZ1bmN0aW9uKCl7YyhlKX0pWyJjYXRjaCJdKGQpfSxvLnByb3RvdHlwZS5hZnRlcj1mdW5jdGlvbihhLGIpeyJ1c2Ugc3RyaWN0Ijt2YXIgYz1wKHRoaXMpLGQ9dGhpcy5pZDtyZXR1cm4gYy5mb3JFYWNoKGZ1bmN0aW9uKGMpe3ZhciBlPXEoYS5ub2RlcyxjLmlkKSxmPWF4ZS51dGlscy5nZXRDaGVja09wdGlvbihjLGQsYiksZz1jLmFmdGVyKGUsZik7ZS5mb3JFYWNoKGZ1bmN0aW9uKGEpe2cuaW5kZXhPZihhKT09PS0xJiYoYS5maWx0ZXJlZD0hMCl9KX0pLGEubm9kZXM9cyhhKSxhfSxvLnByb3RvdHlwZS5jb25maWd1cmU9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO2EuaGFzT3duUHJvcGVydHkoInNlbGVjdG9yIikmJih0aGlzLnNlbGVjdG9yPWEuc2VsZWN0b3IpLGEuaGFzT3duUHJvcGVydHkoImV4Y2x1ZGVIaWRkZW4iKSYmKHRoaXMuZXhjbHVkZUhpZGRlbj0iYm9vbGVhbiIhPXR5cGVvZiBhLmV4Y2x1ZGVIaWRkZW58fGEuZXhjbHVkZUhpZGRlbiksYS5oYXNPd25Qcm9wZXJ0eSgiZW5hYmxlZCIpJiYodGhpcy5lbmFibGVkPSJib29sZWFuIiE9dHlwZW9mIGEuZW5hYmxlZHx8YS5lbmFibGVkKSxhLmhhc093blByb3BlcnR5KCJwYWdlTGV2ZWwiKSYmKHRoaXMucGFnZUxldmVsPSJib29sZWFuIj09dHlwZW9mIGEucGFnZUxldmVsJiZhLnBhZ2VMZXZlbCksYS5oYXNPd25Qcm9wZXJ0eSgiYW55IikmJih0aGlzLmFueT1hLmFueSksYS5oYXNPd25Qcm9wZXJ0eSgiYWxsIikmJih0aGlzLmFsbD1hLmFsbCksYS5oYXNPd25Qcm9wZXJ0eSgibm9uZSIpJiYodGhpcy5ub25lPWEubm9uZSksYS5oYXNPd25Qcm9wZXJ0eSgidGFncyIpJiYodGhpcy50YWdzPWEudGFncyksYS5oYXNPd25Qcm9wZXJ0eSgibWF0Y2hlcyIpJiYoInN0cmluZyI9PXR5cGVvZiBhLm1hdGNoZXM/dGhpcy5tYXRjaGVzPW5ldyBGdW5jdGlvbigicmV0dXJuICIrYS5tYXRjaGVzKyI7IikoKTp0aGlzLm1hdGNoZXM9YS5tYXRjaGVzKX0sZnVuY3Rpb24oYXhlKXt2YXIgYT1be25hbWU6Ik5BIix2YWx1ZToiaW5hcHBsaWNhYmxlIixwcmlvcml0eTowLGdyb3VwOiJpbmFwcGxpY2FibGUifSx7bmFtZToiUEFTUyIsdmFsdWU6InBhc3NlZCIscHJpb3JpdHk6MSxncm91cDoicGFzc2VzIn0se25hbWU6IkNBTlRURUxMIix2YWx1ZToiY2FudFRlbGwiLHByaW9yaXR5OjIsZ3JvdXA6ImluY29tcGxldGUifSx7bmFtZToiRkFJTCIsdmFsdWU6ImZhaWxlZCIscHJpb3JpdHk6Myxncm91cDoidmlvbGF0aW9ucyJ9XSxiPXtyZXN1bHRzOltdLHJlc3VsdEdyb3VwczpbXSxyZXN1bHRHcm91cE1hcDp7fSxpbXBhY3Q6T2JqZWN0LmZyZWV6ZShbIm1pbm9yIiwibW9kZXJhdGUiLCJzZXJpb3VzIiwiY3JpdGljYWwiXSl9O2EuZm9yRWFjaChmdW5jdGlvbihhKXt2YXIgYz1hLm5hbWUsZD1hLnZhbHVlLGU9YS5wcmlvcml0eSxmPWEuZ3JvdXA7YltjXT1kLGJbYysiX1BSSU8iXT1lLGJbYysiX0dST1VQIl09ZixiLnJlc3VsdHNbZV09ZCxiLnJlc3VsdEdyb3Vwc1tlXT1mLGIucmVzdWx0R3JvdXBNYXBbZF09Zn0pLE9iamVjdC5mcmVlemUoYi5yZXN1bHRzKSxPYmplY3QuZnJlZXplKGIucmVzdWx0R3JvdXBzKSxPYmplY3QuZnJlZXplKGIucmVzdWx0R3JvdXBNYXApLE9iamVjdC5mcmVlemUoYiksT2JqZWN0LmRlZmluZVByb3BlcnR5KGF4ZSwiY29uc3RhbnRzIix7dmFsdWU6YixlbnVtZXJhYmxlOiEwLGNvbmZpZ3VyYWJsZTohMSx3cml0YWJsZTohMX0pfShheGUpO3ZhciBYPSJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJiJzeW1ib2wiPT10eXBlb2YgU3ltYm9sLml0ZXJhdG9yP2Z1bmN0aW9uKGEpe3JldHVybiB0eXBlb2YgYX06ZnVuY3Rpb24oYSl7cmV0dXJuIGEmJiJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJmEuY29uc3RydWN0b3I9PT1TeW1ib2wmJmEhPT1TeW1ib2wucHJvdG90eXBlPyJzeW1ib2wiOnR5cGVvZiBhfTtheGUubG9nPWZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiOyJvYmplY3QiPT09KCJ1bmRlZmluZWQiPT10eXBlb2YgY29uc29sZT8idW5kZWZpbmVkIjpYKGNvbnNvbGUpKSYmY29uc29sZS5sb2cmJkZ1bmN0aW9uLnByb3RvdHlwZS5hcHBseS5jYWxsKGNvbnNvbGUubG9nLGNvbnNvbGUsYXJndW1lbnRzKX07dmFyIFg9ImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmInN5bWJvbCI9PXR5cGVvZiBTeW1ib2wuaXRlcmF0b3I/ZnVuY3Rpb24oYSl7cmV0dXJuIHR5cGVvZiBhfTpmdW5jdGlvbihhKXtyZXR1cm4gYSYmImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmYS5jb25zdHJ1Y3Rvcj09PVN5bWJvbCYmYSE9PVN5bWJvbC5wcm90b3R5cGU/InN5bWJvbCI6dHlwZW9mIGF9O2F4ZS5hMTF5Q2hlY2s9ZnVuY3Rpb24oYSxiLGMpeyJ1c2Ugc3RyaWN0IjsiZnVuY3Rpb24iPT10eXBlb2YgYiYmKGM9YixiPXt9KSxiJiYib2JqZWN0Ij09PSgidW5kZWZpbmVkIj09dHlwZW9mIGI/InVuZGVmaW5lZCI6WChiKSl8fChiPXt9KTt2YXIgZD1heGUuX2F1ZGl0O2lmKCFkKXRocm93IG5ldyBFcnJvcigiTm8gYXVkaXQgY29uZmlndXJlZCIpO2IucmVwb3J0ZXI9Yi5yZXBvcnRlcnx8ZC5yZXBvcnRlcnx8InYyIjt2YXIgZT1heGUuZ2V0UmVwb3J0ZXIoYi5yZXBvcnRlcik7YXhlLl9ydW5SdWxlcyhhLGIsZnVuY3Rpb24oYSl7dmFyIGQ9ZShhLGIsYyk7dm9pZCAwIT09ZCYmYyhkKX0sYXhlLmxvZyl9LGF4ZS5jbGVhbnVwPXQsYXhlLmNvbmZpZ3VyZT11LGF4ZS5nZXRSdWxlcz1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7YT1hfHxbXTt2YXIgYj1hLmxlbmd0aD9heGUuX2F1ZGl0LnJ1bGVzLmZpbHRlcihmdW5jdGlvbihiKXtyZXR1cm4hIWEuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBiLnRhZ3MuaW5kZXhPZihhKSE9PS0xfSkubGVuZ3RofSk6YXhlLl9hdWRpdC5ydWxlcyxjPWF4ZS5fYXVkaXQuZGF0YS5ydWxlc3x8e307cmV0dXJuIGIubWFwKGZ1bmN0aW9uKGEpe3ZhciBiPWNbYS5pZF18fHt9O3JldHVybntydWxlSWQ6YS5pZCxkZXNjcmlwdGlvbjpiLmRlc2NyaXB0aW9uLGhlbHA6Yi5oZWxwLGhlbHBVcmw6Yi5oZWxwVXJsLHRhZ3M6YS50YWdzfX0pfSxheGUuX2xvYWQ9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO2F4ZS51dGlscy5yZXNwb25kYWJsZS5zdWJzY3JpYmUoImF4ZS5waW5nIixmdW5jdGlvbihhLGIsYyl7Yyh7YXhlOiEwfSl9KSxheGUudXRpbHMucmVzcG9uZGFibGUuc3Vic2NyaWJlKCJheGUuc3RhcnQiLHYpLGF4ZS5fYXVkaXQ9bmV3IGQoYSl9O3ZhciBheGU9YXhlfHx7fTtheGUucGx1Z2lucz17fSx3LnByb3RvdHlwZS5ydW49ZnVuY3Rpb24oKXsidXNlIHN0cmljdCI7cmV0dXJuIHRoaXMuX3J1bi5hcHBseSh0aGlzLGFyZ3VtZW50cyl9LHcucHJvdG90eXBlLmNvbGxlY3Q9ZnVuY3Rpb24oKXsidXNlIHN0cmljdCI7cmV0dXJuIHRoaXMuX2NvbGxlY3QuYXBwbHkodGhpcyxhcmd1bWVudHMpfSx3LnByb3RvdHlwZS5jbGVhbnVwPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1heGUudXRpbHMucXVldWUoKSxjPXRoaXM7T2JqZWN0LmtleXModGhpcy5fcmVnaXN0cnkpLmZvckVhY2goZnVuY3Rpb24oYSl7Yi5kZWZlcihmdW5jdGlvbihiKXtjLl9yZWdpc3RyeVthXS5jbGVhbnVwKGIpfSl9KSxiLnRoZW4oZnVuY3Rpb24oKXthKCl9KX0sdy5wcm90b3R5cGUuYWRkPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt0aGlzLl9yZWdpc3RyeVthLmlkXT1hfSxheGUucmVnaXN0ZXJQbHVnaW49ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO2F4ZS5wbHVnaW5zW2EuaWRdPW5ldyB3KGEpfTt2YXIgWiwkPXt9O2F4ZS5nZXRSZXBvcnRlcj1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7cmV0dXJuInN0cmluZyI9PXR5cGVvZiBhJiYkW2FdPyRbYV06ImZ1bmN0aW9uIj09dHlwZW9mIGE/YTpafSxheGUuYWRkUmVwb3J0ZXI9ZnVuY3Rpb24oYSxiLGMpeyJ1c2Ugc3RyaWN0IjskW2FdPWIsYyYmKFo9Yil9LGF4ZS5yZXNldD14LGF4ZS5fcnVuUnVsZXM9eTt2YXIgWD0iZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiYic3ltYm9sIj09dHlwZW9mIFN5bWJvbC5pdGVyYXRvcj9mdW5jdGlvbihhKXtyZXR1cm4gdHlwZW9mIGF9OmZ1bmN0aW9uKGEpe3JldHVybiBhJiYiZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiZhLmNvbnN0cnVjdG9yPT09U3ltYm9sJiZhIT09U3ltYm9sLnByb3RvdHlwZT8ic3ltYm9sIjp0eXBlb2YgYX0sXz1mdW5jdGlvbigpe307YXhlLnJ1bj1mdW5jdGlvbihhLGIsYyl7InVzZSBzdHJpY3QiO2lmKCFheGUuX2F1ZGl0KXRocm93IG5ldyBFcnJvcigiTm8gYXVkaXQgY29uZmlndXJlZCIpO3ZhciBkPUEoYSxiLGMpO2E9ZC5jb250ZXh0LGI9ZC5vcHRpb25zLGM9ZC5jYWxsYmFjayxiLnJlcG9ydGVyPWIucmVwb3J0ZXJ8fGF4ZS5fYXVkaXQucmVwb3J0ZXJ8fCJ2MSI7dmFyIGU9dm9pZCAwLGY9XyxnPV87cmV0dXJuIHdpbmRvdy5Qcm9taXNlJiZjPT09XyYmKGU9bmV3IFByb21pc2UoZnVuY3Rpb24oYSxiKXtmPWIsZz1hfSkpLGF4ZS5fcnVuUnVsZXMoYSxiLGZ1bmN0aW9uKGEpe3ZhciBkPWZ1bmN0aW9uKGEpe3RyeXtjKG51bGwsYSl9Y2F0Y2goYil7YXhlLmxvZyhiKX1nKGEpfTt0cnl7dmFyIGU9YXhlLmdldFJlcG9ydGVyKGIucmVwb3J0ZXIpLGg9ZShhLGIsZCk7dm9pZCAwIT09aCYmZChoKX1jYXRjaChpKXtjKGkpLGYoaSl9fSxmdW5jdGlvbihhKXtjKGEpLGYoYSl9KSxlfSxZLmZhaWx1cmVTdW1tYXJ5PWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj17fTtyZXR1cm4gYi5ub25lPWEubm9uZS5jb25jYXQoYS5hbGwpLGIuYW55PWEuYW55LE9iamVjdC5rZXlzKGIpLm1hcChmdW5jdGlvbihhKXtpZihiW2FdLmxlbmd0aCl7dmFyIGM9YXhlLl9hdWRpdC5kYXRhLmZhaWx1cmVTdW1tYXJpZXNbYV07cmV0dXJuIGMmJiJmdW5jdGlvbiI9PXR5cGVvZiBjLmZhaWx1cmVNZXNzYWdlP2MuZmFpbHVyZU1lc3NhZ2UoYlthXS5tYXAoZnVuY3Rpb24oYSl7cmV0dXJuIGEubWVzc2FnZXx8IiJ9KSk6dm9pZCAwfX0pLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4gdm9pZCAwIT09YX0pLmpvaW4oIlxuXG4iKX07dmFyIFg9ImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmInN5bWJvbCI9PXR5cGVvZiBTeW1ib2wuaXRlcmF0b3I/ZnVuY3Rpb24oYSl7cmV0dXJuIHR5cGVvZiBhfTpmdW5jdGlvbihhKXtyZXR1cm4gYSYmImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmYS5jb25zdHJ1Y3Rvcj09PVN5bWJvbCYmYSE9PVN5bWJvbC5wcm90b3R5cGU/InN5bWJvbCI6dHlwZW9mIGF9LGFhPWF4ZS5jb25zdGFudHMucmVzdWx0R3JvdXBzO1kucHJvY2Vzc0FnZ3JlZ2F0ZT1mdW5jdGlvbihhLGIpe3ZhciBjPWF4ZS51dGlscy5hZ2dyZWdhdGVSZXN1bHQoYSk7cmV0dXJuIGMudGltZXN0YW1wPShuZXcgRGF0ZSkudG9JU09TdHJpbmcoKSxjLnVybD13aW5kb3cubG9jYXRpb24uaHJlZixhYS5mb3JFYWNoKGZ1bmN0aW9uKGEpe2NbYV09KGNbYV18fFtdKS5tYXAoZnVuY3Rpb24oYSl7cmV0dXJuIGE9T2JqZWN0LmFzc2lnbih7fSxhKSxBcnJheS5pc0FycmF5KGEubm9kZXMpJiZhLm5vZGVzLmxlbmd0aD4wJiYoYS5ub2Rlcz1hLm5vZGVzLm1hcChmdW5jdGlvbihhKXtyZXR1cm4ib2JqZWN0Ij09PVgoYS5ub2RlKSYmKGEuaHRtbD1hLm5vZGUuc291cmNlLGEudGFyZ2V0PWEubm9kZS5zZWxlY3RvcixiLnhwYXRoJiYoYS54cGF0aD1hLm5vZGUueHBhdGgpKSxkZWxldGUgYS5yZXN1bHQsZGVsZXRlIGEubm9kZSxCKGEsYi54cGF0aCksYX0pKSxhYS5mb3JFYWNoKGZ1bmN0aW9uKGIpe3JldHVybiBkZWxldGUgYVtiXX0pLGRlbGV0ZSBhLnBhZ2VMZXZlbCxkZWxldGUgYS5yZXN1bHQsYX0pfSksY30sYXhlLmFkZFJlcG9ydGVyKCJuYSIsZnVuY3Rpb24oYSxiLGMpeyJ1c2Ugc3RyaWN0IjsiZnVuY3Rpb24iPT10eXBlb2YgYiYmKGM9YixiPXt9KTt2YXIgZD1ZLnByb2Nlc3NBZ2dyZWdhdGUoYSxiKTtjKHt2aW9sYXRpb25zOmQudmlvbGF0aW9ucyxwYXNzZXM6ZC5wYXNzZXMsaW5jb21wbGV0ZTpkLmluY29tcGxldGUsaW5hcHBsaWNhYmxlOmQuaW5hcHBsaWNhYmxlLHRpbWVzdGFtcDpkLnRpbWVzdGFtcCx1cmw6ZC51cmx9KX0pLGF4ZS5hZGRSZXBvcnRlcigibm8tcGFzc2VzIixmdW5jdGlvbihhLGIsYyl7InVzZSBzdHJpY3QiOyJmdW5jdGlvbiI9PXR5cGVvZiBiJiYoYz1iLGI9e30pO3ZhciBkPVkucHJvY2Vzc0FnZ3JlZ2F0ZShhLGIpO2Moe3Zpb2xhdGlvbnM6ZC52aW9sYXRpb25zLHRpbWVzdGFtcDpkLnRpbWVzdGFtcCx1cmw6ZC51cmx9KX0pLGF4ZS5hZGRSZXBvcnRlcigicmF3IixmdW5jdGlvbihhLGIsYyl7InVzZSBzdHJpY3QiOyJmdW5jdGlvbiI9PXR5cGVvZiBiJiYoYz1iLGI9e30pLGMoYSl9KSxheGUuYWRkUmVwb3J0ZXIoInYxIixmdW5jdGlvbihhLGIsYyl7InVzZSBzdHJpY3QiOyJmdW5jdGlvbiI9PXR5cGVvZiBiJiYoYz1iLGI9e30pO3ZhciBkPVkucHJvY2Vzc0FnZ3JlZ2F0ZShhLGIpO2QudmlvbGF0aW9ucy5mb3JFYWNoKGZ1bmN0aW9uKGEpe3JldHVybiBhLm5vZGVzLmZvckVhY2goZnVuY3Rpb24oYSl7YS5mYWlsdXJlU3VtbWFyeT1ZLmZhaWx1cmVTdW1tYXJ5KGEpfSl9KSxjKHt2aW9sYXRpb25zOmQudmlvbGF0aW9ucyxwYXNzZXM6ZC5wYXNzZXMsaW5jb21wbGV0ZTpkLmluY29tcGxldGUsaW5hcHBsaWNhYmxlOmQuaW5hcHBsaWNhYmxlLHRpbWVzdGFtcDpkLnRpbWVzdGFtcCx1cmw6ZC51cmx9KX0pLGF4ZS5hZGRSZXBvcnRlcigidjIiLGZ1bmN0aW9uKGEsYixjKXsidXNlIHN0cmljdCI7ImZ1bmN0aW9uIj09dHlwZW9mIGImJihjPWIsYj17fSk7dmFyIGQ9WS5wcm9jZXNzQWdncmVnYXRlKGEsYik7Yyh7dmlvbGF0aW9uczpkLnZpb2xhdGlvbnMscGFzc2VzOmQucGFzc2VzLGluY29tcGxldGU6ZC5pbmNvbXBsZXRlLGluYXBwbGljYWJsZTpkLmluYXBwbGljYWJsZSx0aW1lc3RhbXA6ZC50aW1lc3RhbXAsdXJsOmQudXJsfSl9LCEwKSxheGUudXRpbHMuYWdncmVnYXRlPWZ1bmN0aW9uKGEsYixjKXtiPWIuc2xpY2UoKSxjJiZiLnB1c2goYyk7dmFyIGQ9Yi5tYXAoZnVuY3Rpb24oYil7cmV0dXJuIGEuaW5kZXhPZihiKX0pLnNvcnQoKTtyZXR1cm4gYVtkLnBvcCgpXX07dmFyIGJhPVtdO2JhW2F4ZS5jb25zdGFudHMuUEFTU19QUklPXT0hMCxiYVtheGUuY29uc3RhbnRzLkNBTlRURUxMX1BSSU9dPW51bGwsYmFbYXhlLmNvbnN0YW50cy5GQUlMX1BSSU9dPSExO3ZhciBjYT1bImFueSIsImFsbCIsIm5vbmUiXTtheGUudXRpbHMuYWdncmVnYXRlQ2hlY2tzPWZ1bmN0aW9uKGEpe3ZhciBiPU9iamVjdC5hc3NpZ24oe30sYSk7QyhiLGZ1bmN0aW9uKGEsYil7dmFyIGM9YmEuaW5kZXhPZihhLnJlc3VsdCk7YS5wcmlvcml0eT1jIT09LTE/YzpheGUuY29uc3RhbnRzLkNBTlRURUxMX1BSSU8sIm5vbmUiPT09YiYmKGEucHJpb3JpdHk9NC1hLnByaW9yaXR5KX0pO3ZhciBjPUMoYixmdW5jdGlvbihhKXtyZXR1cm4gYS5wcmlvcml0eX0pO2IucHJpb3JpdHk9TWF0aC5tYXgoYy5hbGwucmVkdWNlKGZ1bmN0aW9uKGEsYil7cmV0dXJuIE1hdGgubWF4KGEsYil9LDApLGMubm9uZS5yZWR1Y2UoZnVuY3Rpb24oYSxiKXtyZXR1cm4gTWF0aC5tYXgoYSxiKX0sMCksYy5hbnkucmVkdWNlKGZ1bmN0aW9uKGEsYil7cmV0dXJuIE1hdGgubWluKGEsYil9LDQpJTQpO3ZhciBkPVtdO3JldHVybiBjYS5mb3JFYWNoKGZ1bmN0aW9uKGEpe2JbYV09YlthXS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuIGEucHJpb3JpdHk9PT1iLnByaW9yaXR5fSksYlthXS5mb3JFYWNoKGZ1bmN0aW9uKGEpe3JldHVybiBkLnB1c2goYS5pbXBhY3QpfSl9KSxiLnByaW9yaXR5PT09YXhlLmNvbnN0YW50cy5GQUlMX1BSSU8/Yi5pbXBhY3Q9YXhlLnV0aWxzLmFnZ3JlZ2F0ZShheGUuY29uc3RhbnRzLmltcGFjdCxkKTpiLmltcGFjdD1udWxsLEMoYixmdW5jdGlvbihhKXtkZWxldGUgYS5yZXN1bHQsZGVsZXRlIGEucHJpb3JpdHl9KSxiLnJlc3VsdD1heGUuY29uc3RhbnRzLnJlc3VsdHNbYi5wcmlvcml0eV0sZGVsZXRlIGIucHJpb3JpdHksYn0sYXhlLnV0aWxzLmFnZ3JlZ2F0ZVJlc3VsdD1mdW5jdGlvbihhKXt2YXIgYj17fTtyZXR1cm4gYXhlLmNvbnN0YW50cy5yZXN1bHRHcm91cHMuZm9yRWFjaChmdW5jdGlvbihhKXtyZXR1cm4gYlthXT1bXX0pLGEuZm9yRWFjaChmdW5jdGlvbihhKXthLmVycm9yP0QoYixhLGF4ZS5jb25zdGFudHMuQ0FOVFRFTExfR1JPVVApOmEucmVzdWx0PT09YXhlLmNvbnN0YW50cy5OQT9EKGIsYSxheGUuY29uc3RhbnRzLk5BX0dST1VQKTpheGUuY29uc3RhbnRzLnJlc3VsdEdyb3Vwcy5mb3JFYWNoKGZ1bmN0aW9uKGMpe0FycmF5LmlzQXJyYXkoYVtjXSkmJmFbY10ubGVuZ3RoPjAmJkQoYixhLGMpfSl9KSxifSxmdW5jdGlvbigpe2F4ZS51dGlscy5hZ2dyZWdhdGVSdWxlPWZ1bmN0aW9uKGEpe3ZhciBiPXt9O2E9YS5tYXAoZnVuY3Rpb24oYSl7aWYoYS5hbnkmJmEuYWxsJiZhLm5vbmUpcmV0dXJuIGF4ZS51dGlscy5hZ2dyZWdhdGVDaGVja3MoYSk7aWYoQXJyYXkuaXNBcnJheShhLm5vZGUpKXJldHVybiBheGUudXRpbHMuZmluYWxpemVSdWxlUmVzdWx0KGEpO3Rocm93IG5ldyBUeXBlRXJyb3IoIkludmFsaWQgUmVzdWx0IHR5cGUiKX0pO3ZhciBjPWEubWFwKGZ1bmN0aW9uKGEpe3JldHVybiBhLnJlc3VsdH0pO2IucmVzdWx0PWF4ZS51dGlscy5hZ2dyZWdhdGUoYXhlLmNvbnN0YW50cy5yZXN1bHRzLGMsYi5yZXN1bHQpLGF4ZS5jb25zdGFudHMucmVzdWx0R3JvdXBzLmZvckVhY2goZnVuY3Rpb24oYSl7cmV0dXJuIGJbYV09W119KSxhLmZvckVhY2goZnVuY3Rpb24oYSl7dmFyIGM9YXhlLmNvbnN0YW50cy5yZXN1bHRHcm91cE1hcFthLnJlc3VsdF07YltjXS5wdXNoKGEpfSk7dmFyIGQ9YXhlLmNvbnN0YW50cy5GQUlMX0dST1VQO2lmKGJbZF0ubGVuZ3RoPjApe3ZhciBlPWJbZF0ubWFwKGZ1bmN0aW9uKGEpe3JldHVybiBhLmltcGFjdH0pO2IuaW1wYWN0PWF4ZS51dGlscy5hZ2dyZWdhdGUoYXhlLmNvbnN0YW50cy5pbXBhY3QsZSl8fG51bGx9ZWxzZSBiLmltcGFjdD1udWxsO3JldHVybiBifX0oKSxheGUudXRpbHMuYXJlU3R5bGVzU2V0PUUsYXhlLnV0aWxzLmNoZWNrSGVscGVyPWZ1bmN0aW9uKGEsYixjKXsidXNlIHN0cmljdCI7cmV0dXJue2lzQXN5bmM6ITEsYXN5bmM6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5pc0FzeW5jPSEwLGZ1bmN0aW9uKGQpe2QgaW5zdGFuY2VvZiBFcnJvcj09ITE/KGEudmFsdWU9ZCxiKGEpKTpjKGQpfX0sZGF0YTpmdW5jdGlvbihiKXthLmRhdGE9Yn0scmVsYXRlZE5vZGVzOmZ1bmN0aW9uKGIpe2I9YiBpbnN0YW5jZW9mIE5vZGU/W2JdOmF4ZS51dGlscy50b0FycmF5KGIpLGEucmVsYXRlZE5vZGVzPWIubWFwKGZ1bmN0aW9uKGEpe3JldHVybiBuZXcgYXhlLnV0aWxzLkRxRWxlbWVudChhKX0pfX19O3ZhciBYPSJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJiJzeW1ib2wiPT10eXBlb2YgU3ltYm9sLml0ZXJhdG9yP2Z1bmN0aW9uKGEpe3JldHVybiB0eXBlb2YgYX06ZnVuY3Rpb24oYSl7cmV0dXJuIGEmJiJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJmEuY29uc3RydWN0b3I9PT1TeW1ib2wmJmEhPT1TeW1ib2wucHJvdG90eXBlPyJzeW1ib2wiOnR5cGVvZiBhfTtheGUudXRpbHMuY2xvbmU9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiLGMsZD1hO2lmKG51bGwhPT1hJiYib2JqZWN0Ij09PSgidW5kZWZpbmVkIj09dHlwZW9mIGE/InVuZGVmaW5lZCI6WChhKSkpaWYoQXJyYXkuaXNBcnJheShhKSlmb3IoZD1bXSxiPTAsYz1hLmxlbmd0aDtiPGM7YisrKWRbYl09YXhlLnV0aWxzLmNsb25lKGFbYl0pO2Vsc2V7ZD17fTtmb3IoYiBpbiBhKWRbYl09YXhlLnV0aWxzLmNsb25lKGFbYl0pfXJldHVybiBkfSxheGUudXRpbHMuc2VuZENvbW1hbmRUb0ZyYW1lPWZ1bmN0aW9uKGEsYixjLGQpeyJ1c2Ugc3RyaWN0Ijt2YXIgZT1hLmNvbnRlbnRXaW5kb3c7aWYoIWUpcmV0dXJuIGF4ZS5sb2coIkZyYW1lIGRvZXMgbm90IGhhdmUgYSBjb250ZW50IHdpbmRvdyIsYSksdm9pZCBjKG51bGwpO3ZhciBmPXNldFRpbWVvdXQoZnVuY3Rpb24oKXtmPXNldFRpbWVvdXQoZnVuY3Rpb24oKXt2YXIgZT1GKCJObyByZXNwb25zZSBmcm9tIGZyYW1lIixhKTtiLmRlYnVnP2QoZSk6KGF4ZS5sb2coZSksYyhudWxsKSl9LDApfSw1MDApO2F4ZS51dGlscy5yZXNwb25kYWJsZShlLCJheGUucGluZyIsbnVsbCx2b2lkIDAsZnVuY3Rpb24oKXtjbGVhclRpbWVvdXQoZiksZj1zZXRUaW1lb3V0KGZ1bmN0aW9uKCl7ZChGKCJBeGUgaW4gZnJhbWUgdGltZWQgb3V0IixhKSl9LDNlNCksYXhlLnV0aWxzLnJlc3BvbmRhYmxlKGUsImF4ZS5zdGFydCIsYix2b2lkIDAsZnVuY3Rpb24oYSl7Y2xlYXJUaW1lb3V0KGYpLGEgaW5zdGFuY2VvZiBFcnJvcj09ITE/YyhhKTpkKGEpfSl9KX0sYXhlLnV0aWxzLmNvbGxlY3RSZXN1bHRzRnJvbUZyYW1lcz1HLGF4ZS51dGlscy5jb250YWlucz1mdW5jdGlvbihhLGIpeyJ1c2Ugc3RyaWN0IjtyZXR1cm4iZnVuY3Rpb24iPT10eXBlb2YgYS5jb250YWlucz9hLmNvbnRhaW5zKGIpOiEhKDE2JmEuY29tcGFyZURvY3VtZW50UG9zaXRpb24oYikpfSxKLnByb3RvdHlwZS50b0pTT049ZnVuY3Rpb24oKXsidXNlIHN0cmljdCI7cmV0dXJue3NlbGVjdG9yOnRoaXMuc2VsZWN0b3Isc291cmNlOnRoaXMuc291cmNlLHhwYXRoOnRoaXMueHBhdGh9fSxKLmZyb21GcmFtZT1mdW5jdGlvbihhLGIpe3JldHVybiBhLnNlbGVjdG9yLnVuc2hpZnQoYi5zZWxlY3RvciksYS54cGF0aC51bnNoaWZ0KGIueHBhdGgpLG5ldyBheGUudXRpbHMuRHFFbGVtZW50KGIuZWxlbWVudCxhKX0sYXhlLnV0aWxzLkRxRWxlbWVudD1KLGF4ZS51dGlscy5tYXRjaGVzU2VsZWN0b3I9ZnVuY3Rpb24oKXsidXNlIHN0cmljdCI7ZnVuY3Rpb24gYShhKXt2YXIgYixjLGQ9YS5FbGVtZW50LnByb3RvdHlwZSxlPVsibWF0Y2hlcyIsIm1hdGNoZXNTZWxlY3RvciIsIm1vek1hdGNoZXNTZWxlY3RvciIsIndlYmtpdE1hdGNoZXNTZWxlY3RvciIsIm1zTWF0Y2hlc1NlbGVjdG9yIl0sZj1lLmxlbmd0aDtmb3IoYj0wO2I8ZjtiKyspaWYoYz1lW2JdLGRbY10pcmV0dXJuIGN9dmFyIGI7cmV0dXJuIGZ1bmN0aW9uKGMsZCl7cmV0dXJuIGImJmNbYl18fChiPWEoYy5vd25lckRvY3VtZW50LmRlZmF1bHRWaWV3KSksY1tiXShkKX19KCksYXhlLnV0aWxzLmVzY2FwZVNlbGVjdG9yPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijtmb3IodmFyIGIsYz1TdHJpbmcoYSksZD1jLmxlbmd0aCxlPS0xLGY9IiIsZz1jLmNoYXJDb2RlQXQoMCk7KytlPGQ7KXtpZihiPWMuY2hhckNvZGVBdChlKSwwPT1iKXRocm93IG5ldyBFcnJvcigiSU5WQUxJRF9DSEFSQUNURVJfRVJSIik7Zis9Yj49MSYmYjw9MzF8fGI+PTEyNyYmYjw9MTU5fHwwPT1lJiZiPj00OCYmYjw9NTd8fDE9PWUmJmI+PTQ4JiZiPD01NyYmNDU9PWc/IlxcIitiLnRvU3RyaW5nKDE2KSsiICI6KDEhPWV8fDQ1IT1ifHw0NSE9ZykmJihiPj0xMjh8fDQ1PT1ifHw5NT09Ynx8Yj49NDgmJmI8PTU3fHxiPj02NSYmYjw9OTB8fGI+PTk3JiZiPD0xMjIpP2MuY2hhckF0KGUpOiJcXCIrYy5jaGFyQXQoZSl9cmV0dXJuIGZ9LGF4ZS51dGlscy5leHRlbmRNZXRhRGF0YT1mdW5jdGlvbihhLGIpe09iamVjdC5hc3NpZ24oYSxiKSxPYmplY3Qua2V5cyhiKS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuImZ1bmN0aW9uIj09dHlwZW9mIGJbYV19KS5mb3JFYWNoKGZ1bmN0aW9uKGMpe2FbY109bnVsbDt0cnl7YVtjXT1iW2NdKGEpfWNhdGNoKGQpe319KX0sYXhlLnV0aWxzLmZpbmFsaXplUnVsZVJlc3VsdD1mdW5jdGlvbihhKXtyZXR1cm4gT2JqZWN0LmFzc2lnbihhLGF4ZS51dGlscy5hZ2dyZWdhdGVSdWxlKGEubm9kZXMpKSxkZWxldGUgYS5ub2RlcyxhfTt2YXIgWD0iZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiYic3ltYm9sIj09dHlwZW9mIFN5bWJvbC5pdGVyYXRvcj9mdW5jdGlvbihhKXtyZXR1cm4gdHlwZW9mIGF9OmZ1bmN0aW9uKGEpe3JldHVybiBhJiYiZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiZhLmNvbnN0cnVjdG9yPT09U3ltYm9sJiZhIT09U3ltYm9sLnByb3RvdHlwZT8ic3ltYm9sIjp0eXBlb2YgYX07YXhlLnV0aWxzLmZpbmRCeT1mdW5jdGlvbihhLGIsYyl7aWYoQXJyYXkuaXNBcnJheShhKSlyZXR1cm4gYS5maW5kKGZ1bmN0aW9uKGEpe3JldHVybiJvYmplY3QiPT09KCJ1bmRlZmluZWQiPT10eXBlb2YgYT8idW5kZWZpbmVkIjpYKGEpKSYmYVtiXT09PWN9KX0sYXhlLnV0aWxzLmdldEFsbENoZWNrcz1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7dmFyIGI9W107cmV0dXJuIGIuY29uY2F0KGEuYW55fHxbXSkuY29uY2F0KGEuYWxsfHxbXSkuY29uY2F0KGEubm9uZXx8W10pfSxheGUudXRpbHMuZ2V0Q2hlY2tPcHRpb249ZnVuY3Rpb24oYSxiLGMpeyJ1c2Ugc3RyaWN0Ijt2YXIgZD0oKGMucnVsZXMmJmMucnVsZXNbYl18fHt9KS5jaGVja3N8fHt9KVthLmlkXSxlPShjLmNoZWNrc3x8e30pW2EuaWRdLGY9YS5lbmFibGVkLGc9YS5vcHRpb25zO3JldHVybiBlJiYoZS5oYXNPd25Qcm9wZXJ0eSgiZW5hYmxlZCIpJiYoZj1lLmVuYWJsZWQpLGUuaGFzT3duUHJvcGVydHkoIm9wdGlvbnMiKSYmKGc9ZS5vcHRpb25zKSksZCYmKGQuaGFzT3duUHJvcGVydHkoImVuYWJsZWQiKSYmKGY9ZC5lbmFibGVkKSxkLmhhc093blByb3BlcnR5KCJvcHRpb25zIikmJihnPWQub3B0aW9ucykpLHtlbmFibGVkOmYsb3B0aW9uczpnfX0sYXhlLnV0aWxzLmdldFNlbGVjdG9yPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiBiKGEpe3JldHVybiBheGUudXRpbHMuZXNjYXBlU2VsZWN0b3IoYSl9Zm9yKHZhciBjLGQ9W107YS5wYXJlbnROb2RlOyl7aWYoYz0iIixhLmlkJiYxPT09ZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgiIyIrYXhlLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEuaWQpKS5sZW5ndGgpe2QudW5zaGlmdCgiIyIrYXhlLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEuaWQpKTticmVha31pZihhLmNsYXNzTmFtZSYmInN0cmluZyI9PXR5cGVvZiBhLmNsYXNzTmFtZSYmKGM9Ii4iK2EuY2xhc3NOYW1lLnRyaW0oKS5zcGxpdCgvXHMrLykubWFwKGIpLmpvaW4oIi4iKSwoIi4iPT09Y3x8TChhLGMpKSYmKGM9IiIpKSwhYyl7aWYoYz1heGUudXRpbHMuZXNjYXBlU2VsZWN0b3IoYS5ub2RlTmFtZSkudG9Mb3dlckNhc2UoKSwiaHRtbCI9PT1jfHwiYm9keSI9PT1jKXtkLnVuc2hpZnQoYyk7YnJlYWt9TChhLGMpJiYoYys9IjpudGgtb2YtdHlwZSgiK0soYSkrIikiKX1kLnVuc2hpZnQoYyksYT1hLnBhcmVudE5vZGV9cmV0dXJuIGQuam9pbigiID4gIil9LGF4ZS51dGlscy5nZXRYcGF0aD1mdW5jdGlvbihhKXt2YXIgYj1NKGEpO3JldHVybiBOKGIpfTt2YXIgZGE7YXhlLnV0aWxzLmluamVjdFN0eWxlPU8sYXhlLnV0aWxzLmlzSGlkZGVuPWZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO2lmKDk9PT1hLm5vZGVUeXBlKXJldHVybiExO3ZhciBjPXdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGEsbnVsbCk7cmV0dXJuIWN8fCFhLnBhcmVudE5vZGV8fCJub25lIj09PWMuZ2V0UHJvcGVydHlWYWx1ZSgiZGlzcGxheSIpfHwhYiYmImhpZGRlbiI9PT1jLmdldFByb3BlcnR5VmFsdWUoInZpc2liaWxpdHkiKXx8InRydWUiPT09YS5nZXRBdHRyaWJ1dGUoImFyaWEtaGlkZGVuIil8fGF4ZS51dGlscy5pc0hpZGRlbihhLnBhcmVudE5vZGUsITApfSxheGUudXRpbHMubWVyZ2VSZXN1bHRzPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1bXTtyZXR1cm4gYS5mb3JFYWNoKGZ1bmN0aW9uKGEpe3ZhciBjPVIoYSk7YyYmYy5sZW5ndGgmJmMuZm9yRWFjaChmdW5jdGlvbihjKXtjLm5vZGVzJiZhLmZyYW1lJiZQKGMubm9kZXMsYS5mcmFtZUVsZW1lbnQsYS5mcmFtZSk7dmFyIGQ9YXhlLnV0aWxzLmZpbmRCeShiLCJpZCIsYy5pZCk7ZD9jLm5vZGVzLmxlbmd0aCYmUShkLm5vZGVzLGMubm9kZXMpOmIucHVzaChjKX0pfSksYn0sYXhlLnV0aWxzLm5vZGVTb3J0ZXI9ZnVuY3Rpb24oYSxiKXsidXNlIHN0cmljdCI7cmV0dXJuIGE9PT1iPzA6NCZhLmNvbXBhcmVEb2N1bWVudFBvc2l0aW9uKGIpPy0xOjF9LCJmdW5jdGlvbiIhPXR5cGVvZiBPYmplY3QuYXNzaWduJiYhZnVuY3Rpb24oKXtPYmplY3QuYXNzaWduPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtpZih2b2lkIDA9PT1hfHxudWxsPT09YSl0aHJvdyBuZXcgVHlwZUVycm9yKCJDYW5ub3QgY29udmVydCB1bmRlZmluZWQgb3IgbnVsbCB0byBvYmplY3QiKTtmb3IodmFyIGI9T2JqZWN0KGEpLGM9MTtjPGFyZ3VtZW50cy5sZW5ndGg7YysrKXt2YXIgZD1hcmd1bWVudHNbY107aWYodm9pZCAwIT09ZCYmbnVsbCE9PWQpZm9yKHZhciBlIGluIGQpZC5oYXNPd25Qcm9wZXJ0eShlKSYmKGJbZV09ZFtlXSl9cmV0dXJuIGJ9fSgpLEFycmF5LnByb3RvdHlwZS5maW5kfHwoQXJyYXkucHJvdG90eXBlLmZpbmQ9ZnVuY3Rpb24oYSl7aWYobnVsbD09PXRoaXMpdGhyb3cgbmV3IFR5cGVFcnJvcigiQXJyYXkucHJvdG90eXBlLmZpbmQgY2FsbGVkIG9uIG51bGwgb3IgdW5kZWZpbmVkIik7aWYoImZ1bmN0aW9uIiE9dHlwZW9mIGEpdGhyb3cgbmV3IFR5cGVFcnJvcigicHJlZGljYXRlIG11c3QgYmUgYSBmdW5jdGlvbiIpOwpmb3IodmFyIGIsYz1PYmplY3QodGhpcyksZD1jLmxlbmd0aD4+PjAsZT1hcmd1bWVudHNbMV0sZj0wO2Y8ZDtmKyspaWYoYj1jW2ZdLGEuY2FsbChlLGIsZixjKSlyZXR1cm4gYn0pLGF4ZS51dGlscy5wb2xseWZpbGxFbGVtZW50c0Zyb21Qb2ludD1mdW5jdGlvbigpe2lmKGRvY3VtZW50LmVsZW1lbnRzRnJvbVBvaW50KXJldHVybiBkb2N1bWVudC5lbGVtZW50c0Zyb21Qb2ludDtpZihkb2N1bWVudC5tc0VsZW1lbnRzRnJvbVBvaW50KXJldHVybiBkb2N1bWVudC5tc0VsZW1lbnRzRnJvbVBvaW50O3ZhciBhPWZ1bmN0aW9uKCl7dmFyIGE9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgieCIpO3JldHVybiBhLnN0eWxlLmNzc1RleHQ9InBvaW50ZXItZXZlbnRzOmF1dG8iLCJhdXRvIj09PWEuc3R5bGUucG9pbnRlckV2ZW50c30oKSxiPWE/InBvaW50ZXItZXZlbnRzIjoidmlzaWJpbGl0eSIsYz1hPyJub25lIjoiaGlkZGVuIixkPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInN0eWxlIik7cmV0dXJuIGQuaW5uZXJIVE1MPWE/IiogeyBwb2ludGVyLWV2ZW50czogYWxsIH0iOiIqIHsgdmlzaWJpbGl0eTogdmlzaWJsZSB9IixmdW5jdGlvbihhLGUpe3ZhciBmLGcsaCxpPVtdLGo9W107Zm9yKGRvY3VtZW50LmhlYWQuYXBwZW5kQ2hpbGQoZCk7KGY9ZG9jdW1lbnQuZWxlbWVudEZyb21Qb2ludChhLGUpKSYmaS5pbmRleE9mKGYpPT09LTE7KWkucHVzaChmKSxqLnB1c2goe3ZhbHVlOmYuc3R5bGUuZ2V0UHJvcGVydHlWYWx1ZShiKSxwcmlvcml0eTpmLnN0eWxlLmdldFByb3BlcnR5UHJpb3JpdHkoYil9KSxmLnN0eWxlLnNldFByb3BlcnR5KGIsYywiaW1wb3J0YW50Iik7Zm9yKGc9ai5sZW5ndGg7aD1qWy0tZ107KWlbZ10uc3R5bGUuc2V0UHJvcGVydHkoYixoLnZhbHVlP2gudmFsdWU6IiIsaC5wcmlvcml0eSk7cmV0dXJuIGRvY3VtZW50LmhlYWQucmVtb3ZlQ2hpbGQoZCksaX19LCJmdW5jdGlvbiI9PXR5cGVvZiB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lciYmKGRvY3VtZW50LmVsZW1lbnRzRnJvbVBvaW50PWF4ZS51dGlscy5wb2xseWZpbGxFbGVtZW50c0Zyb21Qb2ludCgpKSxBcnJheS5wcm90b3R5cGUuaW5jbHVkZXN8fChBcnJheS5wcm90b3R5cGUuaW5jbHVkZXM9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPU9iamVjdCh0aGlzKSxjPXBhcnNlSW50KGIubGVuZ3RoLDEwKXx8MDtpZigwPT09YylyZXR1cm4hMTt2YXIgZCxlPXBhcnNlSW50KGFyZ3VtZW50c1sxXSwxMCl8fDA7ZT49MD9kPWU6KGQ9YytlLGQ8MCYmKGQ9MCkpO2Zvcih2YXIgZjtkPGM7KXtpZihmPWJbZF0sYT09PWZ8fGEhPT1hJiZmIT09ZilyZXR1cm4hMDtkKyt9cmV0dXJuITF9KSxBcnJheS5wcm90b3R5cGUuc29tZXx8KEFycmF5LnByb3RvdHlwZS5zb21lPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtpZihudWxsPT10aGlzKXRocm93IG5ldyBUeXBlRXJyb3IoIkFycmF5LnByb3RvdHlwZS5zb21lIGNhbGxlZCBvbiBudWxsIG9yIHVuZGVmaW5lZCIpO2lmKCJmdW5jdGlvbiIhPXR5cGVvZiBhKXRocm93IG5ldyBUeXBlRXJyb3I7Zm9yKHZhciBiPU9iamVjdCh0aGlzKSxjPWIubGVuZ3RoPj4+MCxkPWFyZ3VtZW50cy5sZW5ndGg+PTI/YXJndW1lbnRzWzFdOnZvaWQgMCxlPTA7ZTxjO2UrKylpZihlIGluIGImJmEuY2FsbChkLGJbZV0sZSxiKSlyZXR1cm4hMDtyZXR1cm4hMX0pLGF4ZS51dGlscy5wdWJsaXNoTWV0YURhdGE9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPWF4ZS5fYXVkaXQuZGF0YS5jaGVja3N8fHt9LGM9YXhlLl9hdWRpdC5kYXRhLnJ1bGVzfHx7fSxkPWF4ZS51dGlscy5maW5kQnkoYXhlLl9hdWRpdC5ydWxlcywiaWQiLGEuaWQpfHx7fTthLnRhZ3M9YXhlLnV0aWxzLmNsb25lKGQudGFnc3x8W10pO3ZhciBlPVMoYiwhMCksZj1TKGIsITEpO2Eubm9kZXMuZm9yRWFjaChmdW5jdGlvbihhKXthLmFueS5mb3JFYWNoKGUpLGEuYWxsLmZvckVhY2goZSksYS5ub25lLmZvckVhY2goZil9KSxheGUudXRpbHMuZXh0ZW5kTWV0YURhdGEoYSxheGUudXRpbHMuY2xvbmUoY1thLmlkXXx8e30pKX07dmFyIFg9ImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmInN5bWJvbCI9PXR5cGVvZiBTeW1ib2wuaXRlcmF0b3I/ZnVuY3Rpb24oYSl7cmV0dXJuIHR5cGVvZiBhfTpmdW5jdGlvbihhKXtyZXR1cm4gYSYmImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmYS5jb25zdHJ1Y3Rvcj09PVN5bWJvbCYmYSE9PVN5bWJvbC5wcm90b3R5cGU/InN5bWJvbCI6dHlwZW9mIGF9OyFmdW5jdGlvbigpeyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiBhKCl7fWZ1bmN0aW9uIGIoYSl7aWYoImZ1bmN0aW9uIiE9dHlwZW9mIGEpdGhyb3cgbmV3IFR5cGVFcnJvcigiUXVldWUgbWV0aG9kcyByZXF1aXJlIGZ1bmN0aW9ucyBhcyBhcmd1bWVudHMiKX1mdW5jdGlvbiBjKCl7ZnVuY3Rpb24gYyhiKXtyZXR1cm4gZnVuY3Rpb24oYyl7Z1tiXT1jLGktPTEsaXx8aj09PWF8fChrPSEwLGooZykpfX1mdW5jdGlvbiBkKGIpe3JldHVybiBqPWEsbShiKSxnfWZ1bmN0aW9uIGUoKXtmb3IodmFyIGE9Zy5sZW5ndGg7aDxhO2grKyl7dmFyIGI9Z1toXTt0cnl7Yi5jYWxsKG51bGwsYyhoKSxkKX1jYXRjaChlKXtkKGUpfX19dmFyIGYsZz1bXSxoPTAsaT0wLGo9YSxrPSExLGw9ZnVuY3Rpb24oYSl7Zj1hLHNldFRpbWVvdXQoZnVuY3Rpb24oKXt2b2lkIDAhPT1mJiZudWxsIT09ZiYmYXhlLmxvZygiVW5jYXVnaHQgZXJyb3IgKG9mIHF1ZXVlKSIsZil9LDEpfSxtPWwsbj17ZGVmZXI6ZnVuY3Rpb24gbyhhKXtpZigib2JqZWN0Ij09PSgidW5kZWZpbmVkIj09dHlwZW9mIGE/InVuZGVmaW5lZCI6WChhKSkmJmEudGhlbiYmYVsiY2F0Y2giXSl7dmFyIG89YTthPWZ1bmN0aW9uKGEsYil7by50aGVuKGEpWyJjYXRjaCJdKGIpfX1pZihiKGEpLHZvaWQgMD09PWYpe2lmKGspdGhyb3cgbmV3IEVycm9yKCJRdWV1ZSBhbHJlYWR5IGNvbXBsZXRlZCIpO3JldHVybiBnLnB1c2goYSksKytpLGUoKSxufX0sdGhlbjpmdW5jdGlvbihjKXtpZihiKGMpLGohPT1hKXRocm93IG5ldyBFcnJvcigicXVldWUgYHRoZW5gIGFscmVhZHkgc2V0Iik7cmV0dXJuIGZ8fChqPWMsaXx8KGs9ITAsaihnKSkpLG59LCJjYXRjaCI6ZnVuY3Rpb24oYSl7aWYoYihhKSxtIT09bCl0aHJvdyBuZXcgRXJyb3IoInF1ZXVlIGBjYXRjaGAgYWxyZWFkeSBzZXQiKTtyZXR1cm4gZj8oYShmKSxmPW51bGwpOm09YSxufSxhYm9ydDpkfTtyZXR1cm4gbn1heGUudXRpbHMucXVldWU9Y30oKTt2YXIgWD0iZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiYic3ltYm9sIj09dHlwZW9mIFN5bWJvbC5pdGVyYXRvcj9mdW5jdGlvbihhKXtyZXR1cm4gdHlwZW9mIGF9OmZ1bmN0aW9uKGEpe3JldHVybiBhJiYiZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiZhLmNvbnN0cnVjdG9yPT09U3ltYm9sJiZhIT09U3ltYm9sLnByb3RvdHlwZT8ic3ltYm9sIjp0eXBlb2YgYX07IWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiBiKCl7dmFyIGEsYj0iYXhlIixjPSIiO3JldHVybiJ1bmRlZmluZWQiIT10eXBlb2YgYXhlJiZheGUuX2F1ZGl0JiYhYXhlLl9hdWRpdC5hcHBsaWNhdGlvbiYmKGI9YXhlLl9hdWRpdC5hcHBsaWNhdGlvbiksInVuZGVmaW5lZCIhPXR5cGVvZiBheGUmJihjPWF4ZS52ZXJzaW9uKSxhPWIrIi4iK2N9ZnVuY3Rpb24gYyhhKXtpZigib2JqZWN0Ij09PSgidW5kZWZpbmVkIj09dHlwZW9mIGE/InVuZGVmaW5lZCI6WChhKSkmJiJzdHJpbmciPT10eXBlb2YgYS51dWlkJiZhLl9yZXNwb25kYWJsZT09PSEwKXt2YXIgYz1iKCk7cmV0dXJuIGEuX3NvdXJjZT09PWN8fCJheGUueC55LnoiPT09YS5fc291cmNlfHwiYXhlLngueS56Ij09PWN9cmV0dXJuITF9ZnVuY3Rpb24gZChhLGMsZCxlLGYsZyl7dmFyIGg7ZCBpbnN0YW5jZW9mIEVycm9yJiYoaD17bmFtZTpkLm5hbWUsbWVzc2FnZTpkLm1lc3NhZ2Usc3RhY2s6ZC5zdGFja30sZD12b2lkIDApO3ZhciBpPXt1dWlkOmUsdG9waWM6YyxtZXNzYWdlOmQsZXJyb3I6aCxfcmVzcG9uZGFibGU6ITAsX3NvdXJjZTpiKCksX2tlZXBhbGl2ZTpmfTsiZnVuY3Rpb24iPT10eXBlb2YgZyYmKGpbZV09ZyksYS5wb3N0TWVzc2FnZShKU09OLnN0cmluZ2lmeShpKSwiKiIpfWZ1bmN0aW9uIGUoYSxiLGMsZSxmKXt2YXIgZz1lYS52MSgpO2QoYSxiLGMsZyxlLGYpfWZ1bmN0aW9uIGYoYSxiLGMpe3JldHVybiBmdW5jdGlvbihlLGYsZyl7ZChhLGIsZSxjLGYsZyl9fWZ1bmN0aW9uIGcoYSxiLGMpe3ZhciBkPWIudG9waWMsZT1rW2RdO2lmKGUpe3ZhciBnPWYoYSxudWxsLGIudXVpZCk7ZShiLm1lc3NhZ2UsYyxnKX19ZnVuY3Rpb24gaChhKXt2YXIgYj1hLm1lc3NhZ2V8fCJVbmtub3duIGVycm9yIG9jY3VycmVkIixjPXdpbmRvd1thLm5hbWVdfHxFcnJvcjtyZXR1cm4gYS5zdGFjayYmKGIrPSJcbiIrYS5zdGFjay5yZXBsYWNlKGEubWVzc2FnZSwiIikpLG5ldyBjKGIpfWZ1bmN0aW9uIGkoYSl7dmFyIGI7aWYoInN0cmluZyI9PXR5cGVvZiBhKXt0cnl7Yj1KU09OLnBhcnNlKGEpfWNhdGNoKGQpe31pZihjKGIpKXJldHVybiJvYmplY3QiPT09WChiLmVycm9yKT9iLmVycm9yPWgoYi5lcnJvcik6Yi5lcnJvcj12b2lkIDAsYn19dmFyIGo9e30saz17fTtlLnN1YnNjcmliZT1mdW5jdGlvbihhLGIpe2tbYV09Yn0sZS5pc0luRnJhbWU9ZnVuY3Rpb24oYSl7cmV0dXJuIGE9YXx8d2luZG93LCEhYS5mcmFtZUVsZW1lbnR9LCJmdW5jdGlvbiI9PXR5cGVvZiB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lciYmd2luZG93LmFkZEV2ZW50TGlzdGVuZXIoIm1lc3NhZ2UiLGZ1bmN0aW9uKGEpe3ZhciBiPWkoYS5kYXRhKTtpZihiKXt2YXIgYz1iLnV1aWQsZT1iLl9rZWVwYWxpdmUsaD1qW2NdO2lmKGgpe3ZhciBrPWIuZXJyb3J8fGIubWVzc2FnZSxsPWYoYS5zb3VyY2UsYi50b3BpYyxjKTtoKGssZSxsKSxlfHxkZWxldGUgaltjXX1pZighYi5lcnJvcil0cnl7ZyhhLnNvdXJjZSxiLGUpfWNhdGNoKG0pe2QoYS5zb3VyY2UsYi50b3BpYyxtLGMsITEpfX19LCExKSxhLnJlc3BvbmRhYmxlPWV9KHV0aWxzKSxheGUudXRpbHMucnVsZVNob3VsZFJ1bj1mdW5jdGlvbihhLGIsYyl7InVzZSBzdHJpY3QiO3ZhciBkPWMucnVuT25seXx8e30sZT0oYy5ydWxlc3x8e30pW2EuaWRdO3JldHVybiEoYS5wYWdlTGV2ZWwmJiFiLnBhZ2UpJiYoInJ1bGUiPT09ZC50eXBlP2QudmFsdWVzLmluZGV4T2YoYS5pZCkhPT0tMTplJiYiYm9vbGVhbiI9PXR5cGVvZiBlLmVuYWJsZWQ/ZS5lbmFibGVkOiJ0YWciPT09ZC50eXBlJiZkLnZhbHVlcz9UKGEsZC52YWx1ZXMpOlQoYSxbXSkpfSxheGUudXRpbHMuc2VsZWN0PWZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO2Zvcih2YXIgYyxkPVtdLGU9MCxmPWIuaW5jbHVkZS5sZW5ndGg7ZTxmO2UrKyljPWIuaW5jbHVkZVtlXSxjLm5vZGVUeXBlPT09Yy5FTEVNRU5UX05PREUmJmF4ZS51dGlscy5tYXRjaGVzU2VsZWN0b3IoYyxhKSYmVyhkLFtjXSxiKSxXKGQsYy5xdWVyeVNlbGVjdG9yQWxsKGEpLGIpO3JldHVybiBkLnNvcnQoYXhlLnV0aWxzLm5vZGVTb3J0ZXIpfSxheGUudXRpbHMudG9BcnJheT1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7cmV0dXJuIEFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKGEpfTt2YXIgZWE7IWZ1bmN0aW9uKGEpe2Z1bmN0aW9uIGIoYSxiLGMpe3ZhciBkPWImJmN8fDAsZT0wO2ZvcihiPWJ8fFtdLGEudG9Mb3dlckNhc2UoKS5yZXBsYWNlKC9bMC05YS1mXXsyfS9nLGZ1bmN0aW9uKGEpe2U8MTYmJihiW2QrZSsrXT1sW2FdKX0pO2U8MTY7KWJbZCtlKytdPTA7cmV0dXJuIGJ9ZnVuY3Rpb24gYyhhLGIpe3ZhciBjPWJ8fDAsZD1rO3JldHVybiBkW2FbYysrXV0rZFthW2MrK11dK2RbYVtjKytdXStkW2FbYysrXV0rIi0iK2RbYVtjKytdXStkW2FbYysrXV0rIi0iK2RbYVtjKytdXStkW2FbYysrXV0rIi0iK2RbYVtjKytdXStkW2FbYysrXV0rIi0iK2RbYVtjKytdXStkW2FbYysrXV0rZFthW2MrK11dK2RbYVtjKytdXStkW2FbYysrXV0rZFthW2MrK11dfWZ1bmN0aW9uIGQoYSxiLGQpe3ZhciBlPWImJmR8fDAsZj1ifHxbXTthPWF8fHt9O3ZhciBnPW51bGwhPWEuY2xvY2tzZXE/YS5jbG9ja3NlcTpwLGg9bnVsbCE9YS5tc2Vjcz9hLm1zZWNzOihuZXcgRGF0ZSkuZ2V0VGltZSgpLGk9bnVsbCE9YS5uc2Vjcz9hLm5zZWNzOnIrMSxqPWgtcSsoaS1yKS8xZTQ7aWYoajwwJiZudWxsPT1hLmNsb2Nrc2VxJiYoZz1nKzEmMTYzODMpLChqPDB8fGg+cSkmJm51bGw9PWEubnNlY3MmJihpPTApLGk+PTFlNCl0aHJvdyBuZXcgRXJyb3IoInV1aWQudjEoKTogQ2FuJ3QgY3JlYXRlIG1vcmUgdGhhbiAxME0gdXVpZHMvc2VjIik7cT1oLHI9aSxwPWcsaCs9MTIyMTkyOTI4ZTU7dmFyIGs9KDFlNCooMjY4NDM1NDU1JmgpK2kpJTQyOTQ5NjcyOTY7ZltlKytdPWs+Pj4yNCYyNTUsZltlKytdPWs+Pj4xNiYyNTUsZltlKytdPWs+Pj44JjI1NSxmW2UrK109MjU1Jms7dmFyIGw9aC80Mjk0OTY3Mjk2KjFlNCYyNjg0MzU0NTU7ZltlKytdPWw+Pj44JjI1NSxmW2UrK109MjU1JmwsZltlKytdPWw+Pj4yNCYxNXwxNixmW2UrK109bD4+PjE2JjI1NSxmW2UrK109Zz4+Pjh8MTI4LGZbZSsrXT0yNTUmZztmb3IodmFyIG09YS5ub2RlfHxvLG49MDtuPDY7bisrKWZbZStuXT1tW25dO3JldHVybiBiP2I6YyhmKX1mdW5jdGlvbiBlKGEsYixkKXt2YXIgZT1iJiZkfHwwOyJzdHJpbmciPT10eXBlb2YgYSYmKGI9ImJpbmFyeSI9PWE/bmV3IGooMTYpOm51bGwsYT1udWxsKSxhPWF8fHt9O3ZhciBnPWEucmFuZG9tfHwoYS5ybmd8fGYpKCk7aWYoZ1s2XT0xNSZnWzZdfDY0LGdbOF09NjMmZ1s4XXwxMjgsYilmb3IodmFyIGg9MDtoPDE2O2grKyliW2UraF09Z1toXTtyZXR1cm4gYnx8YyhnKX12YXIgZixnPWEuY3J5cHRvfHxhLm1zQ3J5cHRvO2lmKCFmJiZnJiZnLmdldFJhbmRvbVZhbHVlcyl7dmFyIGg9bmV3IFVpbnQ4QXJyYXkoMTYpO2Y9ZnVuY3Rpb24oKXtyZXR1cm4gZy5nZXRSYW5kb21WYWx1ZXMoaCksaH19aWYoIWYpe3ZhciBpPW5ldyBBcnJheSgxNik7Zj1mdW5jdGlvbigpe2Zvcih2YXIgYSxiPTA7YjwxNjtiKyspMD09PSgzJmIpJiYoYT00Mjk0OTY3Mjk2Kk1hdGgucmFuZG9tKCkpLGlbYl09YT4+PigoMyZiKTw8MykmMjU1O3JldHVybiBpfX1mb3IodmFyIGo9ImZ1bmN0aW9uIj09dHlwZW9mIGEuQnVmZmVyP2EuQnVmZmVyOkFycmF5LGs9W10sbD17fSxtPTA7bTwyNTY7bSsrKWtbbV09KG0rMjU2KS50b1N0cmluZygxNikuc3Vic3RyKDEpLGxba1ttXV09bTt2YXIgbj1mKCksbz1bMXxuWzBdLG5bMV0sblsyXSxuWzNdLG5bNF0sbls1XV0scD0xNjM4MyYobls2XTw8OHxuWzddKSxxPTAscj0wO2VhPWUsZWEudjE9ZCxlYS52ND1lLGVhLnBhcnNlPWIsZWEudW5wYXJzZT1jLGVhLkJ1ZmZlckNsYXNzPWp9KHdpbmRvdyksYXhlLl9sb2FkKHtkYXRhOntydWxlczp7YWNjZXNza2V5czp7ZGVzY3JpcHRpb246IkVuc3VyZXMgZXZlcnkgYWNjZXNza2V5IGF0dHJpYnV0ZSB2YWx1ZSBpcyB1bmlxdWUiLGhlbHA6ImFjY2Vzc2tleSBhdHRyaWJ1dGUgdmFsdWUgbXVzdCBiZSB1bmlxdWUifSwiYXJlYS1hbHQiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyA8YXJlYT4gZWxlbWVudHMgb2YgaW1hZ2UgbWFwcyBoYXZlIGFsdGVybmF0ZSB0ZXh0IixoZWxwOiJBY3RpdmUgPGFyZWE+IGVsZW1lbnRzIG11c3QgaGF2ZSBhbHRlcm5hdGUgdGV4dCJ9LCJhcmlhLWFsbG93ZWQtYXR0ciI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIEFSSUEgYXR0cmlidXRlcyBhcmUgYWxsb3dlZCBmb3IgYW4gZWxlbWVudCdzIHJvbGUiLGhlbHA6IkVsZW1lbnRzIG11c3Qgb25seSB1c2UgYWxsb3dlZCBBUklBIGF0dHJpYnV0ZXMifSwiYXJpYS1yZXF1aXJlZC1hdHRyIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgZWxlbWVudHMgd2l0aCBBUklBIHJvbGVzIGhhdmUgYWxsIHJlcXVpcmVkIEFSSUEgYXR0cmlidXRlcyIsaGVscDoiUmVxdWlyZWQgQVJJQSBhdHRyaWJ1dGVzIG11c3QgYmUgcHJvdmlkZWQifSwiYXJpYS1yZXF1aXJlZC1jaGlsZHJlbiI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGVsZW1lbnRzIHdpdGggYW4gQVJJQSByb2xlIHRoYXQgcmVxdWlyZSBjaGlsZCByb2xlcyBjb250YWluIHRoZW0iLGhlbHA6IkNlcnRhaW4gQVJJQSByb2xlcyBtdXN0IGNvbnRhaW4gcGFydGljdWxhciBjaGlsZHJlbiJ9LCJhcmlhLXJlcXVpcmVkLXBhcmVudCI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGVsZW1lbnRzIHdpdGggYW4gQVJJQSByb2xlIHRoYXQgcmVxdWlyZSBwYXJlbnQgcm9sZXMgYXJlIGNvbnRhaW5lZCBieSB0aGVtIixoZWxwOiJDZXJ0YWluIEFSSUEgcm9sZXMgbXVzdCBiZSBjb250YWluZWQgYnkgcGFydGljdWxhciBwYXJlbnRzIn0sImFyaWEtcm9sZXMiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBhbGwgZWxlbWVudHMgd2l0aCBhIHJvbGUgYXR0cmlidXRlIHVzZSBhIHZhbGlkIHZhbHVlIixoZWxwOiJBUklBIHJvbGVzIHVzZWQgbXVzdCBjb25mb3JtIHRvIHZhbGlkIHZhbHVlcyJ9LCJhcmlhLXZhbGlkLWF0dHItdmFsdWUiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBhbGwgQVJJQSBhdHRyaWJ1dGVzIGhhdmUgdmFsaWQgdmFsdWVzIixoZWxwOiJBUklBIGF0dHJpYnV0ZXMgbXVzdCBjb25mb3JtIHRvIHZhbGlkIHZhbHVlcyJ9LCJhcmlhLXZhbGlkLWF0dHIiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBhdHRyaWJ1dGVzIHRoYXQgYmVnaW4gd2l0aCBhcmlhLSBhcmUgdmFsaWQgQVJJQSBhdHRyaWJ1dGVzIixoZWxwOiJBUklBIGF0dHJpYnV0ZXMgbXVzdCBjb25mb3JtIHRvIHZhbGlkIG5hbWVzIn0sImF1ZGlvLWNhcHRpb24iOntkZXNjcmlwdGlvbjoiRW5zdXJlcyA8YXVkaW8+IGVsZW1lbnRzIGhhdmUgY2FwdGlvbnMiLGhlbHA6IjxhdWRpbz4gZWxlbWVudHMgbXVzdCBoYXZlIGEgY2FwdGlvbnMgdHJhY2sifSxibGluazp7ZGVzY3JpcHRpb246IkVuc3VyZXMgPGJsaW5rPiBlbGVtZW50cyBhcmUgbm90IHVzZWQiLGhlbHA6IjxibGluaz4gZWxlbWVudHMgYXJlIGRlcHJlY2F0ZWQgYW5kIG11c3Qgbm90IGJlIHVzZWQifSwiYnV0dG9uLW5hbWUiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBidXR0b25zIGhhdmUgZGlzY2VybmlibGUgdGV4dCIsaGVscDoiQnV0dG9ucyBtdXN0IGhhdmUgZGlzY2VybmlibGUgdGV4dCJ9LGJ5cGFzczp7ZGVzY3JpcHRpb246IkVuc3VyZXMgZWFjaCBwYWdlIGhhcyBhdCBsZWFzdCBvbmUgbWVjaGFuaXNtIGZvciBhIHVzZXIgdG8gYnlwYXNzIG5hdmlnYXRpb24gYW5kIGp1bXAgc3RyYWlnaHQgdG8gdGhlIGNvbnRlbnQiLGhlbHA6IlBhZ2UgbXVzdCBoYXZlIG1lYW5zIHRvIGJ5cGFzcyByZXBlYXRlZCBibG9ja3MifSxjaGVja2JveGdyb3VwOntkZXNjcmlwdGlvbjonRW5zdXJlcyByZWxhdGVkIDxpbnB1dCB0eXBlPSJjaGVja2JveCI+IGVsZW1lbnRzIGhhdmUgYSBncm91cCBhbmQgdGhhdCB0aGF0IGdyb3VwIGRlc2lnbmF0aW9uIGlzIGNvbnNpc3RlbnQnLGhlbHA6IkNoZWNrYm94IGlucHV0cyB3aXRoIHRoZSBzYW1lIG5hbWUgYXR0cmlidXRlIHZhbHVlIG11c3QgYmUgcGFydCBvZiBhIGdyb3VwIn0sImNvbG9yLWNvbnRyYXN0Ijp7ZGVzY3JpcHRpb246IkVuc3VyZXMgdGhlIGNvbnRyYXN0IGJldHdlZW4gZm9yZWdyb3VuZCBhbmQgYmFja2dyb3VuZCBjb2xvcnMgbWVldHMgV0NBRyAyIEFBIGNvbnRyYXN0IHJhdGlvIHRocmVzaG9sZHMiLGhlbHA6IkVsZW1lbnRzIG11c3QgaGF2ZSBzdWZmaWNpZW50IGNvbG9yIGNvbnRyYXN0In0sImRlZmluaXRpb24tbGlzdCI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxkbD4gZWxlbWVudHMgYXJlIHN0cnVjdHVyZWQgY29ycmVjdGx5IixoZWxwOiI8ZGw+IGVsZW1lbnRzIG11c3Qgb25seSBkaXJlY3RseSBjb250YWluIHByb3Blcmx5LW9yZGVyZWQgPGR0PiBhbmQgPGRkPiBncm91cHMsIDxzY3JpcHQ+IG9yIDx0ZW1wbGF0ZT4gZWxlbWVudHMifSxkbGl0ZW06e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxkdD4gYW5kIDxkZD4gZWxlbWVudHMgYXJlIGNvbnRhaW5lZCBieSBhIDxkbD4iLGhlbHA6IjxkdD4gYW5kIDxkZD4gZWxlbWVudHMgbXVzdCBiZSBjb250YWluZWQgYnkgYSA8ZGw+In0sImRvY3VtZW50LXRpdGxlIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgZWFjaCBIVE1MIGRvY3VtZW50IGNvbnRhaW5zIGEgbm9uLWVtcHR5IDx0aXRsZT4gZWxlbWVudCIsaGVscDoiRG9jdW1lbnRzIG11c3QgaGF2ZSA8dGl0bGU+IGVsZW1lbnQgdG8gYWlkIGluIG5hdmlnYXRpb24ifSwiZHVwbGljYXRlLWlkIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgZXZlcnkgaWQgYXR0cmlidXRlIHZhbHVlIGlzIHVuaXF1ZSIsaGVscDoiaWQgYXR0cmlidXRlIHZhbHVlIG11c3QgYmUgdW5pcXVlIn0sImVtcHR5LWhlYWRpbmciOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBoZWFkaW5ncyBoYXZlIGRpc2Nlcm5pYmxlIHRleHQiLGhlbHA6IkhlYWRpbmdzIG11c3Qgbm90IGJlIGVtcHR5In0sImZyYW1lLXRpdGxlLXVuaXF1ZSI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxpZnJhbWU+IGFuZCA8ZnJhbWU+IGVsZW1lbnRzIGNvbnRhaW4gYSB1bmlxdWUgdGl0bGUgYXR0cmlidXRlIixoZWxwOiJGcmFtZXMgbXVzdCBoYXZlIGEgdW5pcXVlIHRpdGxlIGF0dHJpYnV0ZSJ9LCJmcmFtZS10aXRsZSI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxpZnJhbWU+IGFuZCA8ZnJhbWU+IGVsZW1lbnRzIGNvbnRhaW4gYSBub24tZW1wdHkgdGl0bGUgYXR0cmlidXRlIixoZWxwOiJGcmFtZXMgbXVzdCBoYXZlIHRpdGxlIGF0dHJpYnV0ZSJ9LCJoZWFkaW5nLW9yZGVyIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgdGhlIG9yZGVyIG9mIGhlYWRpbmdzIGlzIHNlbWFudGljYWxseSBjb3JyZWN0IixoZWxwOiJIZWFkaW5nIGxldmVscyBzaG91bGQgb25seSBpbmNyZWFzZSBieSBvbmUifSwiaHJlZi1uby1oYXNoIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgdGhhdCBocmVmIHZhbHVlcyBhcmUgdmFsaWQgbGluayByZWZlcmVuY2VzIHRvIHByb21vdGUgb25seSB1c2luZyBhbmNob3JzIGFzIGxpbmtzIixoZWxwOiJBbmNob3JzIG11c3Qgb25seSBiZSB1c2VkIGFzIGxpbmtzIGFuZCBtdXN0IHRoZXJlZm9yZSBoYXZlIGFuIGhyZWYgdmFsdWUgdGhhdCBpcyBhIHZhbGlkIHJlZmVyZW5jZS4gT3RoZXJ3aXNlIHlvdSBzaG91bGQgcHJvYmFibHkgdXNhIGEgYnV0dG9uIn0sImh0bWwtaGFzLWxhbmciOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBldmVyeSBIVE1MIGRvY3VtZW50IGhhcyBhIGxhbmcgYXR0cmlidXRlIixoZWxwOiI8aHRtbD4gZWxlbWVudCBtdXN0IGhhdmUgYSBsYW5nIGF0dHJpYnV0ZSJ9LCJodG1sLWxhbmctdmFsaWQiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyB0aGUgbGFuZyBhdHRyaWJ1dGUgb2YgdGhlIDxodG1sPiBlbGVtZW50IGhhcyBhIHZhbGlkIHZhbHVlIixoZWxwOiI8aHRtbD4gZWxlbWVudCBtdXN0IGhhdmUgYSB2YWxpZCB2YWx1ZSBmb3IgdGhlIGxhbmcgYXR0cmlidXRlIn0sImltYWdlLWFsdCI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxpbWc+IGVsZW1lbnRzIGhhdmUgYWx0ZXJuYXRlIHRleHQgb3IgYSByb2xlIG9mIG5vbmUgb3IgcHJlc2VudGF0aW9uIixoZWxwOiJJbWFnZXMgbXVzdCBoYXZlIGFsdGVybmF0ZSB0ZXh0In0sImltYWdlLXJlZHVuZGFudC1hbHQiOntkZXNjcmlwdGlvbjoiRW5zdXJlIGJ1dHRvbiBhbmQgbGluayB0ZXh0IGlzIG5vdCByZXBlYXRlZCBhcyBpbWFnZSBhbHRlcm5hdGl2ZSIsaGVscDoiVGV4dCBvZiBidXR0b25zIGFuZCBsaW5rcyBzaG91bGQgbm90IGJlIHJlcGVhdGVkIGluIHRoZSBpbWFnZSBhbHRlcm5hdGl2ZSJ9LCJpbnB1dC1pbWFnZS1hbHQiOntkZXNjcmlwdGlvbjonRW5zdXJlcyA8aW5wdXQgdHlwZT0iaW1hZ2UiPiBlbGVtZW50cyBoYXZlIGFsdGVybmF0ZSB0ZXh0JyxoZWxwOiJJbWFnZSBidXR0b25zIG11c3QgaGF2ZSBhbHRlcm5hdGUgdGV4dCJ9LCJsYWJlbC10aXRsZS1vbmx5Ijp7ZGVzY3JpcHRpb246IkVuc3VyZXMgdGhhdCBldmVyeSBmb3JtIGVsZW1lbnQgaXMgbm90IHNvbGVseSBsYWJlbGVkIHVzaW5nIHRoZSB0aXRsZSBvciBhcmlhLWRlc2NyaWJlZGJ5IGF0dHJpYnV0ZXMiLGhlbHA6IkZvcm0gZWxlbWVudHMgc2hvdWxkIGhhdmUgYSB2aXNpYmxlIGxhYmVsIn0sbGFiZWw6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGV2ZXJ5IGZvcm0gZWxlbWVudCBoYXMgYSBsYWJlbCIsaGVscDoiRm9ybSBlbGVtZW50cyBtdXN0IGhhdmUgbGFiZWxzIn0sImxheW91dC10YWJsZSI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIHByZXNlbnRhdGlvbmFsIDx0YWJsZT4gZWxlbWVudHMgZG8gbm90IHVzZSA8dGg+LCA8Y2FwdGlvbj4gZWxlbWVudHMgb3IgdGhlIHN1bW1hcnkgYXR0cmlidXRlIixoZWxwOiJMYXlvdXQgdGFibGVzIG11c3Qgbm90IHVzZSBkYXRhIHRhYmxlIGVsZW1lbnRzIn0sImxpbmstaW4tdGV4dC1ibG9jayI6e2Rlc2NyaXB0aW9uOiJMaW5rcyBjYW4gYmUgZGlzdGluZ3Vpc2hlZCB3aXRob3V0IHJlbHlpbmcgb24gY29sb3IiLGhlbHA6IkxpbmtzIG11c3QgYmUgZGlzdGluZ3Vpc2hlZCBmcm9tIHN1cnJvdW5kaW5nIHRleHQgaW4gYSB3YXkgdGhhdCBkb2VzIG5vdCByZWx5IG9uIGNvbG9yIn0sImxpbmstbmFtZSI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGxpbmtzIGhhdmUgZGlzY2VybmlibGUgdGV4dCIsaGVscDoiTGlua3MgbXVzdCBoYXZlIGRpc2Nlcm5pYmxlIHRleHQifSxsaXN0OntkZXNjcmlwdGlvbjoiRW5zdXJlcyB0aGF0IGxpc3RzIGFyZSBzdHJ1Y3R1cmVkIGNvcnJlY3RseSIsaGVscDoiPHVsPiBhbmQgPG9sPiBtdXN0IG9ubHkgZGlyZWN0bHkgY29udGFpbiA8bGk+LCA8c2NyaXB0PiBvciA8dGVtcGxhdGU+IGVsZW1lbnRzIn0sbGlzdGl0ZW06e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxsaT4gZWxlbWVudHMgYXJlIHVzZWQgc2VtYW50aWNhbGx5IixoZWxwOiI8bGk+IGVsZW1lbnRzIG11c3QgYmUgY29udGFpbmVkIGluIGEgPHVsPiBvciA8b2w+In0sbWFycXVlZTp7ZGVzY3JpcHRpb246IkVuc3VyZXMgPG1hcnF1ZWU+IGVsZW1lbnRzIGFyZSBub3QgdXNlZCIsaGVscDoiPG1hcnF1ZWU+IGVsZW1lbnRzIGFyZSBkZXByZWNhdGVkIGFuZCBtdXN0IG5vdCBiZSB1c2VkIn0sIm1ldGEtcmVmcmVzaCI6e2Rlc2NyaXB0aW9uOidFbnN1cmVzIDxtZXRhIGh0dHAtZXF1aXY9InJlZnJlc2giPiBpcyBub3QgdXNlZCcsaGVscDoiVGltZWQgcmVmcmVzaCBtdXN0IG5vdCBleGlzdCJ9LCJtZXRhLXZpZXdwb3J0LWxhcmdlIjp7ZGVzY3JpcHRpb246J0Vuc3VyZXMgPG1ldGEgbmFtZT0idmlld3BvcnQiPiBjYW4gc2NhbGUgYSBzaWduaWZpY2FudCBhbW91bnQnLGhlbHA6IlVzZXJzIHNob3VsZCBiZSBhYmxlIHRvIHpvb20gYW5kIHNjYWxlIHRoZSB0ZXh0IHVwIHRvIDUwMCUifSwibWV0YS12aWV3cG9ydCI6e2Rlc2NyaXB0aW9uOidFbnN1cmVzIDxtZXRhIG5hbWU9InZpZXdwb3J0Ij4gZG9lcyBub3QgZGlzYWJsZSB0ZXh0IHNjYWxpbmcgYW5kIHpvb21pbmcnLGhlbHA6Ilpvb21pbmcgYW5kIHNjYWxpbmcgbXVzdCBub3QgYmUgZGlzYWJsZWQifSwib2JqZWN0LWFsdCI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxvYmplY3Q+IGVsZW1lbnRzIGhhdmUgYWx0ZXJuYXRlIHRleHQiLGhlbHA6IjxvYmplY3Q+IGVsZW1lbnRzIG11c3QgaGF2ZSBhbHRlcm5hdGUgdGV4dCJ9LHJhZGlvZ3JvdXA6e2Rlc2NyaXB0aW9uOidFbnN1cmVzIHJlbGF0ZWQgPGlucHV0IHR5cGU9InJhZGlvIj4gZWxlbWVudHMgaGF2ZSBhIGdyb3VwIGFuZCB0aGF0IHRoZSBncm91cCBkZXNpZ25hdGlvbiBpcyBjb25zaXN0ZW50JyxoZWxwOiJSYWRpbyBpbnB1dHMgd2l0aCB0aGUgc2FtZSBuYW1lIGF0dHJpYnV0ZSB2YWx1ZSBtdXN0IGJlIHBhcnQgb2YgYSBncm91cCJ9LHJlZ2lvbjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgYWxsIGNvbnRlbnQgaXMgY29udGFpbmVkIHdpdGhpbiBhIGxhbmRtYXJrIHJlZ2lvbiIsaGVscDoiQ29udGVudCBzaG91bGQgYmUgY29udGFpbmVkIGluIGEgbGFuZG1hcmsgcmVnaW9uIn0sInNjb3BlLWF0dHItdmFsaWQiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyB0aGUgc2NvcGUgYXR0cmlidXRlIGlzIHVzZWQgY29ycmVjdGx5IG9uIHRhYmxlcyIsaGVscDoic2NvcGUgYXR0cmlidXRlIHNob3VsZCBiZSB1c2VkIGNvcnJlY3RseSJ9LCJzZXJ2ZXItc2lkZS1pbWFnZS1tYXAiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyB0aGF0IHNlcnZlci1zaWRlIGltYWdlIG1hcHMgYXJlIG5vdCB1c2VkIixoZWxwOiJTZXJ2ZXItc2lkZSBpbWFnZSBtYXBzIG11c3Qgbm90IGJlIHVzZWQifSwic2tpcC1saW5rIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgdGhlIGZpcnN0IGxpbmsgb24gdGhlIHBhZ2UgaXMgYSBza2lwIGxpbmsiLGhlbHA6IlRoZSBwYWdlIHNob3VsZCBoYXZlIGEgc2tpcCBsaW5rIGFzIGl0cyBmaXJzdCBsaW5rIn0sdGFiaW5kZXg6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIHRhYmluZGV4IGF0dHJpYnV0ZSB2YWx1ZXMgYXJlIG5vdCBncmVhdGVyIHRoYW4gMCIsaGVscDoiRWxlbWVudHMgc2hvdWxkIG5vdCBoYXZlIHRhYmluZGV4IGdyZWF0ZXIgdGhhbiB6ZXJvIn0sInRhYmxlLWR1cGxpY2F0ZS1uYW1lIjp7ZGVzY3JpcHRpb246IkVuc3VyZSB0aGF0IHRhYmxlcyBkbyBub3QgaGF2ZSB0aGUgc2FtZSBzdW1tYXJ5IGFuZCBjYXB0aW9uIixoZWxwOiJUaGUgPGNhcHRpb24+IGVsZW1lbnQgc2hvdWxkIG5vdCBjb250YWluIHRoZSBzYW1lIHRleHQgYXMgdGhlIHN1bW1hcnkgYXR0cmlidXRlIn0sInRhYmxlLWZha2UtY2FwdGlvbiI6e2Rlc2NyaXB0aW9uOiJFbnN1cmUgdGhhdCB0YWJsZXMgd2l0aCBhIGNhcHRpb24gdXNlIHRoZSA8Y2FwdGlvbj4gZWxlbWVudC4iLGhlbHA6IkRhdGEgb3IgaGVhZGVyIGNlbGxzIHNob3VsZCBub3QgYmUgdXNlZCB0byBnaXZlIGNhcHRpb24gdG8gYSBkYXRhIHRhYmxlLiJ9LCJ0ZC1oYXMtaGVhZGVyIjp7ZGVzY3JpcHRpb246IkVuc3VyZSB0aGF0IGVhY2ggbm9uLWVtcHR5IGRhdGEgY2VsbCBpbiBhIGxhcmdlIHRhYmxlIGhhcyBvbmUgb3IgbW9yZSB0YWJsZSBoZWFkZXJzIixoZWxwOiJBbGwgbm9uLWVtcHR5IHRkIGVsZW1lbnQgaW4gdGFibGUgbGFyZ2VyIHRoYW4gMyBieSAzIG11c3QgaGF2ZSBhbiBhc3NvY2lhdGVkIHRhYmxlIGhlYWRlciJ9LCJ0ZC1oZWFkZXJzLWF0dHIiOntkZXNjcmlwdGlvbjoiRW5zdXJlIHRoYXQgZWFjaCBjZWxsIGluIGEgdGFibGUgdXNpbmcgdGhlIGhlYWRlcnMgcmVmZXJzIHRvIGFub3RoZXIgY2VsbCBpbiB0aGF0IHRhYmxlIixoZWxwOiJBbGwgY2VsbHMgaW4gYSB0YWJsZSBlbGVtZW50IHRoYXQgdXNlIHRoZSBoZWFkZXJzIGF0dHJpYnV0ZSBtdXN0IG9ubHkgcmVmZXIgdG8gb3RoZXIgY2VsbHMgb2YgdGhhdCBzYW1lIHRhYmxlIn0sInRoLWhhcy1kYXRhLWNlbGxzIjp7ZGVzY3JpcHRpb246IkVuc3VyZSB0aGF0IGVhY2ggdGFibGUgaGVhZGVyIGluIGEgZGF0YSB0YWJsZSByZWZlcnMgdG8gZGF0YSBjZWxscyIsaGVscDoiQWxsIHRoIGVsZW1lbnQgYW5kIGVsZW1lbnRzIHdpdGggcm9sZT1jb2x1bW5oZWFkZXIvcm93aGVhZGVyIG11c3QgZGF0YSBjZWxscyB3aGljaCBpdCBkZXNjcmliZXMifSwidmFsaWQtbGFuZyI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGxhbmcgYXR0cmlidXRlcyBoYXZlIHZhbGlkIHZhbHVlcyIsaGVscDoibGFuZyBhdHRyaWJ1dGUgbXVzdCBoYXZlIGEgdmFsaWQgdmFsdWUifSwidmlkZW8tY2FwdGlvbiI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDx2aWRlbz4gZWxlbWVudHMgaGF2ZSBjYXB0aW9ucyIsaGVscDoiPHZpZGVvPiBlbGVtZW50cyBtdXN0IGhhdmUgY2FwdGlvbnMifSwidmlkZW8tZGVzY3JpcHRpb24iOntkZXNjcmlwdGlvbjoiRW5zdXJlcyA8dmlkZW8+IGVsZW1lbnRzIGhhdmUgYXVkaW8gZGVzY3JpcHRpb25zIixoZWxwOiI8dmlkZW8+IGVsZW1lbnRzIG11c3QgaGF2ZSBhbiBhdWRpbyBkZXNjcmlwdGlvbiB0cmFjayJ9fSxjaGVja3M6e2FjY2Vzc2tleXM6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJBY2Nlc3NrZXkgYXR0cmlidXRlIHZhbHVlIGlzIHVuaXF1ZSI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkRvY3VtZW50IGhhcyBtdWx0aXBsZSBlbGVtZW50cyB3aXRoIHRoZSBzYW1lIGFjY2Vzc2tleSI7cmV0dXJuIGJ9fX0sIm5vbi1lbXB0eS1hbHQiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBoYXMgYSBub24tZW1wdHkgYWx0IGF0dHJpYnV0ZSI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaGFzIG5vIGFsdCBhdHRyaWJ1dGUgb3IgdGhlIGFsdCBhdHRyaWJ1dGUgaXMgZW1wdHkiO3JldHVybiBifX19LCJub24tZW1wdHktdGl0bGUiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBoYXMgYSB0aXRsZSBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGhhcyBubyB0aXRsZSBhdHRyaWJ1dGUgb3IgdGhlIHRpdGxlIGF0dHJpYnV0ZSBpcyBlbXB0eSI7cmV0dXJuIGJ9fX0sImFyaWEtbGFiZWwiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iYXJpYS1sYWJlbCBhdHRyaWJ1dGUgZXhpc3RzIGFuZCBpcyBub3QgZW1wdHkiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJhcmlhLWxhYmVsIGF0dHJpYnV0ZSBkb2VzIG5vdCBleGlzdCBvciBpcyBlbXB0eSI7cmV0dXJuIGJ9fX0sImFyaWEtbGFiZWxsZWRieSI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJhcmlhLWxhYmVsbGVkYnkgYXR0cmlidXRlIGV4aXN0cyBhbmQgcmVmZXJlbmNlcyBlbGVtZW50cyB0aGF0IGFyZSB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iYXJpYS1sYWJlbGxlZGJ5IGF0dHJpYnV0ZSBkb2VzIG5vdCBleGlzdCwgcmVmZXJlbmNlcyBlbGVtZW50cyB0aGF0IGRvIG5vdCBleGlzdCBvciByZWZlcmVuY2VzIGVsZW1lbnRzIHRoYXQgYXJlIGVtcHR5IG9yIG5vdCB2aXNpYmxlIjtyZXR1cm4gYn19fSwiYXJpYS1hbGxvd2VkLWF0dHIiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQVJJQSBhdHRyaWJ1dGVzIGFyZSB1c2VkIGNvcnJlY3RseSBmb3IgdGhlIGRlZmluZWQgcm9sZSI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkFSSUEgYXR0cmlidXRlIisoYS5kYXRhJiZhLmRhdGEubGVuZ3RoPjE/InMgYXJlIjoiIGlzIikrIiBub3QgYWxsb3dlZDoiLGM9YS5kYXRhO2lmKGMpZm9yKHZhciBkLGU9LTEsZj1jLmxlbmd0aC0xO2U8ZjspZD1jW2UrPTFdLGIrPSIgIitkO3JldHVybiBifX19LCJhcmlhLXJlcXVpcmVkLWF0dHIiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQWxsIHJlcXVpcmVkIEFSSUEgYXR0cmlidXRlcyBhcmUgcHJlc2VudCI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlJlcXVpcmVkIEFSSUEgYXR0cmlidXRlIisoYS5kYXRhJiZhLmRhdGEubGVuZ3RoPjE/InMiOiIiKSsiIG5vdCBwcmVzZW50OiIsYz1hLmRhdGE7aWYoYylmb3IodmFyIGQsZT0tMSxmPWMubGVuZ3RoLTE7ZTxmOylkPWNbZSs9MV0sYis9IiAiK2Q7cmV0dXJuIGJ9fX0sImFyaWEtcmVxdWlyZWQtY2hpbGRyZW4iOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iUmVxdWlyZWQgQVJJQSBjaGlsZHJlbiBhcmUgcHJlc2VudCI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlJlcXVpcmVkIEFSSUEgIisoYS5kYXRhJiZhLmRhdGEubGVuZ3RoPjE/ImNoaWxkcmVuIjoiY2hpbGQiKSsiIHJvbGUgbm90IHByZXNlbnQ6IixjPWEuZGF0YTtpZihjKWZvcih2YXIgZCxlPS0xLGY9Yy5sZW5ndGgtMTtlPGY7KWQ9Y1tlKz0xXSxiKz0iICIrZDtyZXR1cm4gYn19fSwiYXJpYS1yZXF1aXJlZC1wYXJlbnQiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iUmVxdWlyZWQgQVJJQSBwYXJlbnQgcm9sZSBwcmVzZW50IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iUmVxdWlyZWQgQVJJQSBwYXJlbnQiKyhhLmRhdGEmJmEuZGF0YS5sZW5ndGg+MT8icyI6IiIpKyIgcm9sZSBub3QgcHJlc2VudDoiLGM9YS5kYXRhO2lmKGMpZm9yKHZhciBkLGU9LTEsZj1jLmxlbmd0aC0xO2U8ZjspZD1jW2UrPTFdLGIrPSIgIitkO3JldHVybiBifX19LGludmFsaWRyb2xlOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQVJJQSByb2xlIGlzIHZhbGlkIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iUm9sZSBtdXN0IGJlIG9uZSBvZiB0aGUgdmFsaWQgQVJJQSByb2xlcyI7cmV0dXJuIGJ9fX0sYWJzdHJhY3Ryb2xlOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJBYnN0cmFjdCByb2xlcyBhcmUgbm90IHVzZWQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJBYnN0cmFjdCByb2xlcyBjYW5ub3QgYmUgZGlyZWN0bHkgdXNlZCI7cmV0dXJuIGJ9fX0sImFyaWEtdmFsaWQtYXR0ci12YWx1ZSI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJBUklBIGF0dHJpYnV0ZSB2YWx1ZXMgYXJlIHZhbGlkIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iSW52YWxpZCBBUklBIGF0dHJpYnV0ZSB2YWx1ZSIrKGEuZGF0YSYmYS5kYXRhLmxlbmd0aD4xPyJzIjoiIikrIjoiLGM9YS5kYXRhO2lmKGMpZm9yKHZhciBkLGU9LTEsZj1jLmxlbmd0aC0xO2U8ZjspZD1jW2UrPTFdLGIrPSIgIitkO3JldHVybiBifX19LCJhcmlhLXZhbGlkLWF0dHIiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQVJJQSBhdHRyaWJ1dGUgbmFtZSIrKGEuZGF0YSYmYS5kYXRhLmxlbmd0aD4xPyJzIjoiIikrIiBhcmUgdmFsaWQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJJbnZhbGlkIEFSSUEgYXR0cmlidXRlIG5hbWUiKyhhLmRhdGEmJmEuZGF0YS5sZW5ndGg+MT8icyI6IiIpKyI6IixjPWEuZGF0YTtpZihjKWZvcih2YXIgZCxlPS0xLGY9Yy5sZW5ndGgtMTtlPGY7KWQ9Y1tlKz0xXSxiKz0iICIrZDtyZXR1cm4gYn19fSxjYXB0aW9uOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iVGhlIG11bHRpbWVkaWEgZWxlbWVudCBoYXMgYSBjYXB0aW9ucyB0cmFjayI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlRoZSBtdWx0aW1lZGlhIGVsZW1lbnQgZG9lcyBub3QgaGF2ZSBhIGNhcHRpb25zIHRyYWNrIjtyZXR1cm4gYn19fSwiaXMtb24tc2NyZWVuIjp7aW1wYWN0OiJtaW5vciIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaXMgbm90IHZpc2libGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGlzIHZpc2libGUiO3JldHVybiBifX19LCJub24tZW1wdHktaWYtcHJlc2VudCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50ICI7cmV0dXJuIGIrPWEuZGF0YT8iaGFzIGEgbm9uLWVtcHR5IHZhbHVlIGF0dHJpYnV0ZSI6ImRvZXMgbm90IGhhdmUgYSB2YWx1ZSBhdHRyaWJ1dGUifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGhhcyBhIHZhbHVlIGF0dHJpYnV0ZSBhbmQgdGhlIHZhbHVlIGF0dHJpYnV0ZSBpcyBlbXB0eSI7cmV0dXJuIGJ9fX0sIm5vbi1lbXB0eS12YWx1ZSI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGhhcyBhIG5vbi1lbXB0eSB2YWx1ZSBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGhhcyBubyB2YWx1ZSBhdHRyaWJ1dGUgb3IgdGhlIHZhbHVlIGF0dHJpYnV0ZSBpcyBlbXB0eSI7cmV0dXJuIGJ9fX0sImJ1dHRvbi1oYXMtdmlzaWJsZS10ZXh0Ijp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaGFzIGlubmVyIHRleHQgdGhhdCBpcyB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBkb2VzIG5vdCBoYXZlIGlubmVyIHRleHQgdGhhdCBpcyB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjtyZXR1cm4gYn19fSwicm9sZS1wcmVzZW50YXRpb24iOntpbXBhY3Q6Im1vZGVyYXRlIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0nRWxlbWVudFwncyBkZWZhdWx0IHNlbWFudGljcyB3ZXJlIG92ZXJyaWRlbiB3aXRoIHJvbGU9InByZXNlbnRhdGlvbiInO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSdFbGVtZW50XCdzIGRlZmF1bHQgc2VtYW50aWNzIHdlcmUgbm90IG92ZXJyaWRkZW4gd2l0aCByb2xlPSJwcmVzZW50YXRpb24iJztyZXR1cm4gYn19fSwicm9sZS1ub25lIjp7aW1wYWN0OiJtb2RlcmF0ZSIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9J0VsZW1lbnRcJ3MgZGVmYXVsdCBzZW1hbnRpY3Mgd2VyZSBvdmVycmlkZW4gd2l0aCByb2xlPSJub25lIic7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9J0VsZW1lbnRcJ3MgZGVmYXVsdCBzZW1hbnRpY3Mgd2VyZSBub3Qgb3ZlcnJpZGRlbiB3aXRoIHJvbGU9Im5vbmUiJztyZXR1cm4gYn19fSwiZm9jdXNhYmxlLW5vLW5hbWUiOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGlzIG5vdCBpbiB0YWIgb3JkZXIgb3IgaGFzIGFjY2Vzc2libGUgdGV4dCI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaXMgaW4gdGFiIG9yZGVyIGFuZCBkb2VzIG5vdCBoYXZlIGFjY2Vzc2libGUgdGV4dCI7cmV0dXJuIGJ9fX0sImludGVybmFsLWxpbmstcHJlc2VudCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJWYWxpZCBza2lwIGxpbmsgZm91bmQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJObyB2YWxpZCBza2lwIGxpbmsgZm91bmQiO3JldHVybiBifX19LCJoZWFkZXItcHJlc2VudCI6e2ltcGFjdDoibW9kZXJhdGUiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJQYWdlIGhhcyBhIGhlYWRlciI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlBhZ2UgZG9lcyBub3QgaGF2ZSBhIGhlYWRlciI7cmV0dXJuIGJ9fX0sbGFuZG1hcms6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IlBhZ2UgaGFzIGEgbGFuZG1hcmsgcmVnaW9uIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iUGFnZSBkb2VzIG5vdCBoYXZlIGEgbGFuZG1hcmsgcmVnaW9uIjtyZXR1cm4gYn19fSwiZ3JvdXAtbGFiZWxsZWRieSI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSdBbGwgZWxlbWVudHMgd2l0aCB0aGUgbmFtZSAiJythLmRhdGEubmFtZSsnIiByZWZlcmVuY2UgdGhlIHNhbWUgZWxlbWVudCB3aXRoIGFyaWEtbGFiZWxsZWRieSc7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9J0FsbCBlbGVtZW50cyB3aXRoIHRoZSBuYW1lICInK2EuZGF0YS5uYW1lKyciIGRvIG5vdCByZWZlcmVuY2UgdGhlIHNhbWUgZWxlbWVudCB3aXRoIGFyaWEtbGFiZWxsZWRieSc7cmV0dXJuIGJ9fX0sZmllbGRzZXQ6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGlzIGNvbnRhaW5lZCBpbiBhIGZpZWxkc2V0IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iIixjPWEuZGF0YSYmYS5kYXRhLmZhaWx1cmVDb2RlO3JldHVybiBiKz0ibm8tbGVnZW5kIj09PWM/IkZpZWxkc2V0IGRvZXMgbm90IGhhdmUgYSBsZWdlbmQgYXMgaXRzIGZpcnN0IGNoaWxkIjoiZW1wdHktbGVnZW5kIj09PWM/IkxlZ2VuZCBkb2VzIG5vdCBoYXZlIHRleHQgdGhhdCBpcyB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjoibWl4ZWQtaW5wdXRzIj09PWM/IkZpZWxkc2V0IGNvbnRhaW5zIHVucmVsYXRlZCBpbnB1dHMiOiJuby1ncm91cC1sYWJlbCI9PT1jPyJBUklBIGdyb3VwIGRvZXMgbm90IGhhdmUgYXJpYS1sYWJlbCBvciBhcmlhLWxhYmVsbGVkYnkiOiJncm91cC1taXhlZC1pbnB1dHMiPT09Yz8iQVJJQSBncm91cCBjb250YWlucyB1bnJlbGF0ZWQgaW5wdXRzIjoiRWxlbWVudCBkb2VzIG5vdCBoYXZlIGEgY29udGFpbmluZyBmaWVsZHNldCBvciBBUklBIGdyb3VwIn19fSwiY29sb3ItY29udHJhc3QiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iIjtyZXR1cm4gYis9YS5kYXRhJiZhLmRhdGEuY29udHJhc3RSYXRpbz8iRWxlbWVudCBoYXMgc3VmZmljaWVudCBjb2xvciBjb250cmFzdCBvZiAiK2EuZGF0YS5jb250cmFzdFJhdGlvOiJVbmFibGUgdG8gZGV0ZXJtaW5lIGNvbnRyYXN0IHJhdGlvIn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBoYXMgaW5zdWZmaWNpZW50IGNvbG9yIGNvbnRyYXN0IG9mICIrYS5kYXRhLmNvbnRyYXN0UmF0aW8rIiAoZm9yZWdyb3VuZCBjb2xvcjogIithLmRhdGEuZmdDb2xvcisiLCBiYWNrZ3JvdW5kIGNvbG9yOiAiK2EuZGF0YS5iZ0NvbG9yKyIsIGZvbnQgc2l6ZTogIithLmRhdGEuZm9udFNpemUrIiwgZm9udCB3ZWlnaHQ6ICIrYS5kYXRhLmZvbnRXZWlnaHQrIikiO3JldHVybiBifX19LCJzdHJ1Y3R1cmVkLWRsaXRlbXMiOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJXaGVuIG5vdCBlbXB0eSwgZWxlbWVudCBoYXMgYm90aCA8ZHQ+IGFuZCA8ZGQ+IGVsZW1lbnRzIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iV2hlbiBub3QgZW1wdHksIGVsZW1lbnQgZG9lcyBub3QgaGF2ZSBhdCBsZWFzdCBvbmUgPGR0PiBlbGVtZW50IGZvbGxvd2VkIGJ5IGF0IGxlYXN0IG9uZSA8ZGQ+IGVsZW1lbnQiO3JldHVybiBifX19LCJvbmx5LWRsaXRlbXMiOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJMaXN0IGVsZW1lbnQgb25seSBoYXMgZGlyZWN0IGNoaWxkcmVuIHRoYXQgYXJlIGFsbG93ZWQgaW5zaWRlIDxkdD4gb3IgPGRkPiBlbGVtZW50cyI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9Ikxpc3QgZWxlbWVudCBoYXMgZGlyZWN0IGNoaWxkcmVuIHRoYXQgYXJlIG5vdCBhbGxvd2VkIGluc2lkZSA8ZHQ+IG9yIDxkZD4gZWxlbWVudHMiO3JldHVybiBifX19LGRsaXRlbTp7aW1wYWN0OiJzZXJpb3VzIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iRGVzY3JpcHRpb24gbGlzdCBpdGVtIGhhcyBhIDxkbD4gcGFyZW50IGVsZW1lbnQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJEZXNjcmlwdGlvbiBsaXN0IGl0ZW0gZG9lcyBub3QgaGF2ZSBhIDxkbD4gcGFyZW50IGVsZW1lbnQiO3JldHVybiBifX19LCJkb2MtaGFzLXRpdGxlIjp7aW1wYWN0OiJtb2RlcmF0ZSIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkRvY3VtZW50IGhhcyBhIG5vbi1lbXB0eSA8dGl0bGU+IGVsZW1lbnQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJEb2N1bWVudCBkb2VzIG5vdCBoYXZlIGEgbm9uLWVtcHR5IDx0aXRsZT4gZWxlbWVudCI7cmV0dXJuIGJ9fX0sImR1cGxpY2F0ZS1pZCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJEb2N1bWVudCBoYXMgbm8gZWxlbWVudHMgdGhhdCBzaGFyZSB0aGUgc2FtZSBpZCBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJEb2N1bWVudCBoYXMgbXVsdGlwbGUgZWxlbWVudHMgd2l0aCB0aGUgc2FtZSBpZCBhdHRyaWJ1dGU6ICIrYS5kYXRhO3JldHVybiBifX19LCJoYXMtdmlzaWJsZS10ZXh0Ijp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaGFzIHRleHQgdGhhdCBpcyB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBkb2VzIG5vdCBoYXZlIHRleHQgdGhhdCBpcyB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjtyZXR1cm4gYn19fSwidW5pcXVlLWZyYW1lLXRpdGxlIjp7aW1wYWN0OiJzZXJpb3VzIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCdzIHRpdGxlIGF0dHJpYnV0ZSBpcyB1bmlxdWUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50J3MgdGl0bGUgYXR0cmlidXRlIGlzIG5vdCB1bmlxdWUiO3JldHVybiBifX19LCJoZWFkaW5nLW9yZGVyIjp7aW1wYWN0OiJtaW5vciIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkhlYWRpbmcgb3JkZXIgdmFsaWQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJIZWFkaW5nIG9yZGVyIGludmFsaWQiO3JldHVybiBifX19LCJocmVmLW5vLWhhc2giOntpbXBhY3Q6Im1vZGVyYXRlIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQW5jaG9yIGRvZXMgbm90IGhhdmUgYSBocmVmIHF1YWxzICMiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJBbmNob3IgaGFzIGEgaHJlZiBxdWFscyAjIjtyZXR1cm4gYn19fSwiaGFzLWxhbmciOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJUaGUgPGh0bWw+IGVsZW1lbnQgaGFzIGEgbGFuZyBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJUaGUgPGh0bWw+IGVsZW1lbnQgZG9lcyBub3QgaGF2ZSBhIGxhbmcgYXR0cmlidXRlIjtyZXR1cm4gYn19fSwidmFsaWQtbGFuZyI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IlZhbHVlIG9mIGxhbmcgYXR0cmlidXRlIGlzIGluY2x1ZGVkIGluIHRoZSBsaXN0IG9mIHZhbGlkIGxhbmd1YWdlcyI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlZhbHVlIG9mIGxhbmcgYXR0cmlidXRlIG5vdCBpbmNsdWRlZCBpbiB0aGUgbGlzdCBvZiB2YWxpZCBsYW5ndWFnZXMiO3JldHVybiBifX19LCJoYXMtYWx0Ijp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaGFzIGFuIGFsdCBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGRvZXMgbm90IGhhdmUgYW4gYWx0IGF0dHJpYnV0ZSI7cmV0dXJuIGJ9fX0sImR1cGxpY2F0ZS1pbWctbGFiZWwiOntpbXBhY3Q6Im1pbm9yIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBkb2VzIG5vdCBkdXBsaWNhdGUgZXhpc3RpbmcgdGV4dCBpbiA8aW1nPiBhbHQgdGV4dCI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgY29udGFpbnMgPGltZz4gZWxlbWVudCB3aXRoIGFsdCB0ZXh0IHRoYXQgZHVwbGljYXRlcyBleGlzdGluZyB0ZXh0IjtyZXR1cm4gYn19fSwidGl0bGUtb25seSI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkZvcm0gZWxlbWVudCBkb2VzIG5vdCBzb2xlbHkgdXNlIHRpdGxlIGF0dHJpYnV0ZSBmb3IgaXRzIGxhYmVsIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iT25seSB0aXRsZSB1c2VkIHRvIGdlbmVyYXRlIGxhYmVsIGZvciBmb3JtIGVsZW1lbnQiO3JldHVybiBifX19LCJpbXBsaWNpdC1sYWJlbCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJGb3JtIGVsZW1lbnQgaGFzIGFuIGltcGxpY2l0ICh3cmFwcGVkKSA8bGFiZWw+IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRm9ybSBlbGVtZW50IGRvZXMgbm90IGhhdmUgYW4gaW1wbGljaXQgKHdyYXBwZWQpIDxsYWJlbD4iO3JldHVybiBifX19LCJleHBsaWNpdC1sYWJlbCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJGb3JtIGVsZW1lbnQgaGFzIGFuIGV4cGxpY2l0IDxsYWJlbD4iO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJGb3JtIGVsZW1lbnQgZG9lcyBub3QgaGF2ZSBhbiBleHBsaWNpdCA8bGFiZWw+IjtyZXR1cm4gYn19fSwiaGVscC1zYW1lLWFzLWxhYmVsIjp7aW1wYWN0OiJtaW5vciIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkhlbHAgdGV4dCAodGl0bGUgb3IgYXJpYS1kZXNjcmliZWRieSkgZG9lcyBub3QgZHVwbGljYXRlIGxhYmVsIHRleHQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJIZWxwIHRleHQgKHRpdGxlIG9yIGFyaWEtZGVzY3JpYmVkYnkpIHRleHQgaXMgdGhlIHNhbWUgYXMgdGhlIGxhYmVsIHRleHQiO3JldHVybiBifX19LCJtdWx0aXBsZS1sYWJlbCI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkZvcm0gZWxlbWVudCBkb2VzIG5vdCBoYXZlIG11bHRpcGxlIDxsYWJlbD4gZWxlbWVudHMiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJGb3JtIGVsZW1lbnQgaGFzIG11bHRpcGxlIDxsYWJlbD4gZWxlbWVudHMiO3JldHVybiBifX19LCJoYXMtdGgiOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJMYXlvdXQgdGFibGUgZG9lcyBub3QgdXNlIDx0aD4gZWxlbWVudHMiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJMYXlvdXQgdGFibGUgdXNlcyA8dGg+IGVsZW1lbnRzIjtyZXR1cm4gYn19fSwiaGFzLWNhcHRpb24iOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJMYXlvdXQgdGFibGUgZG9lcyBub3QgdXNlIDxjYXB0aW9uPiBlbGVtZW50IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iTGF5b3V0IHRhYmxlIHVzZXMgPGNhcHRpb24+IGVsZW1lbnQiO3JldHVybiBifX19LCJoYXMtc3VtbWFyeSI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkxheW91dCB0YWJsZSBkb2VzIG5vdCB1c2Ugc3VtbWFyeSBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJMYXlvdXQgdGFibGUgdXNlcyBzdW1tYXJ5IGF0dHJpYnV0ZSI7cmV0dXJuIGJ9fX0sImxpbmstaW4tdGV4dC1ibG9jayI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJMaW5rcyBjYW4gYmUgZGlzdGluZ3Vpc2hlZCBmcm9tIHN1cnJvdW5kaW5nIHRleHQgaW4gYSB3YXkgdGhhdCBkb2VzIG5vdCByZWx5IG9uIGNvbG9yIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iTGlua3MgY2FuIG5vdCBiZSBkaXN0aW5ndWlzaGVkIGZyb20gc3Vycm91bmRpbmcgdGV4dCBpbiBhIHdheSB0aGF0IGRvZXMgbm90IHJlbHkgb24gY29sb3IiO3JldHVybiBifX19LCJvbmx5LWxpc3RpdGVtcyI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9Ikxpc3QgZWxlbWVudCBvbmx5IGhhcyBkaXJlY3QgY2hpbGRyZW4gdGhhdCBhcmUgYWxsb3dlZCBpbnNpZGUgPGxpPiBlbGVtZW50cyI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9Ikxpc3QgZWxlbWVudCBoYXMgZGlyZWN0IGNoaWxkcmVuIHRoYXQgYXJlIG5vdCBhbGxvd2VkIGluc2lkZSA8bGk+IGVsZW1lbnRzIjtyZXR1cm4gYn19fSxsaXN0aXRlbTp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9J0xpc3QgaXRlbSBoYXMgYSA8dWw+LCA8b2w+IG9yIHJvbGU9Imxpc3QiIHBhcmVudCBlbGVtZW50JztyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0nTGlzdCBpdGVtIGRvZXMgbm90IGhhdmUgYSA8dWw+LCA8b2w+IG9yIHJvbGU9Imxpc3QiIHBhcmVudCBlbGVtZW50JztyZXR1cm4gYn19fSwibWV0YS1yZWZyZXNoIjp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IjxtZXRhPiB0YWcgZG9lcyBub3QgaW1tZWRpYXRlbHkgcmVmcmVzaCB0aGUgcGFnZSI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IjxtZXRhPiB0YWcgZm9yY2VzIHRpbWVkIHJlZnJlc2ggb2YgcGFnZSI7cmV0dXJuIGJ9fX0sIm1ldGEtdmlld3BvcnQtbGFyZ2UiOntpbXBhY3Q6Im1pbm9yIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iPG1ldGE+IHRhZyBkb2VzIG5vdCBwcmV2ZW50IHNpZ25pZmljYW50IHpvb21pbmciO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSI8bWV0YT4gdGFnIGxpbWl0cyB6b29taW5nIjtyZXR1cm4gYn19fSwibWV0YS12aWV3cG9ydCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSI8bWV0YT4gdGFnIGRvZXMgbm90IGRpc2FibGUgem9vbWluZyI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IjxtZXRhPiB0YWcgZGlzYWJsZXMgem9vbWluZyI7cmV0dXJuIGJ9fX0scmVnaW9uOntpbXBhY3Q6Im1vZGVyYXRlIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQ29udGVudCBjb250YWluZWQgYnkgQVJJQSBsYW5kbWFyayI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkNvbnRlbnQgbm90IGNvbnRhaW5lZCBieSBhbiBBUklBIGxhbmRtYXJrIjtyZXR1cm4gYn19fSwiaHRtbDUtc2NvcGUiOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJTY29wZSBhdHRyaWJ1dGUgaXMgb25seSB1c2VkIG9uIHRhYmxlIGhlYWRlciBlbGVtZW50cyAoPHRoPikiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJJbiBIVE1MIDUsIHNjb3BlIGF0dHJpYnV0ZXMgbWF5IG9ubHkgYmUgdXNlZCBvbiB0YWJsZSBoZWFkZXIgZWxlbWVudHMgKDx0aD4pIjtyZXR1cm4gYn19fSwic2NvcGUtdmFsdWUiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iU2NvcGUgYXR0cmlidXRlIGlzIHVzZWQgY29ycmVjdGx5IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iVGhlIHZhbHVlIG9mIHRoZSBzY29wZSBhdHRyaWJ1dGUgbWF5IG9ubHkgYmUgJ3Jvdycgb3IgJ2NvbCciO3JldHVybiBifX19LGV4aXN0czp7aW1wYWN0OiJtaW5vciIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgZG9lcyBub3QgZXhpc3QiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGV4aXN0cyI7cmV0dXJuIGJ9fX0sInNraXAtbGluayI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJWYWxpZCBza2lwIGxpbmsgZm91bmQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJObyB2YWxpZCBza2lwIGxpbmsgZm91bmQiO3JldHVybiBifX19LHRhYmluZGV4OntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGRvZXMgbm90IGhhdmUgYSB0YWJpbmRleCBncmVhdGVyIHRoYW4gMCI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaGFzIGEgdGFiaW5kZXggZ3JlYXRlciB0aGFuIDAiO3JldHVybiBifX19LCJzYW1lLWNhcHRpb24tc3VtbWFyeSI6e2ltcGFjdDoibW9kZXJhdGUiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJDb250ZW50IG9mIHN1bW1hcnkgYXR0cmlidXRlIGFuZCA8Y2FwdGlvbj4gYXJlIG5vdCBkdXBsaWNhdGVkIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iQ29udGVudCBvZiBzdW1tYXJ5IGF0dHJpYnV0ZSBhbmQgPGNhcHRpb24+IGVsZW1lbnQgYXJlIGlkZW50aWNhbCI7cmV0dXJuIGJ9fX0sImNhcHRpb24tZmFrZWQiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iVGhlIGZpcnN0IHJvdyBvZiBhIHRhYmxlIGlzIG5vdCB1c2VkIGFzIGEgY2FwdGlvbiI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlRoZSBmaXJzdCByb3cgb2YgdGhlIHRhYmxlIHNob3VsZCBiZSBhIGNhcHRpb24gaW5zdGVhZCBvZiBhIHRhYmxlIGNlbGwiO3JldHVybiBifX19LCJ0ZC1oYXMtaGVhZGVyIjp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkFsbCBub24tZW1wdHkgZGF0YSBjZWxscyBoYXZlIHRhYmxlIGhlYWRlcnMiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJTb21lIG5vbi1lbXB0eSBkYXRhIGNlbGxzIGRvIG5vdCBoYXZlIHRhYmxlIGhlYWRlcnMiO3JldHVybiBifX19LCJ0ZC1oZWFkZXJzLWF0dHIiOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJUaGUgaGVhZGVycyBhdHRyaWJ1dGUgaXMgZXhjbHVzaXZlbHkgdXNlZCB0byByZWZlciB0byBvdGhlciBjZWxscyBpbiB0aGUgdGFibGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJUaGUgaGVhZGVycyBhdHRyaWJ1dGUgaXMgbm90IGV4Y2x1c2l2ZWx5IHVzZWQgdG8gcmVmZXIgdG8gb3RoZXIgY2VsbHMgaW4gdGhlIHRhYmxlIjtyZXR1cm4gYn19fSwidGgtaGFzLWRhdGEtY2VsbHMiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQWxsIHRhYmxlIGhlYWRlciBjZWxscyByZWZlciB0byBkYXRhIGNlbGxzIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iTm90IGFsbCB0YWJsZSBoZWFkZXIgY2VsbHMgcmVmZXIgdG8gZGF0YSBjZWxscyI7cmV0dXJuIGJ9fX0sZGVzY3JpcHRpb246e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IlRoZSBtdWx0aW1lZGlhIGVsZW1lbnQgaGFzIGFuIGF1ZGlvIGRlc2NyaXB0aW9uIHRyYWNrIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iVGhlIG11bHRpbWVkaWEgZWxlbWVudCBkb2VzIG5vdCBoYXZlIGFuIGF1ZGlvIGRlc2NyaXB0aW9uIHRyYWNrIjtyZXR1cm4gYn19fX0sZmFpbHVyZVN1bW1hcmllczp7YW55OntmYWlsdXJlTWVzc2FnZTpmdW5jdGlvbihhKXt2YXIgYj0iRml4IGFueSBvZiB0aGUgZm9sbG93aW5nOiIsYz1hO2lmKGMpZm9yKHZhciBkLGU9LTEsZj1jLmxlbmd0aC0xO2U8ZjspZD1jW2UrPTFdLGIrPSJcbiAgIitkLnNwbGl0KCJcbiIpLmpvaW4oIlxuICAiKTtyZXR1cm4gYn19LG5vbmU6e2ZhaWx1cmVNZXNzYWdlOmZ1bmN0aW9uKGEpe3ZhciBiPSJGaXggYWxsIG9mIHRoZSBmb2xsb3dpbmc6IixjPWE7aWYoYylmb3IodmFyIGQsZT0tMSxmPWMubGVuZ3RoLTE7ZTxmOylkPWNbZSs9MV0sYis9IlxuICAiK2Quc3BsaXQoIlxuIikuam9pbigiXG4gICIpO3JldHVybiBifX19fSxydWxlczpbe2lkOiJhY2Nlc3NrZXlzIixzZWxlY3RvcjoiW2FjY2Vzc2tleV0iLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYSIsIndjYWcyMTEiXSxhbGw6W10sYW55OltdLG5vbmU6WyJhY2Nlc3NrZXlzIl19LHtpZDoiYXJlYS1hbHQiLApzZWxlY3RvcjoibWFwIGFyZWFbaHJlZl0iLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYSIsIndjYWcxMTEiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5hIl0sYWxsOltdLGFueTpbIm5vbi1lbXB0eS1hbHQiLCJub24tZW1wdHktdGl0bGUiLCJhcmlhLWxhYmVsIiwiYXJpYS1sYWJlbGxlZGJ5Il0sbm9uZTpbXX0se2lkOiJhcmlhLWFsbG93ZWQtYXR0ciIsbWF0Y2hlczpmdW5jdGlvbihhKXt2YXIgYj1hLmdldEF0dHJpYnV0ZSgicm9sZSIpO2J8fChiPWF4ZS5jb21tb25zLmFyaWEuaW1wbGljaXRSb2xlKGEpKTt2YXIgYz1heGUuY29tbW9ucy5hcmlhLmFsbG93ZWRBdHRyKGIpO2lmKGImJmMpe3ZhciBkPS9eYXJpYS0vO2lmKGEuaGFzQXR0cmlidXRlcygpKWZvcih2YXIgZT1hLmF0dHJpYnV0ZXMsZj0wLGc9ZS5sZW5ndGg7ZjxnO2YrKylpZihkLnRlc3QoZVtmXS5uYW1lKSlyZXR1cm4hMH1yZXR1cm4hMX0sdGFnczpbIndjYWcyYSIsIndjYWc0MTEiLCJ3Y2FnNDEyIl0sYWxsOltdLGFueTpbImFyaWEtYWxsb3dlZC1hdHRyIl0sbm9uZTpbXX0se2lkOiJhcmlhLXJlcXVpcmVkLWF0dHIiLHNlbGVjdG9yOiJbcm9sZV0iLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnNDExIiwid2NhZzQxMiJdLGFsbDpbXSxhbnk6WyJhcmlhLXJlcXVpcmVkLWF0dHIiXSxub25lOltdfSx7aWQ6ImFyaWEtcmVxdWlyZWQtY2hpbGRyZW4iLHNlbGVjdG9yOiJbcm9sZV0iLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTMxIl0sYWxsOltdLGFueTpbImFyaWEtcmVxdWlyZWQtY2hpbGRyZW4iXSxub25lOltdfSx7aWQ6ImFyaWEtcmVxdWlyZWQtcGFyZW50IixzZWxlY3RvcjoiW3JvbGVdIix0YWdzOlsid2NhZzJhIiwid2NhZzEzMSJdLGFsbDpbXSxhbnk6WyJhcmlhLXJlcXVpcmVkLXBhcmVudCJdLG5vbmU6W119LHtpZDoiYXJpYS1yb2xlcyIsc2VsZWN0b3I6Iltyb2xlXSIsdGFnczpbIndjYWcyYSIsIndjYWcxMzEiLCJ3Y2FnNDExIiwid2NhZzQxMiJdLGFsbDpbXSxhbnk6W10sbm9uZTpbImludmFsaWRyb2xlIiwiYWJzdHJhY3Ryb2xlIl19LHtpZDoiYXJpYS12YWxpZC1hdHRyLXZhbHVlIixtYXRjaGVzOmZ1bmN0aW9uKGEpe3ZhciBiPS9eYXJpYS0vO2lmKGEuaGFzQXR0cmlidXRlcygpKWZvcih2YXIgYz1hLmF0dHJpYnV0ZXMsZD0wLGU9Yy5sZW5ndGg7ZDxlO2QrKylpZihiLnRlc3QoY1tkXS5uYW1lKSlyZXR1cm4hMDtyZXR1cm4hMX0sdGFnczpbIndjYWcyYSIsIndjYWcxMzEiLCJ3Y2FnNDExIiwid2NhZzQxMiJdLGFsbDpbXSxhbnk6W3tvcHRpb25zOltdLGlkOiJhcmlhLXZhbGlkLWF0dHItdmFsdWUifV0sbm9uZTpbXX0se2lkOiJhcmlhLXZhbGlkLWF0dHIiLG1hdGNoZXM6ZnVuY3Rpb24oYSl7dmFyIGI9L15hcmlhLS87aWYoYS5oYXNBdHRyaWJ1dGVzKCkpZm9yKHZhciBjPWEuYXR0cmlidXRlcyxkPTAsZT1jLmxlbmd0aDtkPGU7ZCsrKWlmKGIudGVzdChjW2RdLm5hbWUpKXJldHVybiEwO3JldHVybiExfSx0YWdzOlsid2NhZzJhIiwid2NhZzQxMSJdLGFsbDpbXSxhbnk6W3tvcHRpb25zOltdLGlkOiJhcmlhLXZhbGlkLWF0dHIifV0sbm9uZTpbXX0se2lkOiJhdWRpby1jYXB0aW9uIixzZWxlY3RvcjoiYXVkaW8iLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYSIsIndjYWcxMjIiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5hIl0sYWxsOltdLGFueTpbXSxub25lOlsiY2FwdGlvbiJdfSx7aWQ6ImJsaW5rIixzZWxlY3RvcjoiYmxpbmsiLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYSIsIndjYWcyMjIiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5qIl0sYWxsOltdLGFueTpbXSxub25lOlsiaXMtb24tc2NyZWVuIl19LHtpZDoiYnV0dG9uLW5hbWUiLHNlbGVjdG9yOididXR0b24sIFtyb2xlPSJidXR0b24iXSwgaW5wdXRbdHlwZT0iYnV0dG9uIl0sIGlucHV0W3R5cGU9InN1Ym1pdCJdLCBpbnB1dFt0eXBlPSJyZXNldCJdJyx0YWdzOlsid2NhZzJhIiwid2NhZzQxMiIsInNlY3Rpb241MDgiLCJzZWN0aW9uNTA4LjIyLmEiXSxhbGw6W10sYW55Olsibm9uLWVtcHR5LWlmLXByZXNlbnQiLCJub24tZW1wdHktdmFsdWUiLCJidXR0b24taGFzLXZpc2libGUtdGV4dCIsImFyaWEtbGFiZWwiLCJhcmlhLWxhYmVsbGVkYnkiLCJyb2xlLXByZXNlbnRhdGlvbiIsInJvbGUtbm9uZSJdLG5vbmU6WyJmb2N1c2FibGUtbm8tbmFtZSJdfSx7aWQ6ImJ5cGFzcyIsc2VsZWN0b3I6Imh0bWwiLHBhZ2VMZXZlbDohMCxtYXRjaGVzOmZ1bmN0aW9uKGEpe3JldHVybiEhYS5xdWVyeVNlbGVjdG9yKCJhW2hyZWZdIil9LHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMjQxIiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIubyJdLGFsbDpbXSxhbnk6WyJpbnRlcm5hbC1saW5rLXByZXNlbnQiLCJoZWFkZXItcHJlc2VudCIsImxhbmRtYXJrIl0sbm9uZTpbXX0se2lkOiJjaGVja2JveGdyb3VwIixzZWxlY3RvcjoiaW5wdXRbdHlwZT1jaGVja2JveF1bbmFtZV0iLHRhZ3M6WyJiZXN0LXByYWN0aWNlIl0sYWxsOltdLGFueTpbImdyb3VwLWxhYmVsbGVkYnkiLCJmaWVsZHNldCJdLG5vbmU6W119LHtpZDoiY29sb3ItY29udHJhc3QiLG1hdGNoZXM6ZnVuY3Rpb24oYSl7dmFyIGI9YS5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpLGM9YS50eXBlLGQ9ZG9jdW1lbnQ7aWYoInRydWUiPT09YS5nZXRBdHRyaWJ1dGUoImFyaWEtZGlzYWJsZWQiKSlyZXR1cm4hMTtpZigiSU5QVVQiPT09YilyZXR1cm5bImhpZGRlbiIsInJhbmdlIiwiY29sb3IiLCJjaGVja2JveCIsInJhZGlvIiwiaW1hZ2UiXS5pbmRleE9mKGMpPT09LTEmJiFhLmRpc2FibGVkO2lmKCJTRUxFQ1QiPT09YilyZXR1cm4hIWEub3B0aW9ucy5sZW5ndGgmJiFhLmRpc2FibGVkO2lmKCJURVhUQVJFQSI9PT1iKXJldHVybiFhLmRpc2FibGVkO2lmKCJPUFRJT04iPT09YilyZXR1cm4hMTtpZigiQlVUVE9OIj09PWImJmEuZGlzYWJsZWQpcmV0dXJuITE7aWYoIkxBQkVMIj09PWIpe3ZhciBlPWEuaHRtbEZvciYmZC5nZXRFbGVtZW50QnlJZChhLmh0bWxGb3IpO2lmKGUmJmUuZGlzYWJsZWQpcmV0dXJuITE7dmFyIGU9YS5xdWVyeVNlbGVjdG9yKCdpbnB1dDpub3QoW3R5cGU9ImhpZGRlbiJdKTpub3QoW3R5cGU9ImltYWdlIl0pOm5vdChbdHlwZT0iYnV0dG9uIl0pOm5vdChbdHlwZT0ic3VibWl0Il0pOm5vdChbdHlwZT0icmVzZXQiXSksIHNlbGVjdCwgdGV4dGFyZWEnKTtpZihlJiZlLmRpc2FibGVkKXJldHVybiExfWlmKGEuaWQpe3ZhciBlPWQucXVlcnlTZWxlY3RvcigiW2FyaWEtbGFiZWxsZWRieX49IitheGUuY29tbW9ucy51dGlscy5lc2NhcGVTZWxlY3RvcihhLmlkKSsiXSIpO2lmKGUmJmUuZGlzYWJsZWQpcmV0dXJuITF9aWYoIiI9PT1heGUuY29tbW9ucy50ZXh0LnZpc2libGUoYSwhMSwhMCkpcmV0dXJuITE7dmFyIGYsZyxoPWRvY3VtZW50LmNyZWF0ZVJhbmdlKCksaT1hLmNoaWxkTm9kZXMsaj1pLmxlbmd0aDtmb3IoZz0wO2c8ajtnKyspZj1pW2ddLDM9PT1mLm5vZGVUeXBlJiYiIiE9PWF4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoZi5ub2RlVmFsdWUpJiZoLnNlbGVjdE5vZGVDb250ZW50cyhmKTt2YXIgaz1oLmdldENsaWVudFJlY3RzKCk7Zm9yKGo9ay5sZW5ndGgsZz0wO2c8ajtnKyspaWYoYXhlLmNvbW1vbnMuZG9tLnZpc3VhbGx5T3ZlcmxhcHMoa1tnXSxhKSlyZXR1cm4hMDtyZXR1cm4hMX0sZXhjbHVkZUhpZGRlbjohMSxvcHRpb25zOntub1Njcm9sbDohMX0sdGFnczpbIndjYWcyYWEiLCJ3Y2FnMTQzIl0sYWxsOltdLGFueTpbImNvbG9yLWNvbnRyYXN0Il0sbm9uZTpbXX0se2lkOiJkZWZpbml0aW9uLWxpc3QiLHNlbGVjdG9yOiJkbDpub3QoW3JvbGVdKSIsdGFnczpbIndjYWcyYSIsIndjYWcxMzEiXSxhbGw6W10sYW55OltdLG5vbmU6WyJzdHJ1Y3R1cmVkLWRsaXRlbXMiLCJvbmx5LWRsaXRlbXMiXX0se2lkOiJkbGl0ZW0iLHNlbGVjdG9yOiJkZDpub3QoW3JvbGVdKSwgZHQ6bm90KFtyb2xlXSkiLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTMxIl0sYWxsOltdLGFueTpbImRsaXRlbSJdLG5vbmU6W119LHtpZDoiZG9jdW1lbnQtdGl0bGUiLHNlbGVjdG9yOiJodG1sIixtYXRjaGVzOmZ1bmN0aW9uKGEpe3JldHVybiB3aW5kb3cuc2VsZj09PXdpbmRvdy50b3B9LHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMjQyIl0sYWxsOltdLGFueTpbImRvYy1oYXMtdGl0bGUiXSxub25lOltdfSx7aWQ6ImR1cGxpY2F0ZS1pZCIsc2VsZWN0b3I6IltpZF0iLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYSIsIndjYWc0MTEiXSxhbGw6W10sYW55OlsiZHVwbGljYXRlLWlkIl0sbm9uZTpbXX0se2lkOiJlbXB0eS1oZWFkaW5nIixzZWxlY3RvcjonaDEsIGgyLCBoMywgaDQsIGg1LCBoNiwgW3JvbGU9ImhlYWRpbmciXScsZW5hYmxlZDohMCx0YWdzOlsiYmVzdC1wcmFjdGljZSJdLGFsbDpbXSxhbnk6WyJoYXMtdmlzaWJsZS10ZXh0Iiwicm9sZS1wcmVzZW50YXRpb24iLCJyb2xlLW5vbmUiXSxub25lOltdfSx7aWQ6ImZyYW1lLXRpdGxlLXVuaXF1ZSIsc2VsZWN0b3I6ImZyYW1lW3RpdGxlXTpub3QoW3RpdGxlPScnXSksIGlmcmFtZVt0aXRsZV06bm90KFt0aXRsZT0nJ10pIixtYXRjaGVzOmZ1bmN0aW9uKGEpe3ZhciBiPWEuZ2V0QXR0cmlidXRlKCJ0aXRsZSIpO3JldHVybiEhKGI/YXhlLmNvbW1vbnMudGV4dC5zYW5pdGl6ZShiKS50cmltKCk6IiIpfSx0YWdzOlsiYmVzdC1wcmFjdGljZSJdLGFsbDpbXSxhbnk6W10sbm9uZTpbInVuaXF1ZS1mcmFtZS10aXRsZSJdfSx7aWQ6ImZyYW1lLXRpdGxlIixzZWxlY3RvcjoiZnJhbWUsIGlmcmFtZSIsdGFnczpbIndjYWcyYSIsIndjYWcyNDEiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5pIl0sYWxsOltdLGFueTpbImFyaWEtbGFiZWwiLCJhcmlhLWxhYmVsbGVkYnkiLCJub24tZW1wdHktdGl0bGUiLCJyb2xlLXByZXNlbnRhdGlvbiIsInJvbGUtbm9uZSJdLG5vbmU6W119LHtpZDoiaGVhZGluZy1vcmRlciIsc2VsZWN0b3I6ImgxLGgyLGgzLGg0LGg1LGg2LFtyb2xlPWhlYWRpbmddIixlbmFibGVkOiExLHRhZ3M6WyJiZXN0LXByYWN0aWNlIl0sYWxsOltdLGFueTpbImhlYWRpbmctb3JkZXIiXSxub25lOltdfSx7aWQ6ImhyZWYtbm8taGFzaCIsc2VsZWN0b3I6ImFbaHJlZl0iLGVuYWJsZWQ6ITEsdGFnczpbImJlc3QtcHJhY3RpY2UiXSxhbGw6W10sYW55OlsiaHJlZi1uby1oYXNoIl0sbm9uZTpbXX0se2lkOiJodG1sLWhhcy1sYW5nIixzZWxlY3RvcjoiaHRtbCIsdGFnczpbIndjYWcyYSIsIndjYWczMTEiXSxhbGw6W10sYW55OlsiaGFzLWxhbmciXSxub25lOltdfSx7aWQ6Imh0bWwtbGFuZy12YWxpZCIsc2VsZWN0b3I6Imh0bWxbbGFuZ10iLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMzExIl0sYWxsOltdLGFueTpbXSxub25lOlt7b3B0aW9uczpbImFhIiwiYWIiLCJhZSIsImFmIiwiYWsiLCJhbSIsImFuIiwiYXIiLCJhcyIsImF2IiwiYXkiLCJheiIsImJhIiwiYmUiLCJiZyIsImJoIiwiYmkiLCJibSIsImJuIiwiYm8iLCJiciIsImJzIiwiY2EiLCJjZSIsImNoIiwiY28iLCJjciIsImNzIiwiY3UiLCJjdiIsImN5IiwiZGEiLCJkZSIsImR2IiwiZHoiLCJlZSIsImVsIiwiZW4iLCJlbyIsImVzIiwiZXQiLCJldSIsImZhIiwiZmYiLCJmaSIsImZqIiwiZm8iLCJmciIsImZ5IiwiZ2EiLCJnZCIsImdsIiwiZ24iLCJndSIsImd2IiwiaGEiLCJoZSIsImhpIiwiaG8iLCJociIsImh0IiwiaHUiLCJoeSIsImh6IiwiaWEiLCJpZCIsImllIiwiaWciLCJpaSIsImlrIiwiaW4iLCJpbyIsImlzIiwiaXQiLCJpdSIsIml3IiwiamEiLCJqaSIsImp2IiwianciLCJrYSIsImtnIiwia2kiLCJraiIsImtrIiwia2wiLCJrbSIsImtuIiwia28iLCJrciIsImtzIiwia3UiLCJrdiIsImt3Iiwia3kiLCJsYSIsImxiIiwibGciLCJsaSIsImxuIiwibG8iLCJsdCIsImx1IiwibHYiLCJtZyIsIm1oIiwibWkiLCJtayIsIm1sIiwibW4iLCJtbyIsIm1yIiwibXMiLCJtdCIsIm15IiwibmEiLCJuYiIsIm5kIiwibmUiLCJuZyIsIm5sIiwibm4iLCJubyIsIm5yIiwibnYiLCJueSIsIm9jIiwib2oiLCJvbSIsIm9yIiwib3MiLCJwYSIsInBpIiwicGwiLCJwcyIsInB0IiwicXUiLCJybSIsInJuIiwicm8iLCJydSIsInJ3Iiwic2EiLCJzYyIsInNkIiwic2UiLCJzZyIsInNoIiwic2kiLCJzayIsInNsIiwic20iLCJzbiIsInNvIiwic3EiLCJzciIsInNzIiwic3QiLCJzdSIsInN2Iiwic3ciLCJ0YSIsInRlIiwidGciLCJ0aCIsInRpIiwidGsiLCJ0bCIsInRuIiwidG8iLCJ0ciIsInRzIiwidHQiLCJ0dyIsInR5IiwidWciLCJ1ayIsInVyIiwidXoiLCJ2ZSIsInZpIiwidm8iLCJ3YSIsIndvIiwieGgiLCJ5aSIsInlvIiwiemEiLCJ6aCIsInp1Il0saWQ6InZhbGlkLWxhbmcifV19LHtpZDoiaW1hZ2UtYWx0IixzZWxlY3RvcjoiaW1nIix0YWdzOlsid2NhZzJhIiwid2NhZzExMSIsInNlY3Rpb241MDgiLCJzZWN0aW9uNTA4LjIyLmEiXSxhbGw6W10sYW55OlsiaGFzLWFsdCIsImFyaWEtbGFiZWwiLCJhcmlhLWxhYmVsbGVkYnkiLCJub24tZW1wdHktdGl0bGUiLCJyb2xlLXByZXNlbnRhdGlvbiIsInJvbGUtbm9uZSJdLG5vbmU6W119LHtpZDoiaW1hZ2UtcmVkdW5kYW50LWFsdCIsc2VsZWN0b3I6J2J1dHRvbiwgW3JvbGU9ImJ1dHRvbiJdLCBhW2hyZWZdLCBwLCBsaSwgdGQsIHRoJyx0YWdzOlsiYmVzdC1wcmFjdGljZSJdLGFsbDpbXSxhbnk6W10sbm9uZTpbImR1cGxpY2F0ZS1pbWctbGFiZWwiXX0se2lkOiJpbnB1dC1pbWFnZS1hbHQiLHNlbGVjdG9yOidpbnB1dFt0eXBlPSJpbWFnZSJdJyx0YWdzOlsid2NhZzJhIiwid2NhZzExMSIsInNlY3Rpb241MDgiLCJzZWN0aW9uNTA4LjIyLmEiXSxhbGw6W10sYW55Olsibm9uLWVtcHR5LWFsdCIsImFyaWEtbGFiZWwiLCJhcmlhLWxhYmVsbGVkYnkiLCJub24tZW1wdHktdGl0bGUiXSxub25lOltdfSx7aWQ6ImxhYmVsLXRpdGxlLW9ubHkiLHNlbGVjdG9yOiJpbnB1dDpub3QoW3R5cGU9J2hpZGRlbiddKTpub3QoW3R5cGU9J2ltYWdlJ10pOm5vdChbdHlwZT0nYnV0dG9uJ10pOm5vdChbdHlwZT0nc3VibWl0J10pOm5vdChbdHlwZT0ncmVzZXQnXSksIHNlbGVjdCwgdGV4dGFyZWEiLGVuYWJsZWQ6ITEsdGFnczpbImJlc3QtcHJhY3RpY2UiXSxhbGw6W10sYW55OltdLG5vbmU6WyJ0aXRsZS1vbmx5Il19LHtpZDoibGFiZWwiLHNlbGVjdG9yOiJpbnB1dDpub3QoW3R5cGU9J2hpZGRlbiddKTpub3QoW3R5cGU9J2ltYWdlJ10pOm5vdChbdHlwZT0nYnV0dG9uJ10pOm5vdChbdHlwZT0nc3VibWl0J10pOm5vdChbdHlwZT0ncmVzZXQnXSksIHNlbGVjdCwgdGV4dGFyZWEiLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMzMyIiwid2NhZzEzMSIsInNlY3Rpb241MDgiLCJzZWN0aW9uNTA4LjIyLm4iXSxhbGw6W10sYW55OlsiYXJpYS1sYWJlbCIsImFyaWEtbGFiZWxsZWRieSIsImltcGxpY2l0LWxhYmVsIiwiZXhwbGljaXQtbGFiZWwiLCJub24tZW1wdHktdGl0bGUiXSxub25lOlsiaGVscC1zYW1lLWFzLWxhYmVsIiwibXVsdGlwbGUtbGFiZWwiXX0se2lkOiJsYXlvdXQtdGFibGUiLHNlbGVjdG9yOiJ0YWJsZSIsbWF0Y2hlczpmdW5jdGlvbihhKXtyZXR1cm4hYXhlLmNvbW1vbnMudGFibGUuaXNEYXRhVGFibGUoYSl9LHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTMxIl0sYWxsOltdLGFueTpbXSxub25lOlsiaGFzLXRoIiwiaGFzLWNhcHRpb24iLCJoYXMtc3VtbWFyeSJdfSx7aWQ6ImxpbmstaW4tdGV4dC1ibG9jayIsc2VsZWN0b3I6ImFbaHJlZl06bm90KFtyb2xlXSksICpbcm9sZT1saW5rXSIsbWF0Y2hlczpmdW5jdGlvbihhKXt2YXIgYj1heGUuY29tbW9ucy50ZXh0LnNhbml0aXplKGEudGV4dENvbnRlbnQpO3JldHVybiEhYiYmKCEhYXhlLmNvbW1vbnMuZG9tLmlzVmlzaWJsZShhLCExKSYmYXhlLmNvbW1vbnMuZG9tLmlzSW5UZXh0QmxvY2soYSkpfSxleGNsdWRlSGlkZGVuOiExLGVuYWJsZWQ6ITEsdGFnczpbImV4cGVyaW1lbnRhbCIsIndjYWcyYSIsIndjYWcxNDEiXSxhbGw6WyJsaW5rLWluLXRleHQtYmxvY2siXSxhbnk6W10sbm9uZTpbXX0se2lkOiJsaW5rLW5hbWUiLHNlbGVjdG9yOidhW2hyZWZdOm5vdChbcm9sZT0iYnV0dG9uIl0pLCBbcm9sZT1saW5rXVtocmVmXScsdGFnczpbIndjYWcyYSIsIndjYWcxMTEiLCJ3Y2FnNDEyIiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIuYSJdLGFsbDpbXSxhbnk6WyJoYXMtdmlzaWJsZS10ZXh0IiwiYXJpYS1sYWJlbCIsImFyaWEtbGFiZWxsZWRieSIsInJvbGUtcHJlc2VudGF0aW9uIiwicm9sZS1ub25lIl0sbm9uZTpbImZvY3VzYWJsZS1uby1uYW1lIl19LHtpZDoibGlzdCIsc2VsZWN0b3I6InVsOm5vdChbcm9sZV0pLCBvbDpub3QoW3JvbGVdKSIsdGFnczpbIndjYWcyYSIsIndjYWcxMzEiXSxhbGw6W10sYW55OltdLG5vbmU6WyJvbmx5LWxpc3RpdGVtcyJdfSx7aWQ6Imxpc3RpdGVtIixzZWxlY3RvcjoibGk6bm90KFtyb2xlXSkiLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTMxIl0sYWxsOltdLGFueTpbImxpc3RpdGVtIl0sbm9uZTpbXX0se2lkOiJtYXJxdWVlIixzZWxlY3RvcjoibWFycXVlZSIsZXhjbHVkZUhpZGRlbjohMSx0YWdzOlsid2NhZzJhIiwid2NhZzIyMiJdLGFsbDpbXSxhbnk6W10sbm9uZTpbImlzLW9uLXNjcmVlbiJdfSx7aWQ6Im1ldGEtcmVmcmVzaCIsc2VsZWN0b3I6J21ldGFbaHR0cC1lcXVpdj0icmVmcmVzaCJdJyxleGNsdWRlSGlkZGVuOiExLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMmFhYSIsIndjYWcyMjEiLCJ3Y2FnMjI0Iiwid2NhZzMyNSJdLGFsbDpbXSxhbnk6WyJtZXRhLXJlZnJlc2giXSxub25lOltdfSx7aWQ6Im1ldGEtdmlld3BvcnQtbGFyZ2UiLHNlbGVjdG9yOidtZXRhW25hbWU9InZpZXdwb3J0Il0nLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbImJlc3QtcHJhY3RpY2UiXSxhbGw6W10sYW55Olt7b3B0aW9uczp7c2NhbGVNaW5pbXVtOjUsbG93ZXJCb3VuZDoyfSxpZDoibWV0YS12aWV3cG9ydC1sYXJnZSJ9XSxub25lOltdfSx7aWQ6Im1ldGEtdmlld3BvcnQiLHNlbGVjdG9yOidtZXRhW25hbWU9InZpZXdwb3J0Il0nLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYWEiLCJ3Y2FnMTQ0Il0sYWxsOltdLGFueTpbe29wdGlvbnM6e3NjYWxlTWluaW11bToyfSxpZDoibWV0YS12aWV3cG9ydCJ9XSxub25lOltdfSx7aWQ6Im9iamVjdC1hbHQiLHNlbGVjdG9yOiJvYmplY3QiLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTExIiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIuYSJdLGFsbDpbXSxhbnk6WyJoYXMtdmlzaWJsZS10ZXh0IiwiYXJpYS1sYWJlbCIsImFyaWEtbGFiZWxsZWRieSIsIm5vbi1lbXB0eS10aXRsZSJdLG5vbmU6W119LHtpZDoicmFkaW9ncm91cCIsc2VsZWN0b3I6ImlucHV0W3R5cGU9cmFkaW9dW25hbWVdIix0YWdzOlsiYmVzdC1wcmFjdGljZSJdLGFsbDpbXSxhbnk6WyJncm91cC1sYWJlbGxlZGJ5IiwiZmllbGRzZXQiXSxub25lOltdfSx7aWQ6InJlZ2lvbiIsc2VsZWN0b3I6Imh0bWwiLHBhZ2VMZXZlbDohMCxlbmFibGVkOiExLHRhZ3M6WyJiZXN0LXByYWN0aWNlIl0sYWxsOltdLGFueTpbInJlZ2lvbiJdLG5vbmU6W119LHtpZDoic2NvcGUtYXR0ci12YWxpZCIsc2VsZWN0b3I6InRkW3Njb3BlXSwgdGhbc2NvcGVdIixlbmFibGVkOiEwLHRhZ3M6WyJiZXN0LXByYWN0aWNlIl0sYWxsOlsiaHRtbDUtc2NvcGUiLCJzY29wZS12YWx1ZSJdLGFueTpbXSxub25lOltdfSx7aWQ6InNlcnZlci1zaWRlLWltYWdlLW1hcCIsc2VsZWN0b3I6ImltZ1tpc21hcF0iLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMjExIiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIuZiJdLGFsbDpbXSxhbnk6W10sbm9uZTpbImV4aXN0cyJdfSx7aWQ6InNraXAtbGluayIsc2VsZWN0b3I6ImFbaHJlZl0iLHBhZ2VMZXZlbDohMCxlbmFibGVkOiExLHRhZ3M6WyJiZXN0LXByYWN0aWNlIl0sYWxsOltdLGFueTpbInNraXAtbGluayJdLG5vbmU6W119LHtpZDoidGFiaW5kZXgiLHNlbGVjdG9yOiJbdGFiaW5kZXhdIix0YWdzOlsiYmVzdC1wcmFjdGljZSJdLGFsbDpbXSxhbnk6WyJ0YWJpbmRleCJdLG5vbmU6W119LHtpZDoidGFibGUtZHVwbGljYXRlLW5hbWUiLHNlbGVjdG9yOiJ0YWJsZSIsdGFnczpbImJlc3QtcHJhY3RpY2UiXSxhbGw6W10sYW55OltdLG5vbmU6WyJzYW1lLWNhcHRpb24tc3VtbWFyeSJdfSx7aWQ6InRhYmxlLWZha2UtY2FwdGlvbiIsc2VsZWN0b3I6InRhYmxlIixtYXRjaGVzOmZ1bmN0aW9uKGEpe3JldHVybiBheGUuY29tbW9ucy50YWJsZS5pc0RhdGFUYWJsZShhKX0sdGFnczpbImV4cGVyaW1lbnRhbCIsIndjYWcyYSIsIndjYWcxMzEiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5nIl0sYWxsOlsiY2FwdGlvbi1mYWtlZCJdLGFueTpbXSxub25lOltdfSx7aWQ6InRkLWhhcy1oZWFkZXIiLHNlbGVjdG9yOiJ0YWJsZSIsbWF0Y2hlczpmdW5jdGlvbihhKXtpZihheGUuY29tbW9ucy50YWJsZS5pc0RhdGFUYWJsZShhKSl7dmFyIGI9YXhlLmNvbW1vbnMudGFibGUudG9BcnJheShhKTtyZXR1cm4gYi5sZW5ndGg+PTMmJmJbMF0ubGVuZ3RoPj0zJiZiWzFdLmxlbmd0aD49MyYmYlsyXS5sZW5ndGg+PTN9cmV0dXJuITF9LHRhZ3M6WyJleHBlcmltZW50YWwiLCJ3Y2FnMmEiLCJ3Y2FnMTMxIiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIuZyJdLGFsbDpbInRkLWhhcy1oZWFkZXIiXSxhbnk6W10sbm9uZTpbXX0se2lkOiJ0ZC1oZWFkZXJzLWF0dHIiLHNlbGVjdG9yOiJ0YWJsZSIsdGFnczpbIndjYWcyYSIsIndjYWcxMzEiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5nIl0sYWxsOlsidGQtaGVhZGVycy1hdHRyIl0sYW55OltdLG5vbmU6W119LHtpZDoidGgtaGFzLWRhdGEtY2VsbHMiLHNlbGVjdG9yOiJ0YWJsZSIsbWF0Y2hlczpmdW5jdGlvbihhKXtyZXR1cm4gYXhlLmNvbW1vbnMudGFibGUuaXNEYXRhVGFibGUoYSl9LHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTMxIiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIuZyJdLGFsbDpbInRoLWhhcy1kYXRhLWNlbGxzIl0sYW55OltdLG5vbmU6W119LHtpZDoidmFsaWQtbGFuZyIsc2VsZWN0b3I6IltsYW5nXTpub3QoaHRtbCksIFt4bWxcXDpsYW5nXTpub3QoaHRtbCkiLHRhZ3M6WyJ3Y2FnMmFhIiwid2NhZzMxMiJdLGFsbDpbXSxhbnk6W10sbm9uZTpbe29wdGlvbnM6WyJhYSIsImFiIiwiYWUiLCJhZiIsImFrIiwiYW0iLCJhbiIsImFyIiwiYXMiLCJhdiIsImF5IiwiYXoiLCJiYSIsImJlIiwiYmciLCJiaCIsImJpIiwiYm0iLCJibiIsImJvIiwiYnIiLCJicyIsImNhIiwiY2UiLCJjaCIsImNvIiwiY3IiLCJjcyIsImN1IiwiY3YiLCJjeSIsImRhIiwiZGUiLCJkdiIsImR6IiwiZWUiLCJlbCIsImVuIiwiZW8iLCJlcyIsImV0IiwiZXUiLCJmYSIsImZmIiwiZmkiLCJmaiIsImZvIiwiZnIiLCJmeSIsImdhIiwiZ2QiLCJnbCIsImduIiwiZ3UiLCJndiIsImhhIiwiaGUiLCJoaSIsImhvIiwiaHIiLCJodCIsImh1IiwiaHkiLCJoeiIsImlhIiwiaWQiLCJpZSIsImlnIiwiaWkiLCJpayIsImluIiwiaW8iLCJpcyIsIml0IiwiaXUiLCJpdyIsImphIiwiamkiLCJqdiIsImp3Iiwia2EiLCJrZyIsImtpIiwia2oiLCJrayIsImtsIiwia20iLCJrbiIsImtvIiwia3IiLCJrcyIsImt1Iiwia3YiLCJrdyIsImt5IiwibGEiLCJsYiIsImxnIiwibGkiLCJsbiIsImxvIiwibHQiLCJsdSIsImx2IiwibWciLCJtaCIsIm1pIiwibWsiLCJtbCIsIm1uIiwibW8iLCJtciIsIm1zIiwibXQiLCJteSIsIm5hIiwibmIiLCJuZCIsIm5lIiwibmciLCJubCIsIm5uIiwibm8iLCJuciIsIm52IiwibnkiLCJvYyIsIm9qIiwib20iLCJvciIsIm9zIiwicGEiLCJwaSIsInBsIiwicHMiLCJwdCIsInF1Iiwicm0iLCJybiIsInJvIiwicnUiLCJydyIsInNhIiwic2MiLCJzZCIsInNlIiwic2ciLCJzaCIsInNpIiwic2siLCJzbCIsInNtIiwic24iLCJzbyIsInNxIiwic3IiLCJzcyIsInN0Iiwic3UiLCJzdiIsInN3IiwidGEiLCJ0ZSIsInRnIiwidGgiLCJ0aSIsInRrIiwidGwiLCJ0biIsInRvIiwidHIiLCJ0cyIsInR0IiwidHciLCJ0eSIsInVnIiwidWsiLCJ1ciIsInV6IiwidmUiLCJ2aSIsInZvIiwid2EiLCJ3byIsInhoIiwieWkiLCJ5byIsInphIiwiemgiLCJ6dSJdLGlkOiJ2YWxpZC1sYW5nIn1dfSx7aWQ6InZpZGVvLWNhcHRpb24iLHNlbGVjdG9yOiJ2aWRlbyIsZXhjbHVkZUhpZGRlbjohMSx0YWdzOlsid2NhZzJhIiwid2NhZzEyMiIsIndjYWcxMjMiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5hIl0sYWxsOltdLGFueTpbXSxub25lOlsiY2FwdGlvbiJdfSx7aWQ6InZpZGVvLWRlc2NyaXB0aW9uIixzZWxlY3RvcjoidmlkZW8iLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYWEiLCJ3Y2FnMTI1Iiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIuYiJdLGFsbDpbXSxhbnk6W10sbm9uZTpbImRlc2NyaXB0aW9uIl19XSxjaGVja3M6W3tpZDoiYWJzdHJhY3Ryb2xlIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiJhYnN0cmFjdCI9PT1heGUuY29tbW9ucy5hcmlhLmdldFJvbGVUeXBlKGEuZ2V0QXR0cmlidXRlKCJyb2xlIikpfX0se2lkOiJhcmlhLWFsbG93ZWQtYXR0ciIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYyxkLGUsZj1bXSxnPWEuZ2V0QXR0cmlidXRlKCJyb2xlIiksaD1hLmF0dHJpYnV0ZXM7aWYoZ3x8KGc9YXhlLmNvbW1vbnMuYXJpYS5pbXBsaWNpdFJvbGUoYSkpLGU9YXhlLmNvbW1vbnMuYXJpYS5hbGxvd2VkQXR0cihnKSxnJiZlKWZvcih2YXIgaT0wLGo9aC5sZW5ndGg7aTxqO2krKyljPWhbaV0sZD1jLm5hbWUsYXhlLmNvbW1vbnMuYXJpYS52YWxpZGF0ZUF0dHIoZCkmJmUuaW5kZXhPZihkKT09PS0xJiZmLnB1c2goZCsnPSInK2Mubm9kZVZhbHVlKyciJyk7cmV0dXJuIWYubGVuZ3RofHwodGhpcy5kYXRhKGYpLCExKX19LHtpZDoiaW52YWxpZHJvbGUiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuIWF4ZS5jb21tb25zLmFyaWEuaXNWYWxpZFJvbGUoYS5nZXRBdHRyaWJ1dGUoInJvbGUiKSl9fSx7aWQ6ImFyaWEtcmVxdWlyZWQtYXR0ciIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1bXTtpZihhLmhhc0F0dHJpYnV0ZXMoKSl7dmFyIGQsZT1hLmdldEF0dHJpYnV0ZSgicm9sZSIpLGY9YXhlLmNvbW1vbnMuYXJpYS5yZXF1aXJlZEF0dHIoZSk7aWYoZSYmZilmb3IodmFyIGc9MCxoPWYubGVuZ3RoO2c8aDtnKyspZD1mW2ddLGEuZ2V0QXR0cmlidXRlKGQpfHxjLnB1c2goZCl9cmV0dXJuIWMubGVuZ3RofHwodGhpcy5kYXRhKGMpLCExKX19LHtpZDoiYXJpYS1yZXF1aXJlZC1jaGlsZHJlbiIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtmdW5jdGlvbiBjKGEsYixjKXtpZihudWxsPT09YSlyZXR1cm4hMTt2YXIgZD1nKGIpLGU9Wydbcm9sZT0iJytiKyciXSddO3JldHVybiBkJiYoZT1lLmNvbmNhdChkKSksZT1lLmpvaW4oIiwiKSxjP2goYSxlKXx8ISFhLnF1ZXJ5U2VsZWN0b3IoZSk6ISFhLnF1ZXJ5U2VsZWN0b3IoZSl9ZnVuY3Rpb24gZChhLGIpe3ZhciBkLGU7Zm9yKGQ9MCxlPWEubGVuZ3RoO2Q8ZTtkKyspaWYobnVsbCE9PWFbZF0mJmMoYVtkXSxiLCEwKSlyZXR1cm4hMDtyZXR1cm4hMX1mdW5jdGlvbiBlKGEsYixlKXt2YXIgZixnPWIubGVuZ3RoLGg9W10saj1pKGEsImFyaWEtb3ducyIpO2ZvcihmPTA7ZjxnO2YrKyl7dmFyIGs9YltmXTtpZihjKGEsayl8fGQoaixrKSl7aWYoIWUpcmV0dXJuIG51bGx9ZWxzZSBlJiZoLnB1c2goayl9cmV0dXJuIGgubGVuZ3RoP2g6IWUmJmIubGVuZ3RoP2I6bnVsbH12YXIgZj1heGUuY29tbW9ucy5hcmlhLnJlcXVpcmVkT3duZWQsZz1heGUuY29tbW9ucy5hcmlhLmltcGxpY2l0Tm9kZXMsaD1heGUuY29tbW9ucy51dGlscy5tYXRjaGVzU2VsZWN0b3IsaT1heGUuY29tbW9ucy5kb20uaWRyZWZzLGo9YS5nZXRBdHRyaWJ1dGUoInJvbGUiKSxrPWYoaik7aWYoIWspcmV0dXJuITA7dmFyIGw9ITEsbT1rLm9uZTtpZighbSl7dmFyIGw9ITA7bT1rLmFsbH12YXIgbj1lKGEsbSxsKTtyZXR1cm4hbnx8KHRoaXMuZGF0YShuKSwhMSl9fSx7aWQ6ImFyaWEtcmVxdWlyZWQtcGFyZW50IixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2Z1bmN0aW9uIGMoYSl7dmFyIGI9YXhlLmNvbW1vbnMuYXJpYS5pbXBsaWNpdE5vZGVzKGEpfHxbXTtyZXR1cm4gYi5jb25jYXQoJ1tyb2xlPSInK2ErJyJdJykuam9pbigiLCIpfWZ1bmN0aW9uIGQoYSxiLGQpe3ZhciBlLGYsZz1hLmdldEF0dHJpYnV0ZSgicm9sZSIpLGg9W107aWYoYnx8KGI9YXhlLmNvbW1vbnMuYXJpYS5yZXF1aXJlZENvbnRleHQoZykpLCFiKXJldHVybiBudWxsO2ZvcihlPTAsZj1iLmxlbmd0aDtlPGY7ZSsrKXtpZihkJiZheGUudXRpbHMubWF0Y2hlc1NlbGVjdG9yKGEsYyhiW2VdKSkpcmV0dXJuIG51bGw7aWYoYXhlLmNvbW1vbnMuZG9tLmZpbmRVcChhLGMoYltlXSkpKXJldHVybiBudWxsO2gucHVzaChiW2VdKX1yZXR1cm4gaH1mdW5jdGlvbiBlKGEpe2Zvcih2YXIgYj1bXSxjPW51bGw7YTspYS5pZCYmKGM9ZG9jdW1lbnQucXVlcnlTZWxlY3RvcigiW2FyaWEtb3duc349IitheGUuY29tbW9ucy51dGlscy5lc2NhcGVTZWxlY3RvcihhLmlkKSsiXSIpLGMmJmIucHVzaChjKSksYT1hLnBhcmVudE5vZGU7cmV0dXJuIGIubGVuZ3RoP2I6bnVsbH12YXIgZj1kKGEpO2lmKCFmKXJldHVybiEwO3ZhciBnPWUoYSk7aWYoZylmb3IodmFyIGg9MCxpPWcubGVuZ3RoO2g8aTtoKyspaWYoZj1kKGdbaF0sZiwhMCksIWYpcmV0dXJuITA7cmV0dXJuIHRoaXMuZGF0YShmKSwhMX19LHtpZDoiYXJpYS12YWxpZC1hdHRyLXZhbHVlIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2I9QXJyYXkuaXNBcnJheShiKT9iOltdO2Zvcih2YXIgYyxkLGU9W10sZj0vXmFyaWEtLyxnPWEuYXR0cmlidXRlcyxoPTAsaT1nLmxlbmd0aDtoPGk7aCsrKWM9Z1toXSxkPWMubmFtZSxiLmluZGV4T2YoZCk9PT0tMSYmZi50ZXN0KGQpJiYhYXhlLmNvbW1vbnMuYXJpYS52YWxpZGF0ZUF0dHJWYWx1ZShhLGQpJiZlLnB1c2goZCsnPSInK2Mubm9kZVZhbHVlKyciJyk7cmV0dXJuIWUubGVuZ3RofHwodGhpcy5kYXRhKGUpLCExKX0sb3B0aW9uczpbXX0se2lkOiJhcmlhLXZhbGlkLWF0dHIiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7Yj1BcnJheS5pc0FycmF5KGIpP2I6W107Zm9yKHZhciBjLGQ9W10sZT0vXmFyaWEtLyxmPWEuYXR0cmlidXRlcyxnPTAsaD1mLmxlbmd0aDtnPGg7ZysrKWM9ZltnXS5uYW1lLGIuaW5kZXhPZihjKT09PS0xJiZlLnRlc3QoYykmJiFheGUuY29tbW9ucy5hcmlhLnZhbGlkYXRlQXR0cihjKSYmZC5wdXNoKGMpO3JldHVybiFkLmxlbmd0aHx8KHRoaXMuZGF0YShkKSwhMSl9LG9wdGlvbnM6W119LHtpZDoiY29sb3ItY29udHJhc3QiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7aWYoIWF4ZS5jb21tb25zLmRvbS5pc1Zpc2libGUoYSwhMSkpcmV0dXJuITA7dmFyIGM9ISEoYnx8e30pLm5vU2Nyb2xsLGQ9W10sZT1heGUuY29tbW9ucy5jb2xvci5nZXRCYWNrZ3JvdW5kQ29sb3IoYSxkLGMpLGY9YXhlLmNvbW1vbnMuY29sb3IuZ2V0Rm9yZWdyb3VuZENvbG9yKGEsYyk7aWYobnVsbCE9PWYmJm51bGwhPT1lKXt2YXIgZz13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShhKSxoPXBhcnNlRmxvYXQoZy5nZXRQcm9wZXJ0eVZhbHVlKCJmb250LXNpemUiKSksaT1nLmdldFByb3BlcnR5VmFsdWUoImZvbnQtd2VpZ2h0Iiksaj1bImJvbGQiLCJib2xkZXIiLCI2MDAiLCI3MDAiLCI4MDAiLCI5MDAiXS5pbmRleE9mKGkpIT09LTEsaz1heGUuY29tbW9ucy5jb2xvci5oYXNWYWxpZENvbnRyYXN0UmF0aW8oZSxmLGgsaik7cmV0dXJuIHRoaXMuZGF0YSh7ZmdDb2xvcjpmLnRvSGV4U3RyaW5nKCksYmdDb2xvcjplLnRvSGV4U3RyaW5nKCksY29udHJhc3RSYXRpbzprLmNvbnRyYXN0UmF0aW8udG9GaXhlZCgyKSxmb250U2l6ZTooNzIqaC85NikudG9GaXhlZCgxKSsicHQiLGZvbnRXZWlnaHQ6aj8iYm9sZCI6Im5vcm1hbCJ9KSxrLmlzVmFsaWR8fHRoaXMucmVsYXRlZE5vZGVzKGQpLGsuaXNWYWxpZH19fSx7aWQ6ImxpbmstaW4tdGV4dC1ibG9jayIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtmdW5jdGlvbiBjKGEsYil7dmFyIGM9YS5nZXRSZWxhdGl2ZUx1bWluYW5jZSgpLGQ9Yi5nZXRSZWxhdGl2ZUx1bWluYW5jZSgpO3JldHVybihNYXRoLm1heChjLGQpKy4wNSkvKE1hdGgubWluKGMsZCkrLjA1KX1mdW5jdGlvbiBkKGEpe3ZhciBiPXdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGEpLmdldFByb3BlcnR5VmFsdWUoImRpc3BsYXkiKTtyZXR1cm4gZi5pbmRleE9mKGIpIT09LTF8fCJ0YWJsZS0iPT09Yi5zdWJzdHIoMCw2KX12YXIgZT1heGUuY29tbW9ucy5jb2xvcixmPVsiYmxvY2siLCJsaXN0LWl0ZW0iLCJ0YWJsZSIsImZsZXgiLCJncmlkIiwiaW5saW5lLWJsb2NrIl07aWYoZChhKSlyZXR1cm4hMTtmb3IodmFyIGc9YS5wYXJlbnROb2RlOzE9PT1nLm5vZGVUeXBlJiYhZChnKTspZz1nLnBhcmVudE5vZGU7aWYoZS5lbGVtZW50SXNEaXN0aW5jdChhLGcpKXJldHVybiEwO3ZhciBoLGk7aWYoaD1lLmdldEZvcmVncm91bmRDb2xvcihhKSxpPWUuZ2V0Rm9yZWdyb3VuZENvbG9yKGcpLGgmJmkpe3ZhciBqPWMoaCxpKTtpZigxPT09ailyZXR1cm4hMDtpZighKGo+PTMpJiYoaD1lLmdldEJhY2tncm91bmRDb2xvcihhKSxpPWUuZ2V0QmFja2dyb3VuZENvbG9yKGcpLGgmJmkmJiEoYyhoLGkpPj0zKSkpcmV0dXJuITF9fX0se2lkOiJmaWVsZHNldCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtmdW5jdGlvbiBjKGEsYil7cmV0dXJuIGF4ZS5jb21tb25zLnV0aWxzLnRvQXJyYXkoYS5xdWVyeVNlbGVjdG9yQWxsKCdzZWxlY3QsdGV4dGFyZWEsYnV0dG9uLGlucHV0Om5vdChbbmFtZT0iJytiKyciXSk6bm90KFt0eXBlPSJoaWRkZW4iXSknKSl9ZnVuY3Rpb24gZChhLGIpe3ZhciBkPWEuZmlyc3RFbGVtZW50Q2hpbGQ7aWYoIWR8fCJMRUdFTkQiIT09ZC5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpKXJldHVybiBpLnJlbGF0ZWROb2RlcyhbYV0pLGg9Im5vLWxlZ2VuZCIsITE7aWYoIWF4ZS5jb21tb25zLnRleHQuYWNjZXNzaWJsZVRleHQoZCkpcmV0dXJuIGkucmVsYXRlZE5vZGVzKFtkXSksaD0iZW1wdHktbGVnZW5kIiwhMTt2YXIgZT1jKGEsYik7cmV0dXJuIWUubGVuZ3RofHwoaS5yZWxhdGVkTm9kZXMoZSksaD0ibWl4ZWQtaW5wdXRzIiwhMSl9ZnVuY3Rpb24gZShhLGIpe3ZhciBkPWF4ZS5jb21tb25zLmRvbS5pZHJlZnMoYSwiYXJpYS1sYWJlbGxlZGJ5Iikuc29tZShmdW5jdGlvbihhKXtyZXR1cm4gYSYmYXhlLmNvbW1vbnMudGV4dC5hY2Nlc3NpYmxlVGV4dChhKX0pLGU9YS5nZXRBdHRyaWJ1dGUoImFyaWEtbGFiZWwiKTtpZighKGR8fGUmJmF4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoZSkpKXJldHVybiBpLnJlbGF0ZWROb2RlcyhhKSxoPSJuby1ncm91cC1sYWJlbCIsITE7dmFyIGY9YyhhLGIpO3JldHVybiFmLmxlbmd0aHx8KGkucmVsYXRlZE5vZGVzKGYpLGg9Imdyb3VwLW1peGVkLWlucHV0cyIsITEpfWZ1bmN0aW9uIGYoYSxiKXtyZXR1cm4gYXhlLmNvbW1vbnMudXRpbHMudG9BcnJheShhKS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuIGEhPT1ifSl9ZnVuY3Rpb24gZyhiKXt2YXIgYz1heGUuY29tbW9ucy51dGlscy5lc2NhcGVTZWxlY3RvcihhLm5hbWUpLGc9ZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnaW5wdXRbdHlwZT0iJytheGUuY29tbW9ucy51dGlscy5lc2NhcGVTZWxlY3RvcihhLnR5cGUpKyciXVtuYW1lPSInK2MrJyJdJyk7aWYoZy5sZW5ndGg8MilyZXR1cm4hMDt2YXIgaj1heGUuY29tbW9ucy5kb20uZmluZFVwKGIsImZpZWxkc2V0Iiksaz1heGUuY29tbW9ucy5kb20uZmluZFVwKGIsJ1tyb2xlPSJncm91cCJdJysoInJhZGlvIj09PWEudHlwZT8nLFtyb2xlPSJyYWRpb2dyb3VwIl0nOiIiKSk7cmV0dXJuIGt8fGo/aj9kKGosYyk6ZShrLGMpOihoPSJuby1ncm91cCIsaS5yZWxhdGVkTm9kZXMoZihnLGIpKSwhMSl9dmFyIGgsaT10aGlzLGo9e25hbWU6YS5nZXRBdHRyaWJ1dGUoIm5hbWUiKSx0eXBlOmEuZ2V0QXR0cmlidXRlKCJ0eXBlIil9LGs9ZyhhKTtyZXR1cm4ga3x8KGouZmFpbHVyZUNvZGU9aCksdGhpcy5kYXRhKGopLGt9LGFmdGVyOmZ1bmN0aW9uKGEsYil7dmFyIGM9e307cmV0dXJuIGEuZmlsdGVyKGZ1bmN0aW9uKGEpe2lmKGEucmVzdWx0KXJldHVybiEwO3ZhciBiPWEuZGF0YTtpZihiKXtpZihjW2IudHlwZV09Y1tiLnR5cGVdfHx7fSwhY1tiLnR5cGVdW2IubmFtZV0pcmV0dXJuIGNbYi50eXBlXVtiLm5hbWVdPVtiXSwhMDt2YXIgZD1jW2IudHlwZV1bYi5uYW1lXS5zb21lKGZ1bmN0aW9uKGEpe3JldHVybiBhLmZhaWx1cmVDb2RlPT09Yi5mYWlsdXJlQ29kZX0pO3JldHVybiBkfHxjW2IudHlwZV1bYi5uYW1lXS5wdXNoKGIpLCFkfXJldHVybiExfSl9fSx7aWQ6Imdyb3VwLWxhYmVsbGVkYnkiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dGhpcy5kYXRhKHtuYW1lOmEuZ2V0QXR0cmlidXRlKCJuYW1lIiksdHlwZTphLmdldEF0dHJpYnV0ZSgidHlwZSIpfSk7dmFyIGM9ZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnaW5wdXRbdHlwZT0iJytheGUuY29tbW9ucy51dGlscy5lc2NhcGVTZWxlY3RvcihhLnR5cGUpKyciXVtuYW1lPSInK2F4ZS5jb21tb25zLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEubmFtZSkrJyJdJyk7cmV0dXJuIGMubGVuZ3RoPD0xfHwwIT09W10ubWFwLmNhbGwoYyxmdW5jdGlvbihhKXt2YXIgYj1hLmdldEF0dHJpYnV0ZSgiYXJpYS1sYWJlbGxlZGJ5Iik7cmV0dXJuIGI/Yi5zcGxpdCgvXHMrLyk6W119KS5yZWR1Y2UoZnVuY3Rpb24oYSxiKXtyZXR1cm4gYS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuIGIuaW5kZXhPZihhKSE9PS0xfSl9KS5maWx0ZXIoZnVuY3Rpb24oYSl7dmFyIGI9ZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoYSk7cmV0dXJuIGImJmF4ZS5jb21tb25zLnRleHQuYWNjZXNzaWJsZVRleHQoYil9KS5sZW5ndGh9LGFmdGVyOmZ1bmN0aW9uKGEsYil7dmFyIGM9e307cmV0dXJuIGEuZmlsdGVyKGZ1bmN0aW9uKGEpe3ZhciBiPWEuZGF0YTtyZXR1cm4hKCFifHwoY1tiLnR5cGVdPWNbYi50eXBlXXx8e30sY1tiLnR5cGVdW2IubmFtZV0pKSYmKGNbYi50eXBlXVtiLm5hbWVdPSEwLCEwKX0pfX0se2lkOiJhY2Nlc3NrZXlzIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiBheGUuY29tbW9ucy5kb20uaXNWaXNpYmxlKGEsITEpJiYodGhpcy5kYXRhKGEuZ2V0QXR0cmlidXRlKCJhY2Nlc3NrZXkiKSksdGhpcy5yZWxhdGVkTm9kZXMoW2FdKSksITB9LGFmdGVyOmZ1bmN0aW9uKGEsYil7dmFyIGM9e307cmV0dXJuIGEuZmlsdGVyKGZ1bmN0aW9uKGEpe2lmKCFhLmRhdGEpcmV0dXJuITE7dmFyIGI9YS5kYXRhLnRvVXBwZXJDYXNlKCk7cmV0dXJuIGNbYl0/KGNbYl0ucmVsYXRlZE5vZGVzLnB1c2goYS5yZWxhdGVkTm9kZXNbMF0pLCExKTooY1tiXT1hLGEucmVsYXRlZE5vZGVzPVtdLCEwKX0pLm1hcChmdW5jdGlvbihhKXtyZXR1cm4gYS5yZXN1bHQ9ISFhLnJlbGF0ZWROb2Rlcy5sZW5ndGgsYX0pfX0se2lkOiJmb2N1c2FibGUtbm8tbmFtZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1hLmdldEF0dHJpYnV0ZSgidGFiaW5kZXgiKSxkPWF4ZS5jb21tb25zLmRvbS5pc0ZvY3VzYWJsZShhKSYmYz4tMTtyZXR1cm4hIWQmJiFheGUuY29tbW9ucy50ZXh0LmFjY2Vzc2libGVUZXh0KGEpfX0se2lkOiJ0YWJpbmRleCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4gYS50YWJJbmRleDw9MH19LHtpZDoiZHVwbGljYXRlLWltZy1sYWJlbCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1hLnF1ZXJ5U2VsZWN0b3JBbGwoImltZyIpLGQ9YXhlLmNvbW1vbnMudGV4dC52aXNpYmxlKGEsITApLnRvTG93ZXJDYXNlKCk7aWYoIiI9PT1kKXJldHVybiExO2Zvcih2YXIgZT0wLGY9Yy5sZW5ndGg7ZTxmO2UrKyl7dmFyIGc9Y1tlXSxoPWF4ZS5jb21tb25zLnRleHQuYWNjZXNzaWJsZVRleHQoZykudG9Mb3dlckNhc2UoKTtpZihoPT09ZCYmInByZXNlbnRhdGlvbiIhPT1nLmdldEF0dHJpYnV0ZSgicm9sZSIpJiZheGUuY29tbW9ucy5kb20uaXNWaXNpYmxlKGcpKXJldHVybiEwfXJldHVybiExfX0se2lkOiJleHBsaWNpdC1sYWJlbCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtpZihhLmlkKXt2YXIgYz1kb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdsYWJlbFtmb3I9IicrYXhlLmNvbW1vbnMudXRpbHMuZXNjYXBlU2VsZWN0b3IoYS5pZCkrJyJdJyk7aWYoYylyZXR1cm4hIWF4ZS5jb21tb25zLnRleHQuYWNjZXNzaWJsZVRleHQoYyl9cmV0dXJuITF9fSx7aWQ6ImhlbHAtc2FtZS1hcy1sYWJlbCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1heGUuY29tbW9ucy50ZXh0LmxhYmVsKGEpLGQ9YS5nZXRBdHRyaWJ1dGUoInRpdGxlIik7aWYoIWMpcmV0dXJuITE7aWYoIWQmJihkPSIiLGEuZ2V0QXR0cmlidXRlKCJhcmlhLWRlc2NyaWJlZGJ5IikpKXt2YXIgZT1heGUuY29tbW9ucy5kb20uaWRyZWZzKGEsImFyaWEtZGVzY3JpYmVkYnkiKTtkPWUubWFwKGZ1bmN0aW9uKGEpe3JldHVybiBhP2F4ZS5jb21tb25zLnRleHQuYWNjZXNzaWJsZVRleHQoYSk6IiJ9KS5qb2luKCIiKX1yZXR1cm4gYXhlLmNvbW1vbnMudGV4dC5zYW5pdGl6ZShkKT09PWF4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoYyl9LGVuYWJsZWQ6ITF9LHtpZDoiaW1wbGljaXQtbGFiZWwiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9YXhlLmNvbW1vbnMuZG9tLmZpbmRVcChhLCJsYWJlbCIpO3JldHVybiEhYyYmISFheGUuY29tbW9ucy50ZXh0LmFjY2Vzc2libGVUZXh0KGMpfX0se2lkOiJtdWx0aXBsZS1sYWJlbCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtmb3IodmFyIGM9W10uc2xpY2UuY2FsbChkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCdsYWJlbFtmb3I9IicrYXhlLmNvbW1vbnMudXRpbHMuZXNjYXBlU2VsZWN0b3IoYS5pZCkrJyJdJykpLGQ9YS5wYXJlbnROb2RlO2Q7KSJMQUJFTCI9PT1kLnRhZ05hbWUmJmMuaW5kZXhPZihkKT09PS0xJiZjLnB1c2goZCksZD1kLnBhcmVudE5vZGU7cmV0dXJuIHRoaXMucmVsYXRlZE5vZGVzKGMpLGMubGVuZ3RoPjF9fSx7aWQ6InRpdGxlLW9ubHkiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9YXhlLmNvbW1vbnMudGV4dC5sYWJlbChhKTtyZXR1cm4hKGN8fCFhLmdldEF0dHJpYnV0ZSgidGl0bGUiKSYmIWEuZ2V0QXR0cmlidXRlKCJhcmlhLWRlc2NyaWJlZGJ5IikpfX0se2lkOiJoYXMtbGFuZyIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4hIShhLmdldEF0dHJpYnV0ZSgibGFuZyIpfHxhLmdldEF0dHJpYnV0ZSgieG1sOmxhbmciKXx8IiIpLnRyaW0oKX19LHtpZDoidmFsaWQtbGFuZyIsb3B0aW9uczpbImFhIiwiYWIiLCJhZSIsImFmIiwiYWsiLCJhbSIsImFuIiwiYXIiLCJhcyIsImF2IiwiYXkiLCJheiIsImJhIiwiYmUiLCJiZyIsImJoIiwiYmkiLCJibSIsImJuIiwiYm8iLCJiciIsImJzIiwiY2EiLCJjZSIsImNoIiwiY28iLCJjciIsImNzIiwiY3UiLCJjdiIsImN5IiwiZGEiLCJkZSIsImR2IiwiZHoiLCJlZSIsImVsIiwiZW4iLCJlbyIsImVzIiwiZXQiLCJldSIsImZhIiwiZmYiLCJmaSIsImZqIiwiZm8iLCJmciIsImZ5IiwiZ2EiLCJnZCIsImdsIiwiZ24iLCJndSIsImd2IiwiaGEiLCJoZSIsImhpIiwiaG8iLCJociIsImh0IiwiaHUiLCJoeSIsImh6IiwiaWEiLCJpZCIsImllIiwiaWciLCJpaSIsImlrIiwiaW4iLCJpbyIsImlzIiwiaXQiLCJpdSIsIml3IiwiamEiLCJqaSIsImp2IiwianciLCJrYSIsImtnIiwia2kiLCJraiIsImtrIiwia2wiLCJrbSIsImtuIiwia28iLCJrciIsImtzIiwia3UiLCJrdiIsImt3Iiwia3kiLCJsYSIsImxiIiwibGciLCJsaSIsImxuIiwibG8iLCJsdCIsImx1IiwibHYiLCJtZyIsIm1oIiwibWkiLCJtayIsIm1sIiwibW4iLCJtbyIsIm1yIiwibXMiLCJtdCIsIm15IiwibmEiLCJuYiIsIm5kIiwibmUiLCJuZyIsIm5sIiwibm4iLCJubyIsIm5yIiwibnYiLCJueSIsIm9jIiwib2oiLCJvbSIsIm9yIiwib3MiLCJwYSIsInBpIiwicGwiLCJwcyIsInB0IiwicXUiLCJybSIsInJuIiwicm8iLCJydSIsInJ3Iiwic2EiLCJzYyIsInNkIiwic2UiLCJzZyIsInNoIiwic2kiLCJzayIsInNsIiwic20iLCJzbiIsInNvIiwic3EiLCJzciIsInNzIiwic3QiLCJzdSIsInN2Iiwic3ciLCJ0YSIsInRlIiwidGciLCJ0aCIsInRpIiwidGsiLCJ0bCIsInRuIiwidG8iLCJ0ciIsInRzIiwidHQiLCJ0dyIsInR5IiwidWciLCJ1ayIsInVyIiwidXoiLCJ2ZSIsInZpIiwidm8iLCJ3YSIsIndvIiwieGgiLCJ5aSIsInlvIiwiemEiLCJ6aCIsInp1Il0sZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtmdW5jdGlvbiBjKGEpe3JldHVybiBhLnRyaW0oKS5zcGxpdCgiLSIpWzBdLnRvTG93ZXJDYXNlKCl9dmFyIGQsZTtyZXR1cm4gZD0oYnx8W10pLm1hcChjKSxlPVsibGFuZyIsInhtbDpsYW5nIl0ucmVkdWNlKGZ1bmN0aW9uKGIsZSl7dmFyIGY9YS5nZXRBdHRyaWJ1dGUoZSk7aWYoInN0cmluZyIhPXR5cGVvZiBmKXJldHVybiBiO3ZhciBnPWMoZik7cmV0dXJuIiIhPT1nJiZkLmluZGV4T2YoZyk9PT0tMSYmYi5wdXNoKGUrJz0iJythLmdldEF0dHJpYnV0ZShlKSsnIicpLGJ9LFtdKSwhIWUubGVuZ3RoJiYodGhpcy5kYXRhKGUpLCEwKX19LHtpZDoiZGxpdGVtIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiJETCI9PT1hLnBhcmVudE5vZGUudGFnTmFtZX19LHtpZDoiaGFzLWxpc3RpdGVtIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuY2hpbGRyZW47aWYoMD09PWMubGVuZ3RoKXJldHVybiEwO2Zvcih2YXIgZD0wO2Q8Yy5sZW5ndGg7ZCsrKWlmKCJMSSI9PT1jW2RdLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCkpcmV0dXJuITE7cmV0dXJuITB9fSx7aWQ6Imxpc3RpdGVtIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVyblsiVUwiLCJPTCJdLmluZGV4T2YoYS5wYXJlbnROb2RlLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCkpIT09LTF8fCJsaXN0Ij09PWEucGFyZW50Tm9kZS5nZXRBdHRyaWJ1dGUoInJvbGUiKX19LHtpZDoib25seS1kbGl0ZW1zIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2Zvcih2YXIgYyxkLGU9W10sZj1hLmNoaWxkTm9kZXMsZz1bIlNUWUxFIiwiTUVUQSIsIkxJTksiLCJNQVAiLCJBUkVBIiwiU0NSSVBUIiwiREFUQUxJU1QiLCJURU1QTEFURSJdLGg9ITEsaT0wO2k8Zi5sZW5ndGg7aSsrKXtjPWZbaV07dmFyIGQ9Yy5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpOzE9PT1jLm5vZGVUeXBlJiYiRFQiIT09ZCYmIkREIiE9PWQmJmcuaW5kZXhPZihkKT09PS0xP2UucHVzaChjKTozPT09Yy5ub2RlVHlwZSYmIiIhPT1jLm5vZGVWYWx1ZS50cmltKCkmJihoPSEwKX1lLmxlbmd0aCYmdGhpcy5yZWxhdGVkTm9kZXMoZSk7dmFyIGo9ISFlLmxlbmd0aHx8aDtyZXR1cm4gan19LHtpZDoib25seS1saXN0aXRlbXMiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7Zm9yKHZhciBjLGQsZT1bXSxmPWEuY2hpbGROb2RlcyxnPVsiU1RZTEUiLCJNRVRBIiwiTElOSyIsIk1BUCIsIkFSRUEiLCJTQ1JJUFQiLCJEQVRBTElTVCIsIlRFTVBMQVRFIl0saD0hMSxpPTA7aTxmLmxlbmd0aDtpKyspYz1mW2ldLGQ9Yy5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpLDE9PT1jLm5vZGVUeXBlJiYiTEkiIT09ZCYmZy5pbmRleE9mKGQpPT09LTE/ZS5wdXNoKGMpOjM9PT1jLm5vZGVUeXBlJiYiIiE9PWMubm9kZVZhbHVlLnRyaW0oKSYmKGg9ITApO3JldHVybiBlLmxlbmd0aCYmdGhpcy5yZWxhdGVkTm9kZXMoZSksISFlLmxlbmd0aHx8aH19LHtpZDoic3RydWN0dXJlZC1kbGl0ZW1zIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuY2hpbGRyZW47aWYoIWN8fCFjLmxlbmd0aClyZXR1cm4hMTtmb3IodmFyIGQsZT0hMSxmPSExLGc9MDtnPGMubGVuZ3RoO2crKyl7aWYoZD1jW2ddLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCksIkRUIj09PWQmJihlPSEwKSxlJiYiREQiPT09ZClyZXR1cm4hMTsiREQiPT09ZCYmKGY9ITApfXJldHVybiBlfHxmfX0se2lkOiJjYXB0aW9uIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiFhLnF1ZXJ5U2VsZWN0b3IoInRyYWNrW2tpbmQ9Y2FwdGlvbnNdIil9fSx7aWQ6ImRlc2NyaXB0aW9uIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiFhLnF1ZXJ5U2VsZWN0b3IoInRyYWNrW2tpbmQ9ZGVzY3JpcHRpb25zXSIpfX0se2lkOiJtZXRhLXZpZXdwb3J0LWxhcmdlIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2I9Ynx8e307Zm9yKHZhciBjLGQ9YS5nZXRBdHRyaWJ1dGUoImNvbnRlbnQiKXx8IiIsZT1kLnNwbGl0KC9bOyxdLyksZj17fSxnPWIuc2NhbGVNaW5pbXVtfHwyLGg9Yi5sb3dlckJvdW5kfHwhMSxpPTAsaj1lLmxlbmd0aDtpPGo7aSsrKXtjPWVbaV0uc3BsaXQoIj0iKTt2YXIgaz1jLnNoaWZ0KCkudG9Mb3dlckNhc2UoKTtrJiZjLmxlbmd0aCYmKGZbay50cmltKCldPWMuc2hpZnQoKS50cmltKCkudG9Mb3dlckNhc2UoKSl9cmV0dXJuISEoaCYmZlsibWF4aW11bS1zY2FsZSJdJiZwYXJzZUZsb2F0KGZbIm1heGltdW0tc2NhbGUiXSk8aCl8fCEoIWgmJiJubyI9PT1mWyJ1c2VyLXNjYWxhYmxlIl0pJiYhKGZbIm1heGltdW0tc2NhbGUiXSYmcGFyc2VGbG9hdChmWyJtYXhpbXVtLXNjYWxlIl0pPGcpfSxvcHRpb25zOntzY2FsZU1pbmltdW06NSxsb3dlckJvdW5kOjJ9fSx7aWQ6Im1ldGEtdmlld3BvcnQiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7Yj1ifHx7fTtmb3IodmFyIGMsZD1hLmdldEF0dHJpYnV0ZSgiY29udGVudCIpfHwiIixlPWQuc3BsaXQoL1s7LF0vKSxmPXt9LGc9Yi5zY2FsZU1pbmltdW18fDIsaD1iLmxvd2VyQm91bmR8fCExLGk9MCxqPWUubGVuZ3RoO2k8ajtpKyspe2M9ZVtpXS5zcGxpdCgiPSIpO3ZhciBrPWMuc2hpZnQoKS50b0xvd2VyQ2FzZSgpO2smJmMubGVuZ3RoJiYoZltrLnRyaW0oKV09Yy5zaGlmdCgpLnRyaW0oKS50b0xvd2VyQ2FzZSgpKX1yZXR1cm4hIShoJiZmWyJtYXhpbXVtLXNjYWxlIl0mJnBhcnNlRmxvYXQoZlsibWF4aW11bS1zY2FsZSJdKTxoKXx8ISghaCYmIm5vIj09PWZbInVzZXItc2NhbGFibGUiXSkmJiEoZlsibWF4aW11bS1zY2FsZSJdJiZwYXJzZUZsb2F0KGZbIm1heGltdW0tc2NhbGUiXSk8Zyl9LG9wdGlvbnM6e3NjYWxlTWluaW11bToyfX0se2lkOiJoZWFkZXItcHJlc2VudCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4hIWEucXVlcnlTZWxlY3RvcignaDEsIGgyLCBoMywgaDQsIGg1LCBoNiwgW3JvbGU9ImhlYWRpbmciXScpfX0se2lkOiJoZWFkaW5nLW9yZGVyIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJhcmlhLWxldmVsIik7aWYobnVsbCE9PWMpcmV0dXJuIHRoaXMuZGF0YShwYXJzZUludChjLDEwKSksITA7dmFyIGQ9YS50YWdOYW1lLm1hdGNoKC9IKFxkKS8pO3JldHVybiFkfHwodGhpcy5kYXRhKHBhcnNlSW50KGRbMV0sMTApKSwhMCl9LGFmdGVyOmZ1bmN0aW9uKGEsYil7aWYoYS5sZW5ndGg8MilyZXR1cm4gYTtmb3IodmFyIGM9YVswXS5kYXRhLGQ9MTtkPGEubGVuZ3RoO2QrKylhW2RdLnJlc3VsdCYmYVtkXS5kYXRhPmMrMSYmKGFbZF0ucmVzdWx0PSExKSxjPWFbZF0uZGF0YTtyZXR1cm4gYX19LHtpZDoiaHJlZi1uby1oYXNoIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJocmVmIik7cmV0dXJuIiMiIT09Y319LHtpZDoiaW50ZXJuYWwtbGluay1wcmVzZW50IixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiEhYS5xdWVyeVNlbGVjdG9yKCdhW2hyZWZePSIjIl0nKX19LHtpZDoibGFuZG1hcmsiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuIGEuZ2V0RWxlbWVudHNCeVRhZ05hbWUoIm1haW4iKS5sZW5ndGg+MHx8ISFhLnF1ZXJ5U2VsZWN0b3IoJ1tyb2xlPSJtYWluIl0nKX19LHtpZDoibWV0YS1yZWZyZXNoIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJjb250ZW50Iil8fCIiLGQ9Yy5zcGxpdCgvWzssXS8pO3JldHVybiIiPT09Y3x8IjAiPT09ZFswXX19LHtpZDoicmVnaW9uIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2Z1bmN0aW9uIGMoYSl7cmV0dXJuIGgmJmF4ZS5jb21tb25zLmRvbS5pc0ZvY3VzYWJsZShheGUuY29tbW9ucy5kb20uZ2V0RWxlbWVudEJ5UmVmZXJlbmNlKGgsImhyZWYiKSkmJmg9PT1hfWZ1bmN0aW9uIGQoYSl7dmFyIGI9YS5nZXRBdHRyaWJ1dGUoInJvbGUiKTtyZXR1cm4gYiYmZy5pbmRleE9mKGIpIT09LTF9ZnVuY3Rpb24gZShhKXtyZXR1cm4gZChhKT9udWxsOmMoYSk/ZihhKTpheGUuY29tbW9ucy5kb20uaXNWaXNpYmxlKGEsITApJiYoYXhlLmNvbW1vbnMudGV4dC52aXNpYmxlKGEsITAsITApfHxheGUuY29tbW9ucy5kb20uaXNWaXN1YWxDb250ZW50KGEpKT9hOmYoYSl9ZnVuY3Rpb24gZihhKXt2YXIgYj1heGUuY29tbW9ucy51dGlscy50b0FycmF5KGEuY2hpbGRyZW4pO3JldHVybiAwPT09Yi5sZW5ndGg/W106Yi5tYXAoZSkuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBudWxsIT09YX0pLnJlZHVjZShmdW5jdGlvbihhLGIpe3JldHVybiBhLmNvbmNhdChiKX0sW10pfXZhciBnPWF4ZS5jb21tb25zLmFyaWEuZ2V0Um9sZXNCeVR5cGUoImxhbmRtYXJrIiksaD1hLnF1ZXJ5U2VsZWN0b3IoImFbaHJlZl0iKSxpPWYoYSk7cmV0dXJuIHRoaXMucmVsYXRlZE5vZGVzKGkpLCFpLmxlbmd0aH0sYWZ0ZXI6ZnVuY3Rpb24oYSxiKXtyZXR1cm5bYVswXV19fSx7aWQ6InNraXAtbGluayIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4gYXhlLmNvbW1vbnMuZG9tLmlzRm9jdXNhYmxlKGF4ZS5jb21tb25zLmRvbS5nZXRFbGVtZW50QnlSZWZlcmVuY2UoYSwiaHJlZiIpKX0sYWZ0ZXI6ZnVuY3Rpb24oYSxiKXtyZXR1cm5bYVswXV19fSx7aWQ6InVuaXF1ZS1mcmFtZS10aXRsZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1heGUuY29tbW9ucy50ZXh0LnNhbml0aXplKGEudGl0bGUpLnRyaW0oKS50b0xvd2VyQ2FzZSgpO3JldHVybiB0aGlzLmRhdGEoYyksITB9LGFmdGVyOmZ1bmN0aW9uKGEsYil7dmFyIGM9e307cmV0dXJuIGEuZm9yRWFjaChmdW5jdGlvbihhKXtjW2EuZGF0YV09dm9pZCAwIT09Y1thLmRhdGFdPysrY1thLmRhdGFdOjB9KSxhLmZvckVhY2goZnVuY3Rpb24oYSl7YS5yZXN1bHQ9ISFjW2EuZGF0YV19KSxhfX0se2lkOiJhcmlhLWxhYmVsIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJhcmlhLWxhYmVsIik7cmV0dXJuISEoYz9heGUuY29tbW9ucy50ZXh0LnNhbml0aXplKGMpLnRyaW0oKToiIil9fSx7aWQ6ImFyaWEtbGFiZWxsZWRieSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1heGUuY29tbW9ucy5kb20uaWRyZWZzO3JldHVybiBjKGEsImFyaWEtbGFiZWxsZWRieSIpLnNvbWUoZnVuY3Rpb24oYSl7cmV0dXJuIGEmJmF4ZS5jb21tb25zLnRleHQuYWNjZXNzaWJsZVRleHQoYSwhMCl9KX19LHtpZDoiYnV0dG9uLWhhcy12aXNpYmxlLXRleHQiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9YS5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpLGQ9YS5nZXRBdHRyaWJ1dGUoInJvbGUiKSxlPXZvaWQgMDtyZXR1cm4oIkJVVFRPTiI9PT1jfHwiYnV0dG9uIj09PWQmJiJJTlBVVCIhPT1jKSYmKGU9YXhlLmNvbW1vbnMudGV4dC5hY2Nlc3NpYmxlVGV4dChhKSx0aGlzLmRhdGEoZSksISFlKX19LHtpZDoiZG9jLWhhcy10aXRsZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1kb2N1bWVudC50aXRsZTtyZXR1cm4hIShjP2F4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoYykudHJpbSgpOiIiKX19LHtpZDoiZHVwbGljYXRlLWlkIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2lmKCFhLmlkLnRyaW0oKSlyZXR1cm4hMDtmb3IodmFyIGM9ZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnW2lkPSInK2F4ZS5jb21tb25zLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEuaWQpKyciXScpLGQ9W10sZT0wO2U8Yy5sZW5ndGg7ZSsrKWNbZV0hPT1hJiZkLnB1c2goY1tlXSk7cmV0dXJuIGQubGVuZ3RoJiZ0aGlzLnJlbGF0ZWROb2RlcyhkKSx0aGlzLmRhdGEoYS5nZXRBdHRyaWJ1dGUoImlkIikpLGMubGVuZ3RoPD0xfSxhZnRlcjpmdW5jdGlvbihhLGIpe3ZhciBjPVtdO3JldHVybiBhLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4gYy5pbmRleE9mKGEuZGF0YSk9PT0tMSYmKGMucHVzaChhLmRhdGEpLCEwKX0pfX0se2lkOiJleGlzdHMiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuITB9fSx7aWQ6Imhhcy1hbHQiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuIGEuaGFzQXR0cmlidXRlKCJhbHQiKX19LHtpZDoiaGFzLXZpc2libGUtdGV4dCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4gYXhlLmNvbW1vbnMudGV4dC5hY2Nlc3NpYmxlVGV4dChhKS5sZW5ndGg+MH19LHtpZDoiaXMtb24tc2NyZWVuIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiBheGUuY29tbW9ucy5kb20uaXNWaXNpYmxlKGEsITEpJiYhYXhlLmNvbW1vbnMuZG9tLmlzT2Zmc2NyZWVuKGEpfX0se2lkOiJub24tZW1wdHktYWx0IixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJhbHQiKTtyZXR1cm4hIShjP2F4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoYykudHJpbSgpOiIiKX19LHtpZDoibm9uLWVtcHR5LWlmLXByZXNlbnQiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9YS5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpLGQ9KGEuZ2V0QXR0cmlidXRlKCJ0eXBlIil8fCIiKS50b0xvd2VyQ2FzZSgpLGU9YS5nZXRBdHRyaWJ1dGUoInZhbHVlIik7cmV0dXJuIHRoaXMuZGF0YShlKSwiSU5QVVQiPT09YyYmWyJzdWJtaXQiLCJyZXNldCJdLmluZGV4T2YoZCkhPT0tMSYmbnVsbD09PWV9fSx7aWQ6Im5vbi1lbXB0eS10aXRsZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1hLmdldEF0dHJpYnV0ZSgidGl0bGUiKTtyZXR1cm4hIShjP2F4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoYykudHJpbSgpOiIiKX19LHtpZDoibm9uLWVtcHR5LXZhbHVlIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJ2YWx1ZSIpO3JldHVybiEhKGM/YXhlLmNvbW1vbnMudGV4dC5zYW5pdGl6ZShjKS50cmltKCk6IiIpfX0se2lkOiJyb2xlLW5vbmUiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuIm5vbmUiPT09YS5nZXRBdHRyaWJ1dGUoInJvbGUiKX19LHtpZDoicm9sZS1wcmVzZW50YXRpb24iLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuInByZXNlbnRhdGlvbiI9PT1hLmdldEF0dHJpYnV0ZSgicm9sZSIpfX0se2lkOiJjYXB0aW9uLWZha2VkIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWF4ZS5jb21tb25zLnRhYmxlLnRvR3JpZChhKSxkPWNbMF07cmV0dXJuIGMubGVuZ3RoPD0xfHxkLmxlbmd0aDw9MXx8YS5yb3dzLmxlbmd0aDw9MXx8ZC5yZWR1Y2UoZnVuY3Rpb24oYSxiLGMpe3JldHVybiBhfHxiIT09ZFtjKzFdJiZ2b2lkIDAhPT1kW2MrMV19LCExKX19LHtpZDoiaGFzLWNhcHRpb24iLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuISFhLmNhcHRpb259fSx7aWQ6Imhhcy1zdW1tYXJ5IixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiEhYS5zdW1tYXJ5fX0se2lkOiJoYXMtdGgiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7Zm9yKHZhciBjLGQsZT1bXSxmPTAsZz1hLnJvd3MubGVuZ3RoO2Y8ZztmKyspe2M9YS5yb3dzW2ZdO2Zvcih2YXIgaD0wLGk9Yy5jZWxscy5sZW5ndGg7aDxpO2grKylkPWMuY2VsbHNbaF0sIlRIIiE9PWQubm9kZU5hbWUudG9VcHBlckNhc2UoKSYmWyJyb3doZWFkZXIiLCJjb2x1bW5oZWFkZXIiXS5pbmRleE9mKGQuZ2V0QXR0cmlidXRlKCJyb2xlIikpPT09LTF8fGUucHVzaChkKX1yZXR1cm4hIWUubGVuZ3RoJiYodGhpcy5yZWxhdGVkTm9kZXMoZSksITApfX0se2lkOiJodG1sNS1zY29wZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4hIWF4ZS5jb21tb25zLmRvbS5pc0hUTUw1KGRvY3VtZW50KSYmIlRIIj09PWEubm9kZU5hbWUudG9VcHBlckNhc2UoKX19LHtpZDoic2FtZS1jYXB0aW9uLXN1bW1hcnkiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuISghYS5zdW1tYXJ5fHwhYS5jYXB0aW9uKSYmYS5zdW1tYXJ5PT09YXhlLmNvbW1vbnMudGV4dC5hY2Nlc3NpYmxlVGV4dChhLmNhcHRpb24pfX0se2lkOiJzY29wZS12YWx1ZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtiPWJ8fHt9O3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJzY29wZSIpLnRvTG93ZXJDYXNlKCksZD1bInJvdyIsImNvbCIsInJvd2dyb3VwIiwiY29sZ3JvdXAiXXx8Yi52YWx1ZXM7cmV0dXJuIGQuaW5kZXhPZihjKSE9PS0xfX0se2lkOiJ0ZC1oYXMtaGVhZGVyIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWF4ZS5jb21tb25zLnRhYmxlLGQ9W10sZT1jLmdldEFsbENlbGxzKGEpO3JldHVybiBlLmZvckVhY2goZnVuY3Rpb24oYSl7aWYoIiIhPT1hLnRleHRDb250ZW50LnRyaW0oKSYmYy5pc0RhdGFDZWxsKGEpJiYhYXhlLmNvbW1vbnMuYXJpYS5sYWJlbChhKSl7dmFyIGI9Yy5nZXRIZWFkZXJzKGEpO2I9Yi5yZWR1Y2UoZnVuY3Rpb24oYSxiKXtyZXR1cm4gYXx8bnVsbCE9PWImJiEhYi50ZXh0Q29udGVudC50cmltKCl9LCExKSxifHxkLnB1c2goYSl9fSksIWQubGVuZ3RofHwodGhpcy5yZWxhdGVkTm9kZXMoZCksITEpfX0se2lkOiJ0ZC1oZWFkZXJzLWF0dHIiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7Zm9yKHZhciBjPVtdLGQ9MCxlPWEucm93cy5sZW5ndGg7ZDxlO2QrKylmb3IodmFyIGY9YS5yb3dzW2RdLGc9MCxoPWYuY2VsbHMubGVuZ3RoO2c8aDtnKyspYy5wdXNoKGYuY2VsbHNbZ10pO3ZhciBpPWMucmVkdWNlKGZ1bmN0aW9uKGEsYil7cmV0dXJuIGIuaWQmJmEucHVzaChiLmlkKSxhfSxbXSksaj1jLnJlZHVjZShmdW5jdGlvbihhLGIpe3ZhciBjLGQsZT0oYi5nZXRBdHRyaWJ1dGUoImhlYWRlcnMiKXx8IiIpLnNwbGl0KC9ccy8pLnJlZHVjZShmdW5jdGlvbihhLGIpe3JldHVybiBiPWIudHJpbSgpLGImJmEucHVzaChiKSxhfSxbXSk7cmV0dXJuIDAhPT1lLmxlbmd0aCYmKGIuaWQmJihjPWUuaW5kZXhPZihiLmlkLnRyaW0oKSkhPT0tMSksZD1lLnJlZHVjZShmdW5jdGlvbihhLGIpe3JldHVybiBhfHxpLmluZGV4T2YoYik9PT0tMX0sITEpLChjfHxkKSYmYS5wdXNoKGIpKSxhfSxbXSk7cmV0dXJuIShqLmxlbmd0aD4wKXx8KHRoaXMucmVsYXRlZE5vZGVzKGopLCExKX19LHtpZDoidGgtaGFzLWRhdGEtY2VsbHMiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9YXhlLmNvbW1vbnMudGFibGUsZD1jLmdldEFsbENlbGxzKGEpLGU9dGhpcyxmPVtdO2QuZm9yRWFjaChmdW5jdGlvbihhKXt2YXIgYj1hLmdldEF0dHJpYnV0ZSgiaGVhZGVycyIpO2ImJihmPWYuY29uY2F0KGIuc3BsaXQoL1xzKy8pKSk7dmFyIGM9YS5nZXRBdHRyaWJ1dGUoImFyaWEtbGFiZWxsZWRieSIpO2MmJihmPWYuY29uY2F0KGMuc3BsaXQoL1xzKy8pKSl9KTt2YXIgZz1kLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4iIiE9PWF4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoYS50ZXh0Q29udGVudCkmJigiVEgiPT09YS5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpfHxbInJvd2hlYWRlciIsImNvbHVtbmhlYWRlciJdLmluZGV4T2YoYS5nZXRBdHRyaWJ1dGUoInJvbGUiKSkhPT0tMSl9KSxoPWMudG9HcmlkKGEpO3JldHVybiBnLnJlZHVjZShmdW5jdGlvbihhLGIpe2lmKGIuaWQmJmYuaW5kZXhPZihiLmlkKSE9PS0xKXJldHVybiEhYXx8YTt2YXIgZD0hMSxnPWMuZ2V0Q2VsbFBvc2l0aW9uKGIsaCk7cmV0dXJuIGMuaXNDb2x1bW5IZWFkZXIoYikmJihkPWMudHJhdmVyc2UoImRvd24iLGcsaCkucmVkdWNlKGZ1bmN0aW9uKGEsYil7cmV0dXJuIGF8fCIiIT09Yi50ZXh0Q29udGVudC50cmltKCkmJiFjLmlzQ29sdW1uSGVhZGVyKGIpfSwhMSkpLCFkJiZjLmlzUm93SGVhZGVyKGIpJiYoZD1jLnRyYXZlcnNlKCJyaWdodCIsZyxoKS5yZWR1Y2UoZnVuY3Rpb24oYSxiKXsKcmV0dXJuIGF8fCIiIT09Yi50ZXh0Q29udGVudC50cmltKCkmJiFjLmlzUm93SGVhZGVyKGIpfSwhMSkpLGR8fGUucmVsYXRlZE5vZGVzKGIpLGEmJmR9LCEwKX19XSxjb21tb25zOmZ1bmN0aW9uKCl7ZnVuY3Rpb24gYShhKXtyZXR1cm4gYS5nZXRQcm9wZXJ0eVZhbHVlKCJmb250LWZhbWlseSIpLnNwbGl0KC9bLDtdL2cpLm1hcChmdW5jdGlvbihhKXtyZXR1cm4gYS50cmltKCkudG9Mb3dlckNhc2UoKX0pfWZ1bmN0aW9uIGIoYixjKXt2YXIgZD13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShiKTtpZigibm9uZSIhPT1kLmdldFByb3BlcnR5VmFsdWUoImJhY2tncm91bmQtaW1hZ2UiKSlyZXR1cm4hMDt2YXIgZT1bImJvcmRlci1ib3R0b20iLCJib3JkZXItdG9wIiwib3V0bGluZSJdLnJlZHVjZShmdW5jdGlvbihhLGIpe3ZhciBjPW5ldyB1LkNvbG9yO3JldHVybiBjLnBhcnNlUmdiU3RyaW5nKGQuZ2V0UHJvcGVydHlWYWx1ZShiKyItY29sb3IiKSksYXx8Im5vbmUiIT09ZC5nZXRQcm9wZXJ0eVZhbHVlKGIrIi1zdHlsZSIpJiZwYXJzZUZsb2F0KGQuZ2V0UHJvcGVydHlWYWx1ZShiKyItd2lkdGgiKSk+MCYmMCE9PWMuYWxwaGF9LCExKTtpZihlKXJldHVybiEwO3ZhciBmPXdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGMpO2lmKGEoZClbMF0hPT1hKGYpWzBdKXJldHVybiEwO3ZhciBnPVsidGV4dC1kZWNvcmF0aW9uLWxpbmUiLCJ0ZXh0LWRlY29yYXRpb24tc3R5bGUiLCJmb250LXdlaWdodCIsImZvbnQtc3R5bGUiLCJmb250LXNpemUiXS5yZWR1Y2UoZnVuY3Rpb24oYSxiKXtyZXR1cm4gYXx8ZC5nZXRQcm9wZXJ0eVZhbHVlKGIpIT09Zi5nZXRQcm9wZXJ0eVZhbHVlKGIpfSwhMSksaD1kLmdldFByb3BlcnR5VmFsdWUoInRleHQtZGVjb3JhdGlvbiIpO3JldHVybiBoLnNwbGl0KCIgIikubGVuZ3RoPDMmJihnPWd8fGghPT1mLmdldFByb3BlcnR5VmFsdWUoInRleHQtZGVjb3JhdGlvbiIpKSxnfWZ1bmN0aW9uIGMoYSxiKXt2YXIgYz1hLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCk7cmV0dXJuISF5LmluY2x1ZGVzKGMpfHwoYj1ifHx3aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShhKSwibm9uZSIhPT1iLmdldFByb3BlcnR5VmFsdWUoImJhY2tncm91bmQtaW1hZ2UiKSl9ZnVuY3Rpb24gZChhLGIpe2I9Ynx8d2luZG93LmdldENvbXB1dGVkU3R5bGUoYSk7dmFyIGM9bmV3IHUuQ29sb3I7aWYoYy5wYXJzZVJnYlN0cmluZyhiLmdldFByb3BlcnR5VmFsdWUoImJhY2tncm91bmQtY29sb3IiKSksMCE9PWMuYWxwaGEpe3ZhciBkPWIuZ2V0UHJvcGVydHlWYWx1ZSgib3BhY2l0eSIpO2MuYWxwaGE9Yy5hbHBoYSpkfXJldHVybiBjfWZ1bmN0aW9uIGUoYSxiKXt2YXIgYz0wO2lmKGE+MClmb3IodmFyIGU9YS0xO2U+PTA7ZS0tKXt2YXIgZj1iW2VdLGc9d2luZG93LmdldENvbXB1dGVkU3R5bGUoZiksaD1kKGYsZyk7aC5hbHBoYT9jKz1oLmFscGhhOmIuc3BsaWNlKGUsMSl9cmV0dXJuIGN9ZnVuY3Rpb24gZihhLGIpeyJ1c2Ugc3RyaWN0Ijt2YXIgYz1iKGEpO2ZvcihhPWEuZmlyc3RDaGlsZDthOyljIT09ITEmJmYoYSxiKSxhPWEubmV4dFNpYmxpbmd9ZnVuY3Rpb24gZyhhKXsidXNlIHN0cmljdCI7dmFyIGI9d2luZG93LmdldENvbXB1dGVkU3R5bGUoYSkuZ2V0UHJvcGVydHlWYWx1ZSgiZGlzcGxheSIpO3JldHVybiB6LmluZGV4T2YoYikhPT0tMXx8InRhYmxlLSI9PT1iLnN1YnN0cigwLDYpfWZ1bmN0aW9uIGgoYSl7InVzZSBzdHJpY3QiO3ZhciBiPWEubWF0Y2goL3JlY3RccypcKChbMC05XSspcHgsP1xzKihbMC05XSspcHgsP1xzKihbMC05XSspcHgsP1xzKihbMC05XSspcHhccypcKS8pO3JldHVybiEoIWJ8fDUhPT1iLmxlbmd0aCkmJihiWzNdLWJbMV08PTAmJmJbMl0tYls0XTw9MCl9ZnVuY3Rpb24gaShhKXt2YXIgYj1udWxsO3JldHVybiBhLmlkJiYoYj1kb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdsYWJlbFtmb3I9IicrYXhlLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEuaWQpKyciXScpKT9iOmI9di5maW5kVXAoYSwibGFiZWwiKX1mdW5jdGlvbiBqKGEpe3JldHVyblsiYnV0dG9uIiwicmVzZXQiLCJzdWJtaXQiXS5pbmRleE9mKGEudHlwZSkhPT0tMX1mdW5jdGlvbiBrKGEpe3ZhciBiPWEubm9kZU5hbWUudG9VcHBlckNhc2UoKTtyZXR1cm4iVEVYVEFSRUEiPT09Ynx8IlNFTEVDVCI9PT1ifHwiSU5QVVQiPT09YiYmImhpZGRlbiIhPT1hLnR5cGUudG9Mb3dlckNhc2UoKX1mdW5jdGlvbiBsKGEpe3JldHVyblsiQlVUVE9OIiwiU1VNTUFSWSIsIkEiXS5pbmRleE9mKGEubm9kZU5hbWUudG9VcHBlckNhc2UoKSkhPT0tMX1mdW5jdGlvbiBtKGEpe3JldHVyblsiVEFCTEUiLCJGSUdVUkUiXS5pbmRleE9mKGEubm9kZU5hbWUudG9VcHBlckNhc2UoKSkhPT0tMX1mdW5jdGlvbiBuKGEpe3ZhciBiPWEubm9kZU5hbWUudG9VcHBlckNhc2UoKTtpZigiSU5QVVQiPT09YilyZXR1cm4hYS5oYXNBdHRyaWJ1dGUoInR5cGUiKXx8Qi5pbmRleE9mKGEuZ2V0QXR0cmlidXRlKCJ0eXBlIikudG9Mb3dlckNhc2UoKSkhPT0tMSYmYS52YWx1ZT9hLnZhbHVlOiIiO2lmKCJTRUxFQ1QiPT09Yil7dmFyIGM9YS5vcHRpb25zO2lmKGMmJmMubGVuZ3RoKXtmb3IodmFyIGQ9IiIsZT0wO2U8Yy5sZW5ndGg7ZSsrKWNbZV0uc2VsZWN0ZWQmJihkKz0iICIrY1tlXS50ZXh0KTtyZXR1cm4geC5zYW5pdGl6ZShkKX1yZXR1cm4iIn1yZXR1cm4iVEVYVEFSRUEiPT09YiYmYS52YWx1ZT9hLnZhbHVlOiIifWZ1bmN0aW9uIG8oYSxiKXt2YXIgYz1hLnF1ZXJ5U2VsZWN0b3IoYi50b0xvd2VyQ2FzZSgpKTtyZXR1cm4gYz94LmFjY2Vzc2libGVUZXh0KGMpOiIifWZ1bmN0aW9uIHAoYSl7aWYoIWEpcmV0dXJuITE7c3dpdGNoKGEubm9kZU5hbWUudG9VcHBlckNhc2UoKSl7Y2FzZSJTRUxFQ1QiOmNhc2UiVEVYVEFSRUEiOnJldHVybiEwO2Nhc2UiSU5QVVQiOnJldHVybiFhLmhhc0F0dHJpYnV0ZSgidHlwZSIpfHxCLmluZGV4T2YoYS5nZXRBdHRyaWJ1dGUoInR5cGUiKS50b0xvd2VyQ2FzZSgpKSE9PS0xO2RlZmF1bHQ6cmV0dXJuITF9fWZ1bmN0aW9uIHEoYSl7dmFyIGI9YS5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpO3JldHVybiJJTlBVVCI9PT1iJiYiaW1hZ2UiPT09YS50eXBlLnRvTG93ZXJDYXNlKCl8fFsiSU1HIiwiQVBQTEVUIiwiQVJFQSJdLmluZGV4T2YoYikhPT0tMX1mdW5jdGlvbiByKGEpe3JldHVybiEheC5zYW5pdGl6ZShhKX12YXIgY29tbW9ucz17fSxzPWNvbW1vbnMuYXJpYT17fSx0PXMuX2x1dD17fTt0LmF0dHJpYnV0ZXM9eyJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiOnt0eXBlOiJpZHJlZiJ9LCJhcmlhLWF0b21pYyI6e3R5cGU6ImJvb2xlYW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSJdfSwiYXJpYS1hdXRvY29tcGxldGUiOnt0eXBlOiJubXRva2VuIix2YWx1ZXM6WyJpbmxpbmUiLCJsaXN0IiwiYm90aCIsIm5vbmUiXX0sImFyaWEtYnVzeSI6e3R5cGU6ImJvb2xlYW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSJdfSwiYXJpYS1jaGVja2VkIjp7dHlwZToibm10b2tlbiIsdmFsdWVzOlsidHJ1ZSIsImZhbHNlIiwibWl4ZWQiLCJ1bmRlZmluZWQiXX0sImFyaWEtY29sY291bnQiOnt0eXBlOiJpbnQifSwiYXJpYS1jb2xpbmRleCI6e3R5cGU6ImludCJ9LCJhcmlhLWNvbHNwYW4iOnt0eXBlOiJpbnQifSwiYXJpYS1jb250cm9scyI6e3R5cGU6ImlkcmVmcyJ9LCJhcmlhLWRlc2NyaWJlZGJ5Ijp7dHlwZToiaWRyZWZzIn0sImFyaWEtZGlzYWJsZWQiOnt0eXBlOiJib29sZWFuIix2YWx1ZXM6WyJ0cnVlIiwiZmFsc2UiXX0sImFyaWEtZHJvcGVmZmVjdCI6e3R5cGU6Im5tdG9rZW5zIix2YWx1ZXM6WyJjb3B5IiwibW92ZSIsInJlZmVyZW5jZSIsImV4ZWN1dGUiLCJwb3B1cCIsIm5vbmUiXX0sImFyaWEtZXhwYW5kZWQiOnt0eXBlOiJubXRva2VuIix2YWx1ZXM6WyJ0cnVlIiwiZmFsc2UiLCJ1bmRlZmluZWQiXX0sImFyaWEtZmxvd3RvIjp7dHlwZToiaWRyZWZzIn0sImFyaWEtZ3JhYmJlZCI6e3R5cGU6Im5tdG9rZW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSIsInVuZGVmaW5lZCJdfSwiYXJpYS1oYXNwb3B1cCI6e3R5cGU6ImJvb2xlYW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSJdfSwiYXJpYS1oaWRkZW4iOnt0eXBlOiJib29sZWFuIix2YWx1ZXM6WyJ0cnVlIiwiZmFsc2UiXX0sImFyaWEtaW52YWxpZCI6e3R5cGU6Im5tdG9rZW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSIsInNwZWxsaW5nIiwiZ3JhbW1hciJdfSwiYXJpYS1sYWJlbCI6e3R5cGU6InN0cmluZyJ9LCJhcmlhLWxhYmVsbGVkYnkiOnt0eXBlOiJpZHJlZnMifSwiYXJpYS1sZXZlbCI6e3R5cGU6ImludCJ9LCJhcmlhLWxpdmUiOnt0eXBlOiJubXRva2VuIix2YWx1ZXM6WyJvZmYiLCJwb2xpdGUiLCJhc3NlcnRpdmUiXX0sImFyaWEtbXVsdGlsaW5lIjp7dHlwZToiYm9vbGVhbiIsdmFsdWVzOlsidHJ1ZSIsImZhbHNlIl19LCJhcmlhLW11bHRpc2VsZWN0YWJsZSI6e3R5cGU6ImJvb2xlYW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSJdfSwiYXJpYS1vcmllbnRhdGlvbiI6e3R5cGU6Im5tdG9rZW4iLHZhbHVlczpbImhvcml6b250YWwiLCJ2ZXJ0aWNhbCJdfSwiYXJpYS1vd25zIjp7dHlwZToiaWRyZWZzIn0sImFyaWEtcG9zaW5zZXQiOnt0eXBlOiJpbnQifSwiYXJpYS1wcmVzc2VkIjp7dHlwZToibm10b2tlbiIsdmFsdWVzOlsidHJ1ZSIsImZhbHNlIiwibWl4ZWQiLCJ1bmRlZmluZWQiXX0sImFyaWEtcmVhZG9ubHkiOnt0eXBlOiJib29sZWFuIix2YWx1ZXM6WyJ0cnVlIiwiZmFsc2UiXX0sImFyaWEtcmVsZXZhbnQiOnt0eXBlOiJubXRva2VucyIsdmFsdWVzOlsiYWRkaXRpb25zIiwicmVtb3ZhbHMiLCJ0ZXh0IiwiYWxsIl19LCJhcmlhLXJlcXVpcmVkIjp7dHlwZToiYm9vbGVhbiIsdmFsdWVzOlsidHJ1ZSIsImZhbHNlIl19LCJhcmlhLXJvd2NvdW50Ijp7dHlwZToiaW50In0sImFyaWEtcm93aW5kZXgiOnt0eXBlOiJpbnQifSwiYXJpYS1yb3dzcGFuIjp7dHlwZToiaW50In0sImFyaWEtc2VsZWN0ZWQiOnt0eXBlOiJubXRva2VuIix2YWx1ZXM6WyJ0cnVlIiwiZmFsc2UiLCJ1bmRlZmluZWQiXX0sImFyaWEtc2V0c2l6ZSI6e3R5cGU6ImludCJ9LCJhcmlhLXNvcnQiOnt0eXBlOiJubXRva2VuIix2YWx1ZXM6WyJhc2NlbmRpbmciLCJkZXNjZW5kaW5nIiwib3RoZXIiLCJub25lIl19LCJhcmlhLXZhbHVlbWF4Ijp7dHlwZToiZGVjaW1hbCJ9LCJhcmlhLXZhbHVlbWluIjp7dHlwZToiZGVjaW1hbCJ9LCJhcmlhLXZhbHVlbm93Ijp7dHlwZToiZGVjaW1hbCJ9LCJhcmlhLXZhbHVldGV4dCI6e3R5cGU6InN0cmluZyJ9fSx0Lmdsb2JhbEF0dHJpYnV0ZXM9WyJhcmlhLWF0b21pYyIsImFyaWEtYnVzeSIsImFyaWEtY29udHJvbHMiLCJhcmlhLWRlc2NyaWJlZGJ5IiwiYXJpYS1kaXNhYmxlZCIsImFyaWEtZHJvcGVmZmVjdCIsImFyaWEtZmxvd3RvIiwiYXJpYS1ncmFiYmVkIiwiYXJpYS1oYXNwb3B1cCIsImFyaWEtaGlkZGVuIiwiYXJpYS1pbnZhbGlkIiwiYXJpYS1sYWJlbCIsImFyaWEtbGFiZWxsZWRieSIsImFyaWEtbGl2ZSIsImFyaWEtb3ducyIsImFyaWEtcmVsZXZhbnQiXSx0LnJvbGU9e2FsZXJ0Ont0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LGFsZXJ0ZGlhbG9nOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LGFwcGxpY2F0aW9uOnt0eXBlOiJsYW5kbWFyayIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbH0sYXJ0aWNsZTp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiYXJ0aWNsZSJdfSxiYW5uZXI6e3R5cGU6ImxhbmRtYXJrIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiaGVhZGVyIl19LGJ1dHRvbjp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCIsImFyaWEtcHJlc3NlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJidXR0b24iLCdpbnB1dFt0eXBlPSJidXR0b24iXScsJ2lucHV0W3R5cGU9ImltYWdlIl0nLCdpbnB1dFt0eXBlPSJyZXNldCJdJywnaW5wdXRbdHlwZT0ic3VibWl0Il0nLCJzdW1tYXJ5Il19LGNlbGw6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtY29saW5kZXgiLCJhcmlhLWNvbHNwYW4iLCJhcmlhLXJvd2luZGV4IiwiYXJpYS1yb3dzcGFuIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJyb3ciXSxpbXBsaWNpdDpbInRkIiwidGgiXX0sY2hlY2tib3g6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7cmVxdWlyZWQ6WyJhcmlhLWNoZWNrZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsnaW5wdXRbdHlwZT0iY2hlY2tib3giXSddfSxjb2x1bW5oZWFkZXI6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiLCJhcmlhLXNvcnQiLCJhcmlhLXJlYWRvbmx5IiwiYXJpYS1zZWxlY3RlZCIsImFyaWEtcmVxdWlyZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpbInJvdyJdLGltcGxpY2l0OlsidGgiXX0sY29tYm9ib3g6e3R5cGU6ImNvbXBvc2l0ZSIsYXR0cmlidXRlczp7cmVxdWlyZWQ6WyJhcmlhLWV4cGFuZGVkIl0sYWxsb3dlZDpbImFyaWEtYXV0b2NvbXBsZXRlIiwiYXJpYS1yZXF1aXJlZCIsImFyaWEtYWN0aXZlZGVzY2VuZGFudCJdfSxvd25lZDp7YWxsOlsibGlzdGJveCIsInRleHRib3giXX0sbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LGNvbW1hbmQ6e25hbWVGcm9tOlsiYXV0aG9yIl0sdHlwZToiYWJzdHJhY3QifSxjb21wbGVtZW50YXJ5Ont0eXBlOiJsYW5kbWFyayIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbImFzaWRlIl19LGNvbXBvc2l0ZTp7bmFtZUZyb206WyJhdXRob3IiXSx0eXBlOiJhYnN0cmFjdCJ9LGNvbnRlbnRpbmZvOnt0eXBlOiJsYW5kbWFyayIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbImZvb3RlciJdfSxkZWZpbml0aW9uOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJkZCJdfSxkaWFsb2c6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbImRpYWxvZyJdfSxkaXJlY3Rvcnk6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpudWxsfSxkb2N1bWVudDp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiYm9keSJdfSxmb3JtOnt0eXBlOiJsYW5kbWFyayIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbImZvcm0iXX0sZ3JpZDp7dHlwZToiY29tcG9zaXRlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1sZXZlbCIsImFyaWEtbXVsdGlzZWxlY3RhYmxlIiwiYXJpYS1yZWFkb25seSIsImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6e29uZTpbInJvd2dyb3VwIiwicm93Il19LG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsidGFibGUiXX0sZ3JpZGNlbGw6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtc2VsZWN0ZWQiLCJhcmlhLXJlYWRvbmx5IiwiYXJpYS1leHBhbmRlZCIsImFyaWEtcmVxdWlyZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpbInJvdyJdLGltcGxpY2l0OlsidGQiLCJ0aCJdfSxncm91cDp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1hY3RpdmVkZXNjZW5kYW50IiwiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiZGV0YWlscyIsIm9wdGdyb3VwIl19LGhlYWRpbmc6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtbGV2ZWwiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbImgxIiwiaDIiLCJoMyIsImg0IiwiaDUiLCJoNiJdfSxpbWc6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbImltZyJdfSxpbnB1dDp7bmFtZUZyb206WyJhdXRob3IiXSx0eXBlOiJhYnN0cmFjdCJ9LGxhbmRtYXJrOntuYW1lRnJvbTpbImF1dGhvciJdLHR5cGU6ImFic3RyYWN0In0sbGluazp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJhW2hyZWZdIl19LGxpc3Q6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6e2FsbDpbImxpc3RpdGVtIl19LG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0Olsib2wiLCJ1bCIsImRsIl19LGxpc3Rib3g6e3R5cGU6ImNvbXBvc2l0ZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtbXVsdGlzZWxlY3RhYmxlIiwiYXJpYS1yZXF1aXJlZCIsImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6e2FsbDpbIm9wdGlvbiJdfSxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbInNlbGVjdCJdfSxsaXN0aXRlbTp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1sZXZlbCIsImFyaWEtcG9zaW5zZXQiLCJhcmlhLXNldHNpemUiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJsaXN0Il0saW1wbGljaXQ6WyJsaSIsImR0Il19LGxvZzp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSxtYWluOnt0eXBlOiJsYW5kbWFyayIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbIm1haW4iXX0sbWFycXVlZTp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSxtYXRoOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJtYXRoIl19LG1lbnU6e3R5cGU6ImNvbXBvc2l0ZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6e29uZTpbIm1lbnVpdGVtIiwibWVudWl0ZW1yYWRpbyIsIm1lbnVpdGVtY2hlY2tib3giXX0sbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WydtZW51W3R5cGU9ImNvbnRleHQiXSddfSxtZW51YmFyOnt0eXBlOiJjb21wb3NpdGUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LG1lbnVpdGVtOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6bnVsbCxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0OlsibWVudSIsIm1lbnViYXIiXSxpbXBsaWNpdDpbJ21lbnVpdGVtW3R5cGU9ImNvbW1hbmQiXSddfSxtZW51aXRlbWNoZWNrYm94Ont0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e3JlcXVpcmVkOlsiYXJpYS1jaGVja2VkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJtZW51IiwibWVudWJhciJdLGltcGxpY2l0OlsnbWVudWl0ZW1bdHlwZT0iY2hlY2tib3giXSddfSxtZW51aXRlbXJhZGlvOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXNlbGVjdGVkIiwiYXJpYS1wb3NpbnNldCIsImFyaWEtc2V0c2l6ZSJdLHJlcXVpcmVkOlsiYXJpYS1jaGVja2VkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJtZW51IiwibWVudWJhciJdLGltcGxpY2l0OlsnbWVudWl0ZW1bdHlwZT0icmFkaW8iXSddfSxuYXZpZ2F0aW9uOnt0eXBlOiJsYW5kbWFyayIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbIm5hdiJdfSxub25lOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6bnVsbCxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSxub3RlOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LG9wdGlvbjp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1zZWxlY3RlZCIsImFyaWEtcG9zaW5zZXQiLCJhcmlhLXNldHNpemUiLCJhcmlhLWNoZWNrZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpbImxpc3Rib3giXSxpbXBsaWNpdDpbIm9wdGlvbiJdfSxwcmVzZW50YXRpb246e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczpudWxsLG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHByb2dyZXNzYmFyOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXZhbHVldGV4dCIsImFyaWEtdmFsdWVub3ciLCJhcmlhLXZhbHVlbWF4IiwiYXJpYS12YWx1ZW1pbiJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsicHJvZ3Jlc3MiXX0scmFkaW86e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtc2VsZWN0ZWQiLCJhcmlhLXBvc2luc2V0IiwiYXJpYS1zZXRzaXplIl0scmVxdWlyZWQ6WyJhcmlhLWNoZWNrZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsnaW5wdXRbdHlwZT0icmFkaW8iXSddfSxyYWRpb2dyb3VwOnt0eXBlOiJjb21wb3NpdGUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiLCJhcmlhLXJlcXVpcmVkIiwiYXJpYS1leHBhbmRlZCJdfSxvd25lZDp7YWxsOlsicmFkaW8iXX0sbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHJhbmdlOntuYW1lRnJvbTpbImF1dGhvciJdLHR5cGU6ImFic3RyYWN0In0scmVnaW9uOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJzZWN0aW9uIl19LHJvbGV0eXBlOnt0eXBlOiJhYnN0cmFjdCJ9LHJvdzp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1sZXZlbCIsImFyaWEtc2VsZWN0ZWQiLCJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOntvbmU6WyJjZWxsIiwiY29sdW1uaGVhZGVyIiwicm93aGVhZGVyIiwiZ3JpZGNlbGwiXX0sbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJyb3dncm91cCIsImdyaWQiLCJ0cmVlZ3JpZCIsInRhYmxlIl0saW1wbGljaXQ6WyJ0ciJdfSxyb3dncm91cDp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1hY3RpdmVkZXNjZW5kYW50IiwiYXJpYS1leHBhbmRlZCJdfSxvd25lZDp7YWxsOlsicm93Il19LG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0OlsiZ3JpZCIsInRhYmxlIl0saW1wbGljaXQ6WyJ0Ym9keSIsInRoZWFkIiwidGZvb3QiXX0scm93aGVhZGVyOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXNvcnQiLCJhcmlhLXJlcXVpcmVkIiwiYXJpYS1yZWFkb25seSIsImFyaWEtZXhwYW5kZWQiLCJhcmlhLXNlbGVjdGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJyb3ciXSxpbXBsaWNpdDpbInRoIl19LHNjcm9sbGJhcjp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOntyZXF1aXJlZDpbImFyaWEtY29udHJvbHMiLCJhcmlhLW9yaWVudGF0aW9uIiwiYXJpYS12YWx1ZW5vdyIsImFyaWEtdmFsdWVtYXgiLCJhcmlhLXZhbHVlbWluIl0sYWxsb3dlZDpbImFyaWEtdmFsdWV0ZXh0Il19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHNlYXJjaDp7dHlwZToibGFuZG1hcmsiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHNlYXJjaGJveDp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1hY3RpdmVkZXNjZW5kYW50IiwiYXJpYS1hdXRvY29tcGxldGUiLCJhcmlhLW11bHRpbGluZSIsImFyaWEtcmVhZG9ubHkiLCJhcmlhLXJlcXVpcmVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WydpbnB1dFt0eXBlPSJzZWFyY2giXSddfSxzZWN0aW9uOntuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sdHlwZToiYWJzdHJhY3QifSxzZWN0aW9uaGVhZDp7bmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLHR5cGU6ImFic3RyYWN0In0sc2VsZWN0OntuYW1lRnJvbTpbImF1dGhvciJdLHR5cGU6ImFic3RyYWN0In0sc2VwYXJhdG9yOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIiwiYXJpYS1vcmllbnRhdGlvbiJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiaHIiXX0sc2xpZGVyOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXZhbHVldGV4dCIsImFyaWEtb3JpZW50YXRpb24iXSxyZXF1aXJlZDpbImFyaWEtdmFsdWVub3ciLCJhcmlhLXZhbHVlbWF4IiwiYXJpYS12YWx1ZW1pbiJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsnaW5wdXRbdHlwZT0icmFuZ2UiXSddfSxzcGluYnV0dG9uOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXZhbHVldGV4dCIsImFyaWEtcmVxdWlyZWQiXSxyZXF1aXJlZDpbImFyaWEtdmFsdWVub3ciLCJhcmlhLXZhbHVlbWF4IiwiYXJpYS12YWx1ZW1pbiJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsnaW5wdXRbdHlwZT0ibnVtYmVyIl0nXX0sc3RhdHVzOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJvdXRwdXQiXX0sc3RydWN0dXJlOnt0eXBlOiJhYnN0cmFjdCJ9LCJzd2l0Y2giOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e3JlcXVpcmVkOlsiYXJpYS1jaGVja2VkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6bnVsbH0sdGFiOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXNlbGVjdGVkIiwiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0OlsidGFibGlzdCJdfSx0YWJsZTp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1jb2xjb3VudCIsImFyaWEtcm93Y291bnQiXX0sb3duZWQ6e29uZTpbInJvd2dyb3VwIiwicm93Il19LG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsidGFibGUiXX0sdGFibGlzdDp7dHlwZToiY29tcG9zaXRlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1hY3RpdmVkZXNjZW5kYW50IiwiYXJpYS1leHBhbmRlZCIsImFyaWEtbGV2ZWwiLCJhcmlhLW11bHRpc2VsZWN0YWJsZSJdfSxvd25lZDp7YWxsOlsidGFiIl19LG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSx0YWJwYW5lbDp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSx0ZXh0Ont0eXBlOiJzdHJ1Y3R1cmUiLG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6bnVsbH0sdGV4dGJveDp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1hY3RpdmVkZXNjZW5kYW50IiwiYXJpYS1hdXRvY29tcGxldGUiLCJhcmlhLW11bHRpbGluZSIsImFyaWEtcmVhZG9ubHkiLCJhcmlhLXJlcXVpcmVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WydpbnB1dFt0eXBlPSJ0ZXh0Il0nLCdpbnB1dFt0eXBlPSJlbWFpbCJdJywnaW5wdXRbdHlwZT0icGFzc3dvcmQiXScsJ2lucHV0W3R5cGU9InRlbCJdJywnaW5wdXRbdHlwZT0idXJsIl0nLCJpbnB1dDpub3QoW3R5cGVdKSIsInRleHRhcmVhIl19LHRpbWVyOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHRvb2xiYXI6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbJ21lbnVbdHlwZT0idG9vbGJhciJdJ119LHRvb2x0aXA6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpudWxsfSx0cmVlOnt0eXBlOiJjb21wb3NpdGUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiLCJhcmlhLW11bHRpc2VsZWN0YWJsZSIsImFyaWEtcmVxdWlyZWQiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOnthbGw6WyJ0cmVlaXRlbSJdfSxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbH0sdHJlZWdyaWQ6e3R5cGU6ImNvbXBvc2l0ZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtZXhwYW5kZWQiLCJhcmlhLWxldmVsIiwiYXJpYS1tdWx0aXNlbGVjdGFibGUiLCJhcmlhLXJlYWRvbmx5IiwiYXJpYS1yZXF1aXJlZCJdfSxvd25lZDp7YWxsOlsidHJlZWl0ZW0iXX0sbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHRyZWVpdGVtOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWNoZWNrZWQiLCJhcmlhLXNlbGVjdGVkIiwiYXJpYS1leHBhbmRlZCIsImFyaWEtbGV2ZWwiLCJhcmlhLXBvc2luc2V0IiwiYXJpYS1zZXRzaXplIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJ0cmVlZ3JpZCIsInRyZWUiXX0sd2lkZ2V0Ont0eXBlOiJhYnN0cmFjdCJ9LHdpbmRvdzp7bmFtZUZyb206WyJhdXRob3IiXSx0eXBlOiJhYnN0cmFjdCJ9fTt2YXIgdT17fTtjb21tb25zLmNvbG9yPXU7dmFyIHY9Y29tbW9ucy5kb209e30sdz1jb21tb25zLnRhYmxlPXt9LHg9Y29tbW9ucy50ZXh0PXt9O2NvbW1vbnMudXRpbHM9YXhlLnV0aWxzO3MucmVxdWlyZWRBdHRyPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj10LnJvbGVbYV0sYz1iJiZiLmF0dHJpYnV0ZXMmJmIuYXR0cmlidXRlcy5yZXF1aXJlZDtyZXR1cm4gY3x8W119LHMuYWxsb3dlZEF0dHI9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPXQucm9sZVthXSxjPWImJmIuYXR0cmlidXRlcyYmYi5hdHRyaWJ1dGVzLmFsbG93ZWR8fFtdLGQ9YiYmYi5hdHRyaWJ1dGVzJiZiLmF0dHJpYnV0ZXMucmVxdWlyZWR8fFtdO3JldHVybiBjLmNvbmNhdCh0Lmdsb2JhbEF0dHJpYnV0ZXMpLmNvbmNhdChkKX0scy52YWxpZGF0ZUF0dHI9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3JldHVybiEhdC5hdHRyaWJ1dGVzW2FdfSxzLnZhbGlkYXRlQXR0clZhbHVlPWZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO3ZhciBjLGQsZT1kb2N1bWVudCxmPWEuZ2V0QXR0cmlidXRlKGIpLGc9dC5hdHRyaWJ1dGVzW2JdO2lmKCFnKXJldHVybiEwO3N3aXRjaChnLnR5cGUpe2Nhc2UiYm9vbGVhbiI6Y2FzZSJubXRva2VuIjpyZXR1cm4ic3RyaW5nIj09dHlwZW9mIGYmJmcudmFsdWVzLmluZGV4T2YoZi50b0xvd2VyQ2FzZSgpKSE9PS0xO2Nhc2Uibm10b2tlbnMiOnJldHVybiBkPWF4ZS51dGlscy50b2tlbkxpc3QoZiksZC5yZWR1Y2UoZnVuY3Rpb24oYSxiKXtyZXR1cm4gYSYmZy52YWx1ZXMuaW5kZXhPZihiKSE9PS0xfSwwIT09ZC5sZW5ndGgpO2Nhc2UiaWRyZWYiOnJldHVybiEoIWZ8fCFlLmdldEVsZW1lbnRCeUlkKGYpKTtjYXNlImlkcmVmcyI6cmV0dXJuIGQ9YXhlLnV0aWxzLnRva2VuTGlzdChmKSxkLnJlZHVjZShmdW5jdGlvbihhLGIpe3JldHVybiEoIWF8fCFlLmdldEVsZW1lbnRCeUlkKGIpKX0sMCE9PWQubGVuZ3RoKTtjYXNlInN0cmluZyI6cmV0dXJuITA7Y2FzZSJkZWNpbWFsIjpyZXR1cm4gYz1mLm1hdGNoKC9eWy0rXT8oWzAtOV0qKVwuPyhbMC05XSopJC8pLCEoIWN8fCFjWzFdJiYhY1syXSk7Y2FzZSJpbnQiOnJldHVybi9eWy0rXT9bMC05XSskLy50ZXN0KGYpfX0scy5sYWJlbD1mdW5jdGlvbihhKXt2YXIgYixjO3JldHVybiBhLmdldEF0dHJpYnV0ZSgiYXJpYS1sYWJlbGxlZGJ5IikmJihiPXYuaWRyZWZzKGEsImFyaWEtbGFiZWxsZWRieSIpLGM9Yi5tYXAoZnVuY3Rpb24oYSl7cmV0dXJuIGE/eC52aXNpYmxlKGEsITApOiIifSkuam9pbigiICIpLnRyaW0oKSk/YzooYz1hLmdldEF0dHJpYnV0ZSgiYXJpYS1sYWJlbCIpLGMmJihjPXguc2FuaXRpemUoYykudHJpbSgpKT9jOm51bGwpfSxzLmlzVmFsaWRSb2xlPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtyZXR1cm4hIXQucm9sZVthXX0scy5nZXRSb2xlc1dpdGhOYW1lRnJvbUNvbnRlbnRzPWZ1bmN0aW9uKCl7cmV0dXJuIE9iamVjdC5rZXlzKHQucm9sZSkuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiB0LnJvbGVbYV0ubmFtZUZyb20mJnQucm9sZVthXS5uYW1lRnJvbS5pbmRleE9mKCJjb250ZW50cyIpIT09LTF9KX0scy5nZXRSb2xlc0J5VHlwZT1mdW5jdGlvbihhKXtyZXR1cm4gT2JqZWN0LmtleXModC5yb2xlKS5maWx0ZXIoZnVuY3Rpb24oYil7cmV0dXJuIHQucm9sZVtiXS50eXBlPT09YX0pfSxzLmdldFJvbGVUeXBlPWZ1bmN0aW9uKGEpe3ZhciBiPXQucm9sZVthXTtyZXR1cm4gYiYmYi50eXBlfHxudWxsfSxzLnJlcXVpcmVkT3duZWQ9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPW51bGwsYz10LnJvbGVbYV07cmV0dXJuIGMmJihiPWF4ZS51dGlscy5jbG9uZShjLm93bmVkKSksYn0scy5yZXF1aXJlZENvbnRleHQ9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPW51bGwsYz10LnJvbGVbYV07cmV0dXJuIGMmJihiPWF4ZS51dGlscy5jbG9uZShjLmNvbnRleHQpKSxifSxzLmltcGxpY2l0Tm9kZXM9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPW51bGwsYz10LnJvbGVbYV07cmV0dXJuIGMmJmMuaW1wbGljaXQmJihiPWF4ZS51dGlscy5jbG9uZShjLmltcGxpY2l0KSksYn0scy5pbXBsaWNpdFJvbGU9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiLGMsZCxlPXQucm9sZTtmb3IoYiBpbiBlKWlmKGUuaGFzT3duUHJvcGVydHkoYikmJihjPWVbYl0sYy5pbXBsaWNpdCkpZm9yKHZhciBmPTAsZz1jLmltcGxpY2l0Lmxlbmd0aDtmPGc7ZisrKWlmKGQ9Yy5pbXBsaWNpdFtmXSxheGUudXRpbHMubWF0Y2hlc1NlbGVjdG9yKGEsZCkpcmV0dXJuIGI7cmV0dXJuIG51bGx9LHUuQ29sb3I9ZnVuY3Rpb24oYSxiLGMsZCl7dGhpcy5yZWQ9YSx0aGlzLmdyZWVuPWIsdGhpcy5ibHVlPWMsdGhpcy5hbHBoYT1kLHRoaXMudG9IZXhTdHJpbmc9ZnVuY3Rpb24oKXt2YXIgYT1NYXRoLnJvdW5kKHRoaXMucmVkKS50b1N0cmluZygxNiksYj1NYXRoLnJvdW5kKHRoaXMuZ3JlZW4pLnRvU3RyaW5nKDE2KSxjPU1hdGgucm91bmQodGhpcy5ibHVlKS50b1N0cmluZygxNik7cmV0dXJuIiMiKyh0aGlzLnJlZD4xNS41P2E6IjAiK2EpKyh0aGlzLmdyZWVuPjE1LjU/YjoiMCIrYikrKHRoaXMuYmx1ZT4xNS41P2M6IjAiK2MpfTt2YXIgZT0vXnJnYlwoKFxkKyksIChcZCspLCAoXGQrKVwpJC8sZj0vXnJnYmFcKChcZCspLCAoXGQrKSwgKFxkKyksIChcZCooXC5cZCspPylcKS87dGhpcy5wYXJzZVJnYlN0cmluZz1mdW5jdGlvbihhKXtpZigidHJhbnNwYXJlbnQiPT09YSlyZXR1cm4gdGhpcy5yZWQ9MCx0aGlzLmdyZWVuPTAsdGhpcy5ibHVlPTAsdm9pZCh0aGlzLmFscGhhPTApO3ZhciBiPWEubWF0Y2goZSk7cmV0dXJuIGI/KHRoaXMucmVkPXBhcnNlSW50KGJbMV0sMTApLHRoaXMuZ3JlZW49cGFyc2VJbnQoYlsyXSwxMCksdGhpcy5ibHVlPXBhcnNlSW50KGJbM10sMTApLHZvaWQodGhpcy5hbHBoYT0xKSk6KGI9YS5tYXRjaChmKSxiPyh0aGlzLnJlZD1wYXJzZUludChiWzFdLDEwKSx0aGlzLmdyZWVuPXBhcnNlSW50KGJbMl0sMTApLHRoaXMuYmx1ZT1wYXJzZUludChiWzNdLDEwKSx2b2lkKHRoaXMuYWxwaGE9cGFyc2VGbG9hdChiWzRdKSkpOnZvaWQgMCl9LHRoaXMuZ2V0UmVsYXRpdmVMdW1pbmFuY2U9ZnVuY3Rpb24oKXt2YXIgYT10aGlzLnJlZC8yNTUsYj10aGlzLmdyZWVuLzI1NSxjPXRoaXMuYmx1ZS8yNTUsZD1hPD0uMDM5Mjg/YS8xMi45MjpNYXRoLnBvdygoYSsuMDU1KS8xLjA1NSwyLjQpLGU9Yjw9LjAzOTI4P2IvMTIuOTI6TWF0aC5wb3coKGIrLjA1NSkvMS4wNTUsMi40KSxmPWM8PS4wMzkyOD9jLzEyLjkyOk1hdGgucG93KChjKy4wNTUpLzEuMDU1LDIuNCk7cmV0dXJuLjIxMjYqZCsuNzE1MiplKy4wNzIyKmZ9fSx1LmZsYXR0ZW5Db2xvcnM9ZnVuY3Rpb24oYSxiKXt2YXIgYz1hLmFscGhhLGQ9KDEtYykqYi5yZWQrYyphLnJlZCxlPSgxLWMpKmIuZ3JlZW4rYyphLmdyZWVuLGY9KDEtYykqYi5ibHVlK2MqYS5ibHVlLGc9YS5hbHBoYStiLmFscGhhKigxLWEuYWxwaGEpO3JldHVybiBuZXcgdS5Db2xvcihkLGUsZixnKX0sdS5nZXRDb250cmFzdD1mdW5jdGlvbihhLGIpe2lmKCFifHwhYSlyZXR1cm4gbnVsbDtiLmFscGhhPDEmJihiPXUuZmxhdHRlbkNvbG9ycyhiLGEpKTt2YXIgYz1hLmdldFJlbGF0aXZlTHVtaW5hbmNlKCksZD1iLmdldFJlbGF0aXZlTHVtaW5hbmNlKCk7cmV0dXJuKE1hdGgubWF4KGQsYykrLjA1KS8oTWF0aC5taW4oZCxjKSsuMDUpfSx1Lmhhc1ZhbGlkQ29udHJhc3RSYXRpbz1mdW5jdGlvbihhLGIsYyxkKXt2YXIgZT11LmdldENvbnRyYXN0KGEsYiksZj1kJiZNYXRoLmNlaWwoNzIqYykvOTY8MTR8fCFkJiZNYXRoLmNlaWwoNzIqYykvOTY8MTg7cmV0dXJue2lzVmFsaWQ6ZiYmZT49NC41fHwhZiYmZT49Myxjb250cmFzdFJhdGlvOmV9fSx1LmVsZW1lbnRJc0Rpc3RpbmN0PWI7dmFyIHk9WyJJTUciLCJDQU5WQVMiLCJPQkpFQ1QiLCJJRlJBTUUiLCJWSURFTyIsIlNWRyJdO3UuZ2V0QmFja2dyb3VuZFN0YWNrPWZ1bmN0aW9uKGEpe3ZhciBiPWEuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCksZj12b2lkIDAsZz12b2lkIDA7aWYoIShiLmxlZnQ+d2luZG93LmlubmVyV2lkdGh8fGIudG9wPndpbmRvdy5pbm5lcldpZHRoKSl7Zj1NYXRoLm1pbihNYXRoLmNlaWwoYi5sZWZ0K2Iud2lkdGgvMiksd2luZG93LmlubmVyV2lkdGgtMSksZz1NYXRoLm1pbihNYXRoLmNlaWwoYi50b3ArYi5oZWlnaHQvMiksd2luZG93LmlubmVySGVpZ2h0LTEpO3ZhciBoPWRvY3VtZW50LmVsZW1lbnRzRnJvbVBvaW50KGYsZyk7aD12LnJlZHVjZVRvRWxlbWVudHNCZWxvd0Zsb2F0aW5nKGgsYSk7dmFyIGk9aC5pbmRleE9mKGRvY3VtZW50LmJvZHkpO2k+MSYmIWMoZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50KSYmMD09PWQoZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50KS5hbHBoYSYmKGguc3BsaWNlKGksMSksaC5zcGxpY2UoaC5pbmRleE9mKGRvY3VtZW50LmRvY3VtZW50RWxlbWVudCksMSksaC5wdXNoKGRvY3VtZW50LmJvZHkpKTt2YXIgaj1oLmluZGV4T2YoYSk7cmV0dXJuIGUoaixoKT49Ljk5P251bGw6aiE9PS0xP2g6bnVsbH19LHUuZ2V0QmFja2dyb3VuZENvbG9yPWZ1bmN0aW9uKGEpe3ZhciBiPWFyZ3VtZW50cy5sZW5ndGg+MSYmdm9pZCAwIT09YXJndW1lbnRzWzFdP2FyZ3VtZW50c1sxXTpbXSxlPWFyZ3VtZW50cy5sZW5ndGg+MiYmdm9pZCAwIT09YXJndW1lbnRzWzJdJiZhcmd1bWVudHNbMl07ZSE9PSEwJiZhLnNjcm9sbEludG9WaWV3KCk7dmFyIGY9W10sZz11LmdldEJhY2tncm91bmRTdGFjayhhKTtyZXR1cm4oZ3x8W10pLnNvbWUoZnVuY3Rpb24oZSl7dmFyIGc9d2luZG93LmdldENvbXB1dGVkU3R5bGUoZSksaD1kKGUsZyk7cmV0dXJuIGEhPT1lJiYhdi52aXN1YWxseUNvbnRhaW5zKGEsZSkmJjAhPT1oLmFscGhhfHxjKGUsZyk/KGY9bnVsbCxiLnB1c2goZSksITApOjAhPT1oLmFscGhhJiYoYi5wdXNoKGUpLGYucHVzaChoKSwxPT09aC5hbHBoYSl9KSxudWxsIT09ZiYmbnVsbCE9PWc/KGYucHVzaChuZXcgdS5Db2xvcigyNTUsMjU1LDI1NSwxKSksZi5yZWR1Y2UodS5mbGF0dGVuQ29sb3JzKSk6bnVsbH0sdi5pc09wYXF1ZT1mdW5jdGlvbihhKXt2YXIgYj13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShhKTtyZXR1cm4gYyhhLGIpfHwxPT09ZChhLGIpLmFscGhhfSx1LmdldEZvcmVncm91bmRDb2xvcj1mdW5jdGlvbihhLGIpe3ZhciBjPXdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGEpLGQ9bmV3IHUuQ29sb3I7ZC5wYXJzZVJnYlN0cmluZyhjLmdldFByb3BlcnR5VmFsdWUoImNvbG9yIikpO3ZhciBlPWMuZ2V0UHJvcGVydHlWYWx1ZSgib3BhY2l0eSIpO2lmKGQuYWxwaGE9ZC5hbHBoYSplLDE9PT1kLmFscGhhKXJldHVybiBkO3ZhciBmPXUuZ2V0QmFja2dyb3VuZENvbG9yKGEsW10sYik7cmV0dXJuIG51bGw9PT1mP251bGw6dS5mbGF0dGVuQ29sb3JzKGQsZil9LHYucmVkdWNlVG9FbGVtZW50c0JlbG93RmxvYXRpbmc9ZnVuY3Rpb24oYSxiKXt2YXIgYyxkLGUsZj1bImZpeGVkIiwic3RpY2t5Il0sZz1bXSxoPSExO2ZvcihjPTA7YzxhLmxlbmd0aDsrK2MpZD1hW2NdLGQ9PT1iJiYoaD0hMCksZT13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShkKSxofHxmLmluZGV4T2YoZS5wb3NpdGlvbik9PT0tMT9nLnB1c2goZCk6Zz1bXTtyZXR1cm4gZ30sdi5maW5kVXA9ZnVuY3Rpb24oYSxiKXsidXNlIHN0cmljdCI7dmFyIGMsZD1kb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKGIpLGU9ZC5sZW5ndGg7aWYoIWUpcmV0dXJuIG51bGw7Zm9yKGQ9YXhlLnV0aWxzLnRvQXJyYXkoZCksYz1hLnBhcmVudE5vZGU7YyYmZC5pbmRleE9mKGMpPT09LTE7KWM9Yy5wYXJlbnROb2RlO3JldHVybiBjfSx2LmdldEVsZW1lbnRCeVJlZmVyZW5jZT1mdW5jdGlvbihhLGIpeyJ1c2Ugc3RyaWN0Ijt2YXIgYyxkPWEuZ2V0QXR0cmlidXRlKGIpLGU9ZG9jdW1lbnQ7aWYoZCYmIiMiPT09ZC5jaGFyQXQoMCkpe2lmKGQ9ZC5zdWJzdHJpbmcoMSksYz1lLmdldEVsZW1lbnRCeUlkKGQpKXJldHVybiBjO2lmKGM9ZS5nZXRFbGVtZW50c0J5TmFtZShkKSxjLmxlbmd0aClyZXR1cm4gY1swXX1yZXR1cm4gbnVsbH0sdi5nZXRFbGVtZW50Q29vcmRpbmF0ZXM9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPXYuZ2V0U2Nyb2xsT2Zmc2V0KGRvY3VtZW50KSxjPWIubGVmdCxkPWIudG9wLGU9YS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtyZXR1cm57dG9wOmUudG9wK2QscmlnaHQ6ZS5yaWdodCtjLGJvdHRvbTplLmJvdHRvbStkLGxlZnQ6ZS5sZWZ0K2Msd2lkdGg6ZS5yaWdodC1lLmxlZnQsaGVpZ2h0OmUuYm90dG9tLWUudG9wfX0sdi5nZXRTY3JvbGxPZmZzZXQ9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO2lmKCFhLm5vZGVUeXBlJiZhLmRvY3VtZW50JiYoYT1hLmRvY3VtZW50KSw5PT09YS5ub2RlVHlwZSl7dmFyIGI9YS5kb2N1bWVudEVsZW1lbnQsYz1hLmJvZHk7cmV0dXJue2xlZnQ6YiYmYi5zY3JvbGxMZWZ0fHxjJiZjLnNjcm9sbExlZnR8fDAsdG9wOmImJmIuc2Nyb2xsVG9wfHxjJiZjLnNjcm9sbFRvcHx8MH19cmV0dXJue2xlZnQ6YS5zY3JvbGxMZWZ0LHRvcDphLnNjcm9sbFRvcH19LHYuZ2V0Vmlld3BvcnRTaXplPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYixjPWEuZG9jdW1lbnQsZD1jLmRvY3VtZW50RWxlbWVudDtyZXR1cm4gYS5pbm5lcldpZHRoP3t3aWR0aDphLmlubmVyV2lkdGgsaGVpZ2h0OmEuaW5uZXJIZWlnaHR9OmQ/e3dpZHRoOmQuY2xpZW50V2lkdGgsaGVpZ2h0OmQuY2xpZW50SGVpZ2h0fTooYj1jLmJvZHkse3dpZHRoOmIuY2xpZW50V2lkdGgsaGVpZ2h0OmIuY2xpZW50SGVpZ2h0fSl9LHYuaWRyZWZzPWZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO3ZhciBjLGQsZT1kb2N1bWVudCxmPVtdLGc9YS5nZXRBdHRyaWJ1dGUoYik7aWYoZylmb3IoZz1heGUudXRpbHMudG9rZW5MaXN0KGcpLGM9MCxkPWcubGVuZ3RoO2M8ZDtjKyspZi5wdXNoKGUuZ2V0RWxlbWVudEJ5SWQoZ1tjXSkpO3JldHVybiBmfSx2LmlzRm9jdXNhYmxlPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtpZighYXx8YS5kaXNhYmxlZHx8IXYuaXNWaXNpYmxlKGEpJiYiQVJFQSIhPT1hLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCkpcmV0dXJuITE7c3dpdGNoKGEubm9kZU5hbWUudG9VcHBlckNhc2UoKSl7Y2FzZSJBIjpjYXNlIkFSRUEiOmlmKGEuaHJlZilyZXR1cm4hMDticmVhaztjYXNlIklOUFVUIjpyZXR1cm4iaGlkZGVuIiE9PWEudHlwZTtjYXNlIlRFWFRBUkVBIjpjYXNlIlNFTEVDVCI6Y2FzZSJERVRBSUxTIjpjYXNlIkJVVFRPTiI6cmV0dXJuITB9dmFyIGI9YS5nZXRBdHRyaWJ1dGUoInRhYmluZGV4Iik7cmV0dXJuISghYnx8aXNOYU4ocGFyc2VJbnQoYiwxMCkpKX0sdi5pc0hUTUw1PWZ1bmN0aW9uKGEpe3ZhciBiPWEuZG9jdHlwZTtyZXR1cm4gbnVsbCE9PWImJigiaHRtbCI9PT1iLm5hbWUmJiFiLnB1YmxpY0lkJiYhYi5zeXN0ZW1JZCl9O3ZhciB6PVsiYmxvY2siLCJsaXN0LWl0ZW0iLCJ0YWJsZSIsImZsZXgiLCJncmlkIiwiaW5saW5lLWJsb2NrIl07di5pc0luVGV4dEJsb2NrPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtpZihnKGEpKXJldHVybiExO2Zvcih2YXIgYj1hLnBhcmVudE5vZGU7MT09PWIubm9kZVR5cGUmJiFnKGIpOyliPWIucGFyZW50Tm9kZTt2YXIgYz0iIixkPSIiLGU9MDtyZXR1cm4gZihiLGZ1bmN0aW9uKGIpe2lmKDI9PT1lKXJldHVybiExO2lmKDM9PT1iLm5vZGVUeXBlJiYoYys9Yi5ub2RlVmFsdWUpLDE9PT1iLm5vZGVUeXBlKXt2YXIgZj0oYi5ub2RlTmFtZXx8IiIpLnRvVXBwZXJDYXNlKCk7aWYoWyJCUiIsIkhSIl0uaW5kZXhPZihmKSE9PS0xKTA9PT1lPyhjPSIiLGQ9IiIpOmU9MjtlbHNle2lmKCJub25lIj09PWIuc3R5bGUuZGlzcGxheXx8ImhpZGRlbiI9PT1iLnN0eWxlLm92ZXJmbG93fHxbIiIsbnVsbCwibm9uZSJdLmluZGV4T2YoYi5zdHlsZVsiZmxvYXQiXSk9PT0tMXx8WyIiLG51bGwsInJlbGF0aXZlIl0uaW5kZXhPZihiLnN0eWxlLnBvc2l0aW9uKT09PS0xKXJldHVybiExO2lmKCJBIj09PWYmJmIuaHJlZnx8ImxpbmsiPT09KGIuZ2V0QXR0cmlidXRlKCJyb2xlIil8fCIiKS50b0xvd2VyQ2FzZSgpKXJldHVybiBiPT09YSYmKGU9MSksZCs9Yi50ZXh0Q29udGVudCwhMX19fSksYz1heGUuY29tbW9ucy50ZXh0LnNhbml0aXplKGMpLGQ9YXhlLmNvbW1vbnMudGV4dC5zYW5pdGl6ZShkKSxjLmxlbmd0aD5kLmxlbmd0aH0sdi5pc05vZGU9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3JldHVybiBhIGluc3RhbmNlb2YgTm9kZX0sdi5pc09mZnNjcmVlbj1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7dmFyIGIsYz1kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQsZD13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShkb2N1bWVudC5ib2R5fHxjKS5nZXRQcm9wZXJ0eVZhbHVlKCJkaXJlY3Rpb24iKSxlPXYuZ2V0RWxlbWVudENvb3JkaW5hdGVzKGEpO2lmKGUuYm90dG9tPDApcmV0dXJuITA7aWYoImx0ciI9PT1kKXtpZihlLnJpZ2h0PDApcmV0dXJuITB9ZWxzZSBpZihiPU1hdGgubWF4KGMuc2Nyb2xsV2lkdGgsdi5nZXRWaWV3cG9ydFNpemUod2luZG93KS53aWR0aCksZS5sZWZ0PmIpcmV0dXJuITA7cmV0dXJuITF9LHYuaXNWaXNpYmxlPWZ1bmN0aW9uKGEsYixjKXsidXNlIHN0cmljdCI7dmFyIGQsZT1hLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCksZj1hLnBhcmVudE5vZGU7cmV0dXJuIDk9PT1hLm5vZGVUeXBlfHwoZD13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShhLG51bGwpLG51bGwhPT1kJiYoISgibm9uZSI9PT1kLmdldFByb3BlcnR5VmFsdWUoImRpc3BsYXkiKXx8IlNUWUxFIj09PWUudG9VcHBlckNhc2UoKXx8IlNDUklQVCI9PT1lLnRvVXBwZXJDYXNlKCl8fCFiJiZoKGQuZ2V0UHJvcGVydHlWYWx1ZSgiY2xpcCIpKXx8IWMmJigiaGlkZGVuIj09PWQuZ2V0UHJvcGVydHlWYWx1ZSgidmlzaWJpbGl0eSIpfHwhYiYmdi5pc09mZnNjcmVlbihhKSl8fGImJiJ0cnVlIj09PWEuZ2V0QXR0cmlidXRlKCJhcmlhLWhpZGRlbiIpKSYmKCEhZiYmdi5pc1Zpc2libGUoZixiLCEwKSkpKX0sdi5pc1Zpc3VhbENvbnRlbnQ9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3N3aXRjaChhLnRhZ05hbWUudG9VcHBlckNhc2UoKSl7Y2FzZSJJTUciOmNhc2UiSUZSQU1FIjpjYXNlIk9CSkVDVCI6Y2FzZSJWSURFTyI6Y2FzZSJBVURJTyI6Y2FzZSJDQU5WQVMiOmNhc2UiU1ZHIjpjYXNlIk1BVEgiOmNhc2UiQlVUVE9OIjpjYXNlIlNFTEVDVCI6Y2FzZSJURVhUQVJFQSI6Y2FzZSJLRVlHRU4iOmNhc2UiUFJPR1JFU1MiOmNhc2UiTUVURVIiOnJldHVybiEwO2Nhc2UiSU5QVVQiOnJldHVybiJoaWRkZW4iIT09YS50eXBlO2RlZmF1bHQ6cmV0dXJuITF9fSx2LnZpc3VhbGx5Q29udGFpbnM9ZnVuY3Rpb24oYSxiKXt2YXIgYz1hLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLGQ9LjAxLGU9e3RvcDpjLnRvcCtkLGJvdHRvbTpjLmJvdHRvbS1kLGxlZnQ6Yy5sZWZ0K2QscmlnaHQ6Yy5yaWdodC1kfSxmPWIuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCksZz1mLnRvcCxoPWYubGVmdCxpPXt0b3A6Zy1iLnNjcm9sbFRvcCxib3R0b206Zy1iLnNjcm9sbFRvcCtiLnNjcm9sbEhlaWdodCxsZWZ0OmgtYi5zY3JvbGxMZWZ0LHJpZ2h0OmgtYi5zY3JvbGxMZWZ0K2Iuc2Nyb2xsV2lkdGh9O2lmKGUubGVmdDxpLmxlZnQmJmUubGVmdDxmLmxlZnR8fGUudG9wPGkudG9wJiZlLnRvcDxmLnRvcHx8ZS5yaWdodD5pLnJpZ2h0JiZlLnJpZ2h0PmYucmlnaHR8fGUuYm90dG9tPmkuYm90dG9tJiZlLmJvdHRvbT5mLmJvdHRvbSlyZXR1cm4hMTt2YXIgaj13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShiKTtyZXR1cm4hKGUucmlnaHQ+Zi5yaWdodHx8ZS5ib3R0b20+Zi5ib3R0b20pfHwoInNjcm9sbCI9PT1qLm92ZXJmbG93fHwiYXV0byI9PT1qLm92ZXJmbG93fHwiaGlkZGVuIj09PWoub3ZlcmZsb3d8fGIgaW5zdGFuY2VvZiBIVE1MQm9keUVsZW1lbnR8fGIgaW5zdGFuY2VvZiBIVE1MSHRtbEVsZW1lbnQpfSx2LnZpc3VhbGx5T3ZlcmxhcHM9ZnVuY3Rpb24oYSxiKXt2YXIgYz1iLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLGQ9Yy50b3AsZT1jLmxlZnQsZj17dG9wOmQtYi5zY3JvbGxUb3AsYm90dG9tOmQtYi5zY3JvbGxUb3ArYi5zY3JvbGxIZWlnaHQsbGVmdDplLWIuc2Nyb2xsTGVmdCxyaWdodDplLWIuc2Nyb2xsTGVmdCtiLnNjcm9sbFdpZHRofTtpZihhLmxlZnQ+Zi5yaWdodCYmYS5sZWZ0PmMucmlnaHR8fGEudG9wPmYuYm90dG9tJiZhLnRvcD5jLmJvdHRvbXx8YS5yaWdodDxmLmxlZnQmJmEucmlnaHQ8Yy5sZWZ0fHxhLmJvdHRvbTxmLnRvcCYmYS5ib3R0b208Yy50b3ApcmV0dXJuITE7dmFyIGc9d2luZG93LmdldENvbXB1dGVkU3R5bGUoYik7cmV0dXJuIShhLmxlZnQ+Yy5yaWdodHx8YS50b3A+Yy5ib3R0b20pfHwoInNjcm9sbCI9PT1nLm92ZXJmbG93fHwiYXV0byI9PT1nLm92ZXJmbG93fHxiIGluc3RhbmNlb2YgSFRNTEJvZHlFbGVtZW50fHxiIGluc3RhbmNlb2YgSFRNTEh0bWxFbGVtZW50KX0sdy5nZXRBbGxDZWxscz1mdW5jdGlvbihhKXt2YXIgYixjLGQsZSxmPVtdO2ZvcihiPTAsZD1hLnJvd3MubGVuZ3RoO2I8ZDtiKyspZm9yKGM9MCxlPWEucm93c1tiXS5jZWxscy5sZW5ndGg7YzxlO2MrKylmLnB1c2goYS5yb3dzW2JdLmNlbGxzW2NdKTtyZXR1cm4gZn0sdy5nZXRDZWxsUG9zaXRpb249ZnVuY3Rpb24oYSxiKXt2YXIgYyxkO2ZvcihifHwoYj13LnRvR3JpZCh2LmZpbmRVcChhLCJ0YWJsZSIpKSksYz0wO2M8Yi5sZW5ndGg7YysrKWlmKGJbY10mJihkPWJbY10uaW5kZXhPZihhKSxkIT09LTEpKXJldHVybnt4OmQseTpjfX0sdy5nZXRIZWFkZXJzPWZ1bmN0aW9uKGEpe2lmKGEuaGFzQXR0cmlidXRlKCJoZWFkZXJzIikpcmV0dXJuIGNvbW1vbnMuZG9tLmlkcmVmcyhhLCJoZWFkZXJzIik7dmFyIGI9Y29tbW9ucy50YWJsZS50b0dyaWQoY29tbW9ucy5kb20uZmluZFVwKGEsInRhYmxlIikpLGM9Y29tbW9ucy50YWJsZS5nZXRDZWxsUG9zaXRpb24oYSxiKSxkPXcudHJhdmVyc2UoImxlZnQiLGMsYikuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiB3LmlzUm93SGVhZGVyKGEpfSksZT13LnRyYXZlcnNlKCJ1cCIsYyxiKS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuIHcuaXNDb2x1bW5IZWFkZXIoYSl9KTtyZXR1cm5bXS5jb25jYXQoZCxlKS5yZXZlcnNlKCl9LHcuZ2V0U2NvcGU9ZnVuY3Rpb24oYSl7dmFyIGI9YS5nZXRBdHRyaWJ1dGUoInNjb3BlIiksYz1hLmdldEF0dHJpYnV0ZSgicm9sZSIpO2lmKGEgaW5zdGFuY2VvZiBFbGVtZW50PT0hMXx8WyJURCIsIlRIIl0uaW5kZXhPZihhLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCkpPT09LTEpdGhyb3cgbmV3IFR5cGVFcnJvcigiRXhwZWN0ZWQgVEQgb3IgVEggZWxlbWVudCIpO2lmKCJjb2x1bW5oZWFkZXIiPT09YylyZXR1cm4iY29sIjtpZigicm93aGVhZGVyIj09PWMpcmV0dXJuInJvdyI7aWYoImNvbCI9PT1ifHwicm93Ij09PWIpcmV0dXJuIGI7aWYoIlRIIiE9PWEubm9kZU5hbWUudG9VcHBlckNhc2UoKSlyZXR1cm4hMTt2YXIgZD13LnRvR3JpZCh2LmZpbmRVcChhLCJ0YWJsZSIpKSxlPXcuZ2V0Q2VsbFBvc2l0aW9uKGEpLGY9ZFtlLnldLnJlZHVjZShmdW5jdGlvbihhLGIpe3JldHVybiBhJiYiVEgiPT09Yi5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpfSwhMCk7aWYoZilyZXR1cm4iY29sIjt2YXIgZz1kLm1hcChmdW5jdGlvbihhKXtyZXR1cm4gYVtlLnhdfSkucmVkdWNlKGZ1bmN0aW9uKGEsYil7cmV0dXJuIGEmJiJUSCI9PT1iLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCl9LCEwKTtyZXR1cm4gZz8icm93IjoiYXV0byJ9LHcuaXNDb2x1bW5IZWFkZXI9ZnVuY3Rpb24oYSl7cmV0dXJuWyJjb2wiLCJhdXRvIl0uaW5kZXhPZih3LmdldFNjb3BlKGEpKSE9PS0xfSx3LmlzRGF0YUNlbGw9ZnVuY3Rpb24oYSl7cmV0dXJuISghYS5jaGlsZHJlbi5sZW5ndGgmJiFhLnRleHRDb250ZW50LnRyaW0oKSkmJiJURCI9PT1hLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCl9LHcuaXNEYXRhVGFibGU9ZnVuY3Rpb24oYSl7dmFyIGI9YS5nZXRBdHRyaWJ1dGUoInJvbGUiKTtpZigoInByZXNlbnRhdGlvbiI9PT1ifHwibm9uZSI9PT1iKSYmIXYuaXNGb2N1c2FibGUoYSkpcmV0dXJuITE7aWYoInRydWUiPT09YS5nZXRBdHRyaWJ1dGUoImNvbnRlbnRlZGl0YWJsZSIpfHx2LmZpbmRVcChhLCdbY29udGVudGVkaXRhYmxlPSJ0cnVlIl0nKSlyZXR1cm4hMDtpZigiZ3JpZCI9PT1ifHwidHJlZWdyaWQiPT09Ynx8InRhYmxlIj09PWIpcmV0dXJuITA7aWYoImxhbmRtYXJrIj09PWNvbW1vbnMuYXJpYS5nZXRSb2xlVHlwZShiKSlyZXR1cm4hMDtpZigiMCI9PT1hLmdldEF0dHJpYnV0ZSgiZGF0YXRhYmxlIikpcmV0dXJuITE7aWYoYS5nZXRBdHRyaWJ1dGUoInN1bW1hcnkiKSlyZXR1cm4hMDtpZihhLnRIZWFkfHxhLnRGb290fHxhLmNhcHRpb24pcmV0dXJuITA7Zm9yKHZhciBjPTAsZD1hLmNoaWxkcmVuLmxlbmd0aDtjPGQ7YysrKWlmKCJDT0xHUk9VUCI9PT1hLmNoaWxkcmVuW2NdLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCkpcmV0dXJuITA7Zm9yKHZhciBlLGYsZz0wLGg9YS5yb3dzLmxlbmd0aCxpPSExLGo9MDtqPGg7aisrKXtlPWEucm93c1tqXTtmb3IodmFyIGs9MCxsPWUuY2VsbHMubGVuZ3RoO2s8bDtrKyspe2lmKGY9ZS5jZWxsc1trXSwiVEgiPT09Zi5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpKXJldHVybiEwO2lmKGl8fGYub2Zmc2V0V2lkdGg9PT1mLmNsaWVudFdpZHRoJiZmLm9mZnNldEhlaWdodD09PWYuY2xpZW50SGVpZ2h0fHwoaT0hMCksZi5nZXRBdHRyaWJ1dGUoInNjb3BlIil8fGYuZ2V0QXR0cmlidXRlKCJoZWFkZXJzIil8fGYuZ2V0QXR0cmlidXRlKCJhYmJyIikpcmV0dXJuITA7aWYoWyJjb2x1bW5oZWFkZXIiLCJyb3doZWFkZXIiXS5pbmRleE9mKGYuZ2V0QXR0cmlidXRlKCJyb2xlIikpIT09LTEpcmV0dXJuITA7aWYoMT09PWYuY2hpbGRyZW4ubGVuZ3RoJiYiQUJCUiI9PT1mLmNoaWxkcmVuWzBdLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCkpcmV0dXJuITA7ZysrfX1pZihhLmdldEVsZW1lbnRzQnlUYWdOYW1lKCJ0YWJsZSIpLmxlbmd0aClyZXR1cm4hMTtpZihoPDIpcmV0dXJuITE7dmFyIG09YS5yb3dzW01hdGguY2VpbChoLzIpXTtpZigxPT09bS5jZWxscy5sZW5ndGgmJjE9PT1tLmNlbGxzWzBdLmNvbFNwYW4pcmV0dXJuITE7aWYobS5jZWxscy5sZW5ndGg+PTUpcmV0dXJuITA7aWYoaSlyZXR1cm4hMDt2YXIgbixvO2ZvcihqPTA7ajxoO2orKyl7aWYoZT1hLnJvd3Nbal0sbiYmbiE9PXdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGUpLmdldFByb3BlcnR5VmFsdWUoImJhY2tncm91bmQtY29sb3IiKSlyZXR1cm4hMDtpZihuPXdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGUpLmdldFByb3BlcnR5VmFsdWUoImJhY2tncm91bmQtY29sb3IiKSxvJiZvIT09d2luZG93LmdldENvbXB1dGVkU3R5bGUoZSkuZ2V0UHJvcGVydHlWYWx1ZSgiYmFja2dyb3VuZC1pbWFnZSIpKXJldHVybiEwO289d2luZG93LmdldENvbXB1dGVkU3R5bGUoZSkuZ2V0UHJvcGVydHlWYWx1ZSgiYmFja2dyb3VuZC1pbWFnZSIpfXJldHVybiBoPj0yMHx8ISh2LmdldEVsZW1lbnRDb29yZGluYXRlcyhhKS53aWR0aD4uOTUqdi5nZXRWaWV3cG9ydFNpemUod2luZG93KS53aWR0aCkmJighKGc8MTApJiYhYS5xdWVyeVNlbGVjdG9yKCJvYmplY3QsIGVtYmVkLCBpZnJhbWUsIGFwcGxldCIpKX0sdy5pc0hlYWRlcj1mdW5jdGlvbihhKXtyZXR1cm4hKCF3LmlzQ29sdW1uSGVhZGVyKGEpJiYhdy5pc1Jvd0hlYWRlcihhKSl8fCEhYS5pZCYmISFkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdbaGVhZGVyc349IicrYXhlLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEuaWQpKyciXScpfSx3LmlzUm93SGVhZGVyPWZ1bmN0aW9uKGEpe3JldHVyblsicm93IiwiYXV0byJdLmluZGV4T2Yody5nZXRTY29wZShhKSkhPT0tMX0sdy50b0dyaWQ9ZnVuY3Rpb24oYSl7Zm9yKHZhciBiPVtdLGM9YS5yb3dzLGQ9MCxlPWMubGVuZ3RoO2Q8ZTtkKyspe3ZhciBmPWNbZF0uY2VsbHM7YltkXT1iW2RdfHxbXTtmb3IodmFyIGc9MCxoPTAsaT1mLmxlbmd0aDtoPGk7aCsrKWZvcih2YXIgaj0wO2o8ZltoXS5jb2xTcGFuO2orKyl7Zm9yKHZhciBrPTA7azxmW2hdLnJvd1NwYW47aysrKXtmb3IoYltkK2tdPWJbZCtrXXx8W107YltkK2tdW2ddOylnKys7YltkK2tdW2ddPWZbaF19ZysrfX1yZXR1cm4gYn0sdy50b0FycmF5PXcudG9HcmlkLGZ1bmN0aW9uKGEpe3ZhciBiPWZ1bmN0aW9uIGMoYSxiLGQsZSl7dmFyIGYsZz1kW2IueV0/ZFtiLnldW2IueF06dm9pZCAwO3JldHVybiBnPyJmdW5jdGlvbiI9PXR5cGVvZiBlJiYoZj1lKGcsYixkKSxmPT09ITApP1tnXTooZj1jKGEse3g6Yi54K2EueCx5OmIueSthLnl9LGQsZSksZi51bnNoaWZ0KGcpLGYpOltdfTthLnRyYXZlcnNlPWZ1bmN0aW9uKGEsYyxkLGUpe2lmKEFycmF5LmlzQXJyYXkoYykmJihlPWQsZD1jLGM9e3g6MCx5OjB9KSwic3RyaW5nIj09dHlwZW9mIGEpc3dpdGNoKGEpe2Nhc2UibGVmdCI6YT17eDotMSx5OjB9O2JyZWFrO2Nhc2UidXAiOmE9e3g6MCx5Oi0xfTticmVhaztjYXNlInJpZ2h0IjphPXt4OjEseTowfTticmVhaztjYXNlImRvd24iOmE9e3g6MCx5OjF9fXJldHVybiBiKGEse3g6Yy54K2EueCx5OmMueSthLnl9LGQsZSl9fSh3KTt2YXIgQT17c3VibWl0OiJTdWJtaXQiLHJlc2V0OiJSZXNldCJ9LEI9WyJ0ZXh0Iiwic2VhcmNoIiwidGVsIiwidXJsIiwiZW1haWwiLCJkYXRlIiwidGltZSIsIm51bWJlciIsInJhbmdlIiwiY29sb3IiXSxDPVsiQSIsIkVNIiwiU1RST05HIiwiU01BTEwiLCJNQVJLIiwiQUJCUiIsIkRGTiIsIkkiLCJCIiwiUyIsIlUiLCJDT0RFIiwiVkFSIiwiU0FNUCIsIktCRCIsIlNVUCIsIlNVQiIsIlEiLCJDSVRFIiwiU1BBTiIsIkJETyIsIkJESSIsIkJSIiwiV0JSIiwiSU5TIiwiREVMIiwiSU1HIiwiRU1CRUQiLCJPQkpFQ1QiLCJJRlJBTUUiLCJNQVAiLCJBUkVBIiwiU0NSSVBUIiwiTk9TQ1JJUFQiLCJSVUJZIiwiVklERU8iLCJBVURJTyIsIklOUFVUIiwiVEVYVEFSRUEiLCJTRUxFQ1QiLCJCVVRUT04iLCJMQUJFTCIsIk9VVFBVVCIsIkRBVEFMSVNUIiwiS0VZR0VOIiwiUFJPR1JFU1MiLCJDT01NQU5EIiwiQ0FOVkFTIiwiVElNRSIsIk1FVEVSIl07cmV0dXJuIHguYWNjZXNzaWJsZVRleHQ9ZnVuY3Rpb24oYSxiKXtmdW5jdGlvbiBjKGEsYixjKXsKZm9yKHZhciBkLGU9YS5jaGlsZE5vZGVzLGc9IiIsaD0wO2g8ZS5sZW5ndGg7aCsrKWQ9ZVtoXSwzPT09ZC5ub2RlVHlwZT9nKz1kLnRleHRDb250ZW50OjE9PT1kLm5vZGVUeXBlJiYoQy5pbmRleE9mKGQubm9kZU5hbWUudG9VcHBlckNhc2UoKSk9PT0tMSYmKGcrPSIgIiksZys9ZihlW2hdLGIsYykpO3JldHVybiBnfWZ1bmN0aW9uIGQoYSxiLGQpe3ZhciBlPSIiLGc9YS5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpO2lmKGwoYSkmJihlPWMoYSwhMSwhMSl8fCIiLHIoZSkpKXJldHVybiBlO2lmKCJGSUdVUkUiPT09ZyYmKGU9byhhLCJmaWdjYXB0aW9uIikscihlKSkpcmV0dXJuIGU7aWYoIlRBQkxFIj09PWcpe2lmKGU9byhhLCJjYXB0aW9uIikscihlKSlyZXR1cm4gZTtpZihlPWEuZ2V0QXR0cmlidXRlKCJ0aXRsZSIpfHxhLmdldEF0dHJpYnV0ZSgic3VtbWFyeSIpfHwiIixyKGUpKXJldHVybiBlfWlmKHEoYSkpcmV0dXJuIGEuZ2V0QXR0cmlidXRlKCJhbHQiKXx8IiI7aWYoayhhKSYmIWQpe2lmKGooYSkpcmV0dXJuIGEudmFsdWV8fGEudGl0bGV8fEFbYS50eXBlXXx8IiI7dmFyIGg9aShhKTtpZihoKXJldHVybiBmKGgsYiwhMCl9cmV0dXJuIiJ9ZnVuY3Rpb24gZShhLGIsYyl7cmV0dXJuIWImJmEuaGFzQXR0cmlidXRlKCJhcmlhLWxhYmVsbGVkYnkiKT94LnNhbml0aXplKHYuaWRyZWZzKGEsImFyaWEtbGFiZWxsZWRieSIpLm1hcChmdW5jdGlvbihiKXtyZXR1cm4gYT09PWImJmcucG9wKCksZihiLCEwLGEhPT1iKX0pLmpvaW4oIiAiKSk6YyYmcChhKXx8IWEuaGFzQXR0cmlidXRlKCJhcmlhLWxhYmVsIik/IiI6eC5zYW5pdGl6ZShhLmdldEF0dHJpYnV0ZSgiYXJpYS1sYWJlbCIpKX12YXIgZixnPVtdO3JldHVybiBmPWZ1bmN0aW9uKGEsYixmKXsidXNlIHN0cmljdCI7dmFyIGg7aWYobnVsbD09PWF8fGcuaW5kZXhPZihhKSE9PS0xKXJldHVybiIiO2lmKCFiJiYhdi5pc1Zpc2libGUoYSwhMCkpcmV0dXJuIiI7Zy5wdXNoKGEpO3ZhciBpPWEuZ2V0QXR0cmlidXRlKCJyb2xlIik7cmV0dXJuIGg9ZShhLGIsZikscihoKT9oOihoPWQoYSxiLGYpLHIoaCk/aDpmJiYoaD1uKGEpLHIoaCkpP2g6bShhKXx8aSYmcy5nZXRSb2xlc1dpdGhOYW1lRnJvbUNvbnRlbnRzKCkuaW5kZXhPZihpKT09PS0xfHwoaD1jKGEsYixmKSwhcihoKSk/YS5oYXNBdHRyaWJ1dGUoInRpdGxlIik/YS5nZXRBdHRyaWJ1dGUoInRpdGxlIik6IiI6aCl9LHguc2FuaXRpemUoZihhLGIpKX0seC5sYWJlbD1mdW5jdGlvbihhKXt2YXIgYixjO3JldHVybihjPXMubGFiZWwoYSkpP2M6YS5pZCYmKGI9ZG9jdW1lbnQucXVlcnlTZWxlY3RvcignbGFiZWxbZm9yPSInK2F4ZS51dGlscy5lc2NhcGVTZWxlY3RvcihhLmlkKSsnIl0nKSxjPWImJngudmlzaWJsZShiLCEwKSk/YzooYj12LmZpbmRVcChhLCJsYWJlbCIpLGM9YiYmeC52aXNpYmxlKGIsITApLGM/YzpudWxsKX0seC5zYW5pdGl6ZT1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7cmV0dXJuIGEucmVwbGFjZSgvXHJcbi9nLCJcbiIpLnJlcGxhY2UoL1x1MDBBMC9nLCIgIikucmVwbGFjZSgvW1xzXXsyLH0vZywiICIpLnRyaW0oKX0seC52aXNpYmxlPWZ1bmN0aW9uKGEsYixjKXsidXNlIHN0cmljdCI7dmFyIGQsZSxmLGc9YS5jaGlsZE5vZGVzLGg9Zy5sZW5ndGgsaT0iIjtmb3IoZD0wO2Q8aDtkKyspZT1nW2RdLDM9PT1lLm5vZGVUeXBlPyhmPWUubm9kZVZhbHVlLGYmJnYuaXNWaXNpYmxlKGEsYikmJihpKz1lLm5vZGVWYWx1ZSkpOmN8fChpKz14LnZpc2libGUoZSxiKSk7cmV0dXJuIHguc2FuaXRpemUoaSl9LGF4ZS51dGlscy50b0FycmF5PWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtyZXR1cm4gQXJyYXkucHJvdG90eXBlLnNsaWNlLmNhbGwoYSl9LGF4ZS51dGlscy50b2tlbkxpc3Q9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3JldHVybiBhLnRyaW0oKS5yZXBsYWNlKC9cc3syLH0vZywiICIpLnNwbGl0KCIgIil9LGNvbW1vbnN9KCl9KX0oIm9iamVjdCI9PXR5cGVvZiB3aW5kb3c/d2luZG93OnRoaXMpOw==","base64");
 
-const axe=Buffer("LyohIGFYZSB2Mi4xLjcKICogQ29weXJpZ2h0IChjKSAyMDE2IERlcXVlIFN5c3RlbXMsIEluYy4KICoKICogWW91ciB1c2Ugb2YgdGhpcyBTb3VyY2UgQ29kZSBGb3JtIGlzIHN1YmplY3QgdG8gdGhlIHRlcm1zIG9mIHRoZSBNb3ppbGxhIFB1YmxpYwogKiBMaWNlbnNlLCB2LiAyLjAuIElmIGEgY29weSBvZiB0aGUgTVBMIHdhcyBub3QgZGlzdHJpYnV0ZWQgd2l0aCB0aGlzCiAqIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uCiAqCiAqIFRoaXMgZW50aXJlIGNvcHlyaWdodCBub3RpY2UgbXVzdCBhcHBlYXIgaW4gZXZlcnkgY29weSBvZiB0aGlzIGZpbGUgeW91CiAqIGRpc3RyaWJ1dGUgb3IgaW4gYW55IGZpbGUgdGhhdCBjb250YWlucyBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGlzIHNvdXJjZQogKiBjb2RlLgogKi8KIWZ1bmN0aW9uIGEod2luZG93KXtmdW5jdGlvbiBiKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYjtyZXR1cm4gYT8oYj1heGUudXRpbHMuY2xvbmUoYSksYi5jb21tb25zPWEuY29tbW9ucyk6Yj17fSxiLnJlcG9ydGVyPWIucmVwb3J0ZXJ8fG51bGwsYi5ydWxlcz1iLnJ1bGVzfHxbXSxiLmNoZWNrcz1iLmNoZWNrc3x8W10sYi5kYXRhPU9iamVjdC5hc3NpZ24oe2NoZWNrczp7fSxydWxlczp7fX0sYi5kYXRhKSxifWZ1bmN0aW9uIGMoYSxiLGMpeyJ1c2Ugc3RyaWN0Ijt2YXIgZCxlO2ZvcihkPTAsZT1hLmxlbmd0aDtkPGU7ZCsrKWJbY10oYVtkXSl9ZnVuY3Rpb24gZChhKXt0aGlzLmJyYW5kPSJheGUiLHRoaXMuYXBwbGljYXRpb249ImF4ZUFQSSIsdGhpcy50YWdFeGNsdWRlPVsiZXhwZXJpbWVudGFsIl0sdGhpcy5kZWZhdWx0Q29uZmlnPWEsdGhpcy5faW5pdCgpfWZ1bmN0aW9uIGUoYSl7InVzZSBzdHJpY3QiO3RoaXMuaWQ9YS5pZCx0aGlzLmRhdGE9bnVsbCx0aGlzLnJlbGF0ZWROb2Rlcz1bXSx0aGlzLnJlc3VsdD1udWxsfWZ1bmN0aW9uIGYoYSl7InVzZSBzdHJpY3QiO3JldHVybiJzdHJpbmciPT10eXBlb2YgYT9uZXcgRnVuY3Rpb24oInJldHVybiAiK2ErIjsiKSgpOmF9ZnVuY3Rpb24gZyhhKXthJiYodGhpcy5pZD1hLmlkLHRoaXMuY29uZmlndXJlKGEpKX1mdW5jdGlvbiBoKGEsYil7InVzZSBzdHJpY3QiO2lmKCFheGUudXRpbHMuaXNIaWRkZW4oYikpe3ZhciBjPWF4ZS51dGlscy5maW5kQnkoYSwibm9kZSIsYik7Y3x8YS5wdXNoKHtub2RlOmIsaW5jbHVkZTpbXSxleGNsdWRlOltdfSl9fWZ1bmN0aW9uIGkoYSxiLGMpeyJ1c2Ugc3RyaWN0IjthLmZyYW1lcz1hLmZyYW1lc3x8W107dmFyIGQsZSxmPWRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoYy5zaGlmdCgpKTthOmZvcih2YXIgZz0wLGg9Zi5sZW5ndGg7ZzxoO2crKyl7ZT1mW2ddO2Zvcih2YXIgaT0wLGo9YS5mcmFtZXMubGVuZ3RoO2k8ajtpKyspaWYoYS5mcmFtZXNbaV0ubm9kZT09PWUpe2EuZnJhbWVzW2ldW2JdLnB1c2goYyk7YnJlYWsgYX1kPXtub2RlOmUsaW5jbHVkZTpbXSxleGNsdWRlOltdfSxjJiZkW2JdLnB1c2goYyksYS5mcmFtZXMucHVzaChkKX19ZnVuY3Rpb24gaihhKXsidXNlIHN0cmljdCI7aWYoYSYmIm9iamVjdCI9PT0oInVuZGVmaW5lZCI9PXR5cGVvZiBhPyJ1bmRlZmluZWQiOlgoYSkpfHxhIGluc3RhbmNlb2YgTm9kZUxpc3Qpe2lmKGEgaW5zdGFuY2VvZiBOb2RlKXJldHVybntpbmNsdWRlOlthXSxleGNsdWRlOltdfTtpZihhLmhhc093blByb3BlcnR5KCJpbmNsdWRlIil8fGEuaGFzT3duUHJvcGVydHkoImV4Y2x1ZGUiKSlyZXR1cm57aW5jbHVkZTphLmluY2x1ZGV8fFtkb2N1bWVudF0sZXhjbHVkZTphLmV4Y2x1ZGV8fFtdfTtpZihhLmxlbmd0aD09PSthLmxlbmd0aClyZXR1cm57aW5jbHVkZTphLGV4Y2x1ZGU6W119fXJldHVybiJzdHJpbmciPT10eXBlb2YgYT97aW5jbHVkZTpbYV0sZXhjbHVkZTpbXX06e2luY2x1ZGU6W2RvY3VtZW50XSxleGNsdWRlOltdfX1mdW5jdGlvbiBrKGEsYil7InVzZSBzdHJpY3QiO2Zvcih2YXIgYyxkPVtdLGU9MCxmPWFbYl0ubGVuZ3RoO2U8ZjtlKyspe2lmKGM9YVtiXVtlXSwic3RyaW5nIj09dHlwZW9mIGMpe2Q9ZC5jb25jYXQoYXhlLnV0aWxzLnRvQXJyYXkoZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbChjKSkpO2JyZWFrfSFjfHwhYy5sZW5ndGh8fGMgaW5zdGFuY2VvZiBOb2RlP2QucHVzaChjKTpjLmxlbmd0aD4xP2koYSxiLGMpOmQ9ZC5jb25jYXQoYXhlLnV0aWxzLnRvQXJyYXkoZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbChjWzBdKSkpfXJldHVybiBkLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4gYX0pfWZ1bmN0aW9uIGwoYSl7InVzZSBzdHJpY3QiO2lmKDA9PT1hLmluY2x1ZGUubGVuZ3RoKXtpZigwPT09YS5mcmFtZXMubGVuZ3RoKXt2YXIgYj1heGUudXRpbHMucmVzcG9uZGFibGUuaXNJbkZyYW1lKCk/ImZyYW1lIjoicGFnZSI7cmV0dXJuIG5ldyBFcnJvcigiTm8gZWxlbWVudHMgZm91bmQgZm9yIGluY2x1ZGUgaW4gIitiKyIgQ29udGV4dCIpfWEuZnJhbWVzLmZvckVhY2goZnVuY3Rpb24oYSxiKXtpZigwPT09YS5pbmNsdWRlLmxlbmd0aClyZXR1cm4gbmV3IEVycm9yKCJObyBlbGVtZW50cyBmb3VuZCBmb3IgaW5jbHVkZSBpbiBDb250ZXh0IG9mIGZyYW1lICIrYil9KX19ZnVuY3Rpb24gbShhKXsidXNlIHN0cmljdCI7dmFyIGI9dGhpczt0aGlzLmZyYW1lcz1bXSx0aGlzLmluaXRpYXRvcj0hYXx8ImJvb2xlYW4iIT10eXBlb2YgYS5pbml0aWF0b3J8fGEuaW5pdGlhdG9yLHRoaXMucGFnZT0hMSxhPWooYSksdGhpcy5leGNsdWRlPWEuZXhjbHVkZSx0aGlzLmluY2x1ZGU9YS5pbmNsdWRlLHRoaXMuaW5jbHVkZT1rKHRoaXMsImluY2x1ZGUiKSx0aGlzLmV4Y2x1ZGU9ayh0aGlzLCJleGNsdWRlIiksYXhlLnV0aWxzLnNlbGVjdCgiZnJhbWUsIGlmcmFtZSIsdGhpcykuZm9yRWFjaChmdW5jdGlvbihhKXtWKGEsYikmJmgoYi5mcmFtZXMsYSl9KSwxPT09dGhpcy5pbmNsdWRlLmxlbmd0aCYmdGhpcy5pbmNsdWRlWzBdPT09ZG9jdW1lbnQmJih0aGlzLnBhZ2U9ITApO3ZhciBjPWwodGhpcyk7aWYoYyBpbnN0YW5jZW9mIEVycm9yKXRocm93IGN9ZnVuY3Rpb24gbihhKXsidXNlIHN0cmljdCI7dGhpcy5pZD1hLmlkLHRoaXMucmVzdWx0PWF4ZS5jb25zdGFudHMuTkEsdGhpcy5wYWdlTGV2ZWw9YS5wYWdlTGV2ZWwsdGhpcy5pbXBhY3Q9bnVsbCx0aGlzLm5vZGVzPVtdfWZ1bmN0aW9uIG8oYSxiKXsidXNlIHN0cmljdCI7dGhpcy5fYXVkaXQ9Yix0aGlzLmlkPWEuaWQsdGhpcy5zZWxlY3Rvcj1hLnNlbGVjdG9yfHwiKiIsdGhpcy5leGNsdWRlSGlkZGVuPSJib29sZWFuIiE9dHlwZW9mIGEuZXhjbHVkZUhpZGRlbnx8YS5leGNsdWRlSGlkZGVuLHRoaXMuZW5hYmxlZD0iYm9vbGVhbiIhPXR5cGVvZiBhLmVuYWJsZWR8fGEuZW5hYmxlZCx0aGlzLnBhZ2VMZXZlbD0iYm9vbGVhbiI9PXR5cGVvZiBhLnBhZ2VMZXZlbCYmYS5wYWdlTGV2ZWwsdGhpcy5hbnk9YS5hbnl8fFtdLHRoaXMuYWxsPWEuYWxsfHxbXSx0aGlzLm5vbmU9YS5ub25lfHxbXSx0aGlzLnRhZ3M9YS50YWdzfHxbXSxhLm1hdGNoZXMmJih0aGlzLm1hdGNoZXM9ZihhLm1hdGNoZXMpKX1mdW5jdGlvbiBwKGEpeyJ1c2Ugc3RyaWN0IjtyZXR1cm4gYXhlLnV0aWxzLmdldEFsbENoZWNrcyhhKS5tYXAoZnVuY3Rpb24oYil7dmFyIGM9YS5fYXVkaXQuY2hlY2tzW2IuaWR8fGJdO3JldHVybiBjJiYiZnVuY3Rpb24iPT10eXBlb2YgYy5hZnRlcj9jOm51bGx9KS5maWx0ZXIoQm9vbGVhbil9ZnVuY3Rpb24gcShhLGIpeyJ1c2Ugc3RyaWN0Ijt2YXIgYz1bXTtyZXR1cm4gYS5mb3JFYWNoKGZ1bmN0aW9uKGEpe3ZhciBkPWF4ZS51dGlscy5nZXRBbGxDaGVja3MoYSk7ZC5mb3JFYWNoKGZ1bmN0aW9uKGEpe2EuaWQ9PT1iJiZjLnB1c2goYSl9KX0pLGN9ZnVuY3Rpb24gcihhKXsidXNlIHN0cmljdCI7cmV0dXJuIGEuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBhLmZpbHRlcmVkIT09ITB9KX1mdW5jdGlvbiBzKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1bImFueSIsImFsbCIsIm5vbmUiXSxjPWEubm9kZXMuZmlsdGVyKGZ1bmN0aW9uKGEpe3ZhciBjPTA7cmV0dXJuIGIuZm9yRWFjaChmdW5jdGlvbihiKXthW2JdPXIoYVtiXSksYys9YVtiXS5sZW5ndGh9KSxjPjB9KTtyZXR1cm4gYS5wYWdlTGV2ZWwmJmMubGVuZ3RoJiYoYz1bYy5yZWR1Y2UoZnVuY3Rpb24oYSxjKXtpZihhKXJldHVybiBiLmZvckVhY2goZnVuY3Rpb24oYil7YVtiXS5wdXNoLmFwcGx5KGFbYl0sY1tiXSl9KSxhfSldKSxjfWZ1bmN0aW9uIHQoYSxiKXsidXNlIHN0cmljdCI7aWYoIWF4ZS5fYXVkaXQpdGhyb3cgbmV3IEVycm9yKCJObyBhdWRpdCBjb25maWd1cmVkIik7dmFyIGM9YXhlLnV0aWxzLnF1ZXVlKCksZD1bXTtPYmplY3Qua2V5cyhheGUucGx1Z2lucykuZm9yRWFjaChmdW5jdGlvbihhKXtjLmRlZmVyKGZ1bmN0aW9uKGIpe3ZhciBjPWZ1bmN0aW9uKGEpe2QucHVzaChhKSxiKCl9O3RyeXtheGUucGx1Z2luc1thXS5jbGVhbnVwKGIsYyl9Y2F0Y2goZSl7YyhlKX19KX0pLGF4ZS51dGlscy50b0FycmF5KGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoImZyYW1lLCBpZnJhbWUiKSkuZm9yRWFjaChmdW5jdGlvbihhKXtjLmRlZmVyKGZ1bmN0aW9uKGIsYyl7cmV0dXJuIGF4ZS51dGlscy5zZW5kQ29tbWFuZFRvRnJhbWUoYSx7Y29tbWFuZDoiY2xlYW51cC1wbHVnaW4ifSxiLGMpfSl9KSxjLnRoZW4oZnVuY3Rpb24oYyl7MD09PWQubGVuZ3RoP2EoYyk6YihkKX0pWyJjYXRjaCJdKGIpfWZ1bmN0aW9uIHUoYSl7InVzZSBzdHJpY3QiO3ZhciBiO2lmKGI9YXhlLl9hdWRpdCwhYil0aHJvdyBuZXcgRXJyb3IoIk5vIGF1ZGl0IGNvbmZpZ3VyZWQiKTthLnJlcG9ydGVyJiYoImZ1bmN0aW9uIj09dHlwZW9mIGEucmVwb3J0ZXJ8fCRbYS5yZXBvcnRlcl0pJiYoYi5yZXBvcnRlcj1hLnJlcG9ydGVyKSxhLmNoZWNrcyYmYS5jaGVja3MuZm9yRWFjaChmdW5jdGlvbihhKXtiLmFkZENoZWNrKGEpfSksYS5ydWxlcyYmYS5ydWxlcy5mb3JFYWNoKGZ1bmN0aW9uKGEpe2IuYWRkUnVsZShhKX0pLCJ1bmRlZmluZWQiIT10eXBlb2YgYS5icmFuZGluZz9iLnNldEJyYW5kaW5nKGEuYnJhbmRpbmcpOmIuX2NvbnN0cnVjdEhlbHBVcmxzKCksYS50YWdFeGNsdWRlJiYoYi50YWdFeGNsdWRlPWEudGFnRXhjbHVkZSl9ZnVuY3Rpb24gdihhLGIsYyl7InVzZSBzdHJpY3QiO3ZhciBkPWMsZT1mdW5jdGlvbihhKXthIGluc3RhbmNlb2YgRXJyb3I9PSExJiYoYT1uZXcgRXJyb3IoYSkpLGMoYSl9LGY9YSYmYS5jb250ZXh0fHx7fTtmLmluY2x1ZGUmJiFmLmluY2x1ZGUubGVuZ3RoJiYoZi5pbmNsdWRlPVtkb2N1bWVudF0pO3ZhciBnPWEmJmEub3B0aW9uc3x8e307c3dpdGNoKGEuY29tbWFuZCl7Y2FzZSJydWxlcyI6cmV0dXJuIHkoZixnLGQsZSk7Y2FzZSJjbGVhbnVwLXBsdWdpbiI6cmV0dXJuIHQoZCxlKTtkZWZhdWx0OmlmKGF4ZS5fYXVkaXQmJmF4ZS5fYXVkaXQuY29tbWFuZHMmJmF4ZS5fYXVkaXQuY29tbWFuZHNbYS5jb21tYW5kXSlyZXR1cm4gYXhlLl9hdWRpdC5jb21tYW5kc1thLmNvbW1hbmRdKGEsYyl9fWZ1bmN0aW9uIHcoYSl7InVzZSBzdHJpY3QiO3RoaXMuX3J1bj1hLnJ1bix0aGlzLl9jb2xsZWN0PWEuY29sbGVjdCx0aGlzLl9yZWdpc3RyeT17fSxhLmNvbW1hbmRzLmZvckVhY2goZnVuY3Rpb24oYSl7YXhlLl9hdWRpdC5yZWdpc3RlckNvbW1hbmQoYSl9KX1mdW5jdGlvbiB4KCl7InVzZSBzdHJpY3QiO3ZhciBhPWF4ZS5fYXVkaXQ7aWYoIWEpdGhyb3cgbmV3IEVycm9yKCJObyBhdWRpdCBjb25maWd1cmVkIik7YS5yZXNldFJ1bGVzQW5kQ2hlY2tzKCl9ZnVuY3Rpb24geShhLGIsYyxkKXsidXNlIHN0cmljdCI7YT1uZXcgbShhKTt2YXIgZT1heGUudXRpbHMucXVldWUoKSxmPWF4ZS5fYXVkaXQ7YS5mcmFtZXMubGVuZ3RoJiZlLmRlZmVyKGZ1bmN0aW9uKGMsZCl7YXhlLnV0aWxzLmNvbGxlY3RSZXN1bHRzRnJvbUZyYW1lcyhhLGIsInJ1bGVzIixudWxsLGMsZCl9KSxlLmRlZmVyKGZ1bmN0aW9uKGMsZCl7Zi5ydW4oYSxiLGMsZCl9KSxlLnRoZW4oZnVuY3Rpb24oZSl7dHJ5e3ZhciBnPWF4ZS51dGlscy5tZXJnZVJlc3VsdHMoZS5tYXAoZnVuY3Rpb24oYSl7cmV0dXJue3Jlc3VsdHM6YX19KSk7YS5pbml0aWF0b3ImJihnPWYuYWZ0ZXIoZyxiKSxnLmZvckVhY2goYXhlLnV0aWxzLnB1Ymxpc2hNZXRhRGF0YSksZz1nLm1hcChheGUudXRpbHMuZmluYWxpemVSdWxlUmVzdWx0KSk7dHJ5e2MoZyl9Y2F0Y2goaCl7YXhlLmxvZyhoKX19Y2F0Y2goaCl7ZChoKX19KVsiY2F0Y2giXShkKX1mdW5jdGlvbiB6KGEpeyJ1c2Ugc3RyaWN0Ijtzd2l0Y2goITApe2Nhc2Uic3RyaW5nIj09dHlwZW9mIGE6Y2FzZSBBcnJheS5pc0FycmF5KGEpOmNhc2UgTm9kZSYmYSBpbnN0YW5jZW9mIE5vZGU6Y2FzZSBOb2RlTGlzdCYmYSBpbnN0YW5jZW9mIE5vZGVMaXN0OnJldHVybiEwO2Nhc2Uib2JqZWN0IiE9PSgidW5kZWZpbmVkIj09dHlwZW9mIGE/InVuZGVmaW5lZCI6WChhKSk6cmV0dXJuITE7Y2FzZSB2b2lkIDAhPT1hLmluY2x1ZGU6Y2FzZSB2b2lkIDAhPT1hLmV4Y2x1ZGU6Y2FzZSJudW1iZXIiPT10eXBlb2YgYS5sZW5ndGg6cmV0dXJuITA7ZGVmYXVsdDpyZXR1cm4hMX19ZnVuY3Rpb24gQShhLGIsYyl7InVzZSBzdHJpY3QiO3ZhciBkPW5ldyBUeXBlRXJyb3IoImF4ZS5ydW4gYXJndW1lbnRzIGFyZSBpbnZhbGlkIik7aWYoIXooYSkpe2lmKHZvaWQgMCE9PWMpdGhyb3cgZDtjPWIsYj1hLGE9ZG9jdW1lbnR9aWYoIm9iamVjdCIhPT0oInVuZGVmaW5lZCI9PXR5cGVvZiBiPyJ1bmRlZmluZWQiOlgoYikpKXtpZih2b2lkIDAhPT1jKXRocm93IGQ7Yz1iLGI9e319aWYoImZ1bmN0aW9uIiE9dHlwZW9mIGMmJnZvaWQgMCE9PWMpdGhyb3cgZDtyZXR1cm57Y29udGV4dDphLG9wdGlvbnM6YixjYWxsYmFjazpjfHxffX1mdW5jdGlvbiBCKGEsYil7InVzZSBzdHJpY3QiO1siYW55IiwiYWxsIiwibm9uZSJdLmZvckVhY2goZnVuY3Rpb24oYyl7QXJyYXkuaXNBcnJheShhW2NdKSYmYVtjXS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuIEFycmF5LmlzQXJyYXkoYS5yZWxhdGVkTm9kZXMpfSkuZm9yRWFjaChmdW5jdGlvbihhKXthLnJlbGF0ZWROb2Rlcz1hLnJlbGF0ZWROb2Rlcy5tYXAoZnVuY3Rpb24oYSl7dmFyIGM9e2h0bWw6YS5zb3VyY2UsdGFyZ2V0OmEuc2VsZWN0b3J9O3JldHVybiBiJiYoYy54cGF0aD1hLnhwYXRoKSxjfSl9KX0pfWZ1bmN0aW9uIEMoYSxiKXtyZXR1cm4gY2EucmVkdWNlKGZ1bmN0aW9uKGMsZCl7cmV0dXJuIGNbZF09KGFbZF18fFtdKS5tYXAoZnVuY3Rpb24oYSl7cmV0dXJuIGIoYSxkKX0pLGN9LHt9KX1mdW5jdGlvbiBEKGEsYixjKXt2YXIgZD1PYmplY3QuYXNzaWduKHt9LGIpO2Qubm9kZXM9KGRbY118fFtdKS5jb25jYXQoKSxheGUuY29uc3RhbnRzLnJlc3VsdEdyb3Vwcy5mb3JFYWNoKGZ1bmN0aW9uKGEpe2RlbGV0ZSBkW2FdfSksYVtjXS5wdXNoKGQpfWZ1bmN0aW9uIEUoYSxiLGMpeyJ1c2Ugc3RyaWN0Ijt2YXIgZD13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShhLG51bGwpLGU9ITE7cmV0dXJuISFkJiYoYi5mb3JFYWNoKGZ1bmN0aW9uKGEpe2QuZ2V0UHJvcGVydHlWYWx1ZShhLnByb3BlcnR5KT09PWEudmFsdWUmJihlPSEwKX0pLCEhZXx8IShhLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCk9PT1jLnRvVXBwZXJDYXNlKCl8fCFhLnBhcmVudE5vZGUpJiZFKGEucGFyZW50Tm9kZSxiLGMpKX1mdW5jdGlvbiBGKGEsYil7InVzZSBzdHJpY3QiO3JldHVybiBuZXcgRXJyb3IoYSsiOiAiK2F4ZS51dGlscy5nZXRTZWxlY3RvcihiKSl9ZnVuY3Rpb24gRyhhLGIsYyxkLGUsZil7InVzZSBzdHJpY3QiO3ZhciBnPWF4ZS51dGlscy5xdWV1ZSgpLGg9YS5mcmFtZXM7aC5mb3JFYWNoKGZ1bmN0aW9uKGUpe3ZhciBmPXtvcHRpb25zOmIsY29tbWFuZDpjLHBhcmFtZXRlcjpkLGNvbnRleHQ6e2luaXRpYXRvcjohMSxwYWdlOmEucGFnZSxpbmNsdWRlOmUuaW5jbHVkZXx8W10sZXhjbHVkZTplLmV4Y2x1ZGV8fFtdfX07Zy5kZWZlcihmdW5jdGlvbihhLGIpe3ZhciBjPWUubm9kZTtheGUudXRpbHMuc2VuZENvbW1hbmRUb0ZyYW1lKGMsZixmdW5jdGlvbihiKXtyZXR1cm4gYj9hKHtyZXN1bHRzOmIsZnJhbWVFbGVtZW50OmMsZnJhbWU6YXhlLnV0aWxzLmdldFNlbGVjdG9yKGMpfSk6dm9pZCBhKG51bGwpfSxiKX0pfSksZy50aGVuKGZ1bmN0aW9uKGEpe2UoYXhlLnV0aWxzLm1lcmdlUmVzdWx0cyhhKSl9KVsiY2F0Y2giXShmKX1mdW5jdGlvbiBIKGEsYil7InVzZSBzdHJpY3QiO2lmKGI9Ynx8MzAwLGEubGVuZ3RoPmIpe3ZhciBjPWEuaW5kZXhPZigiPiIpO2E9YS5zdWJzdHJpbmcoMCxjKzEpfXJldHVybiBhfWZ1bmN0aW9uIEkoYSl7InVzZSBzdHJpY3QiO3ZhciBiPWEub3V0ZXJIVE1MO3JldHVybiBifHwiZnVuY3Rpb24iIT10eXBlb2YgWE1MU2VyaWFsaXplcnx8KGI9KG5ldyBYTUxTZXJpYWxpemVyKS5zZXJpYWxpemVUb1N0cmluZyhhKSksSChifHwiIil9ZnVuY3Rpb24gSihhLGIpeyJ1c2Ugc3RyaWN0IjtiPWJ8fHt9LHRoaXMuc2VsZWN0b3I9Yi5zZWxlY3Rvcnx8W2F4ZS51dGlscy5nZXRTZWxlY3RvcihhKV0sdGhpcy54cGF0aD1iLnhwYXRofHxbYXhlLnV0aWxzLmdldFhwYXRoKGEpXSx0aGlzLnNvdXJjZT12b2lkIDAhPT1iLnNvdXJjZT9iLnNvdXJjZTpJKGEpLHRoaXMuZWxlbWVudD1hfWZ1bmN0aW9uIEsoYSl7InVzZSBzdHJpY3QiO3ZhciBiPTEsYz1hLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCk7Zm9yKGE9YS5wcmV2aW91c0VsZW1lbnRTaWJsaW5nO2E7KWEubm9kZU5hbWUudG9VcHBlckNhc2UoKT09PWMmJmIrKyxhPWEucHJldmlvdXNFbGVtZW50U2libGluZztyZXR1cm4gYn1mdW5jdGlvbiBMKGEsYil7InVzZSBzdHJpY3QiO3ZhciBjLGQsZT1hLnBhcmVudE5vZGUuY2hpbGRyZW47aWYoIWUpcmV0dXJuITE7dmFyIGY9ZS5sZW5ndGg7Zm9yKGM9MDtjPGY7YysrKWlmKGQ9ZVtjXSxkIT09YSYmYXhlLnV0aWxzLm1hdGNoZXNTZWxlY3RvcihkLGIpKXJldHVybiEwO3JldHVybiExfWZ1bmN0aW9uIE0oYSxiKXt2YXIgYyxkO2lmKCFhKXJldHVybltdO2lmKCFiJiY5PT09YS5ub2RlVHlwZSlyZXR1cm4gYj1be3N0cjoiaHRtbCJ9XTtpZihiPWJ8fFtdLGEucGFyZW50Tm9kZSYmYS5wYXJlbnROb2RlIT09YSYmKGI9TShhLnBhcmVudE5vZGUsYikpLGEucHJldmlvdXNTaWJsaW5nKXtkPTEsYz1hLnByZXZpb3VzU2libGluZztkbyAxPT09Yy5ub2RlVHlwZSYmYy5ub2RlTmFtZT09PWEubm9kZU5hbWUmJmQrKyxjPWMucHJldmlvdXNTaWJsaW5nO3doaWxlKGMpOzE9PT1kJiYoZD1udWxsKX1lbHNlIGlmKGEubmV4dFNpYmxpbmcpe2M9YS5uZXh0U2libGluZztkbyAxPT09Yy5ub2RlVHlwZSYmYy5ub2RlTmFtZT09PWEubm9kZU5hbWU/KGQ9MSxjPW51bGwpOihkPW51bGwsYz1jLnByZXZpb3VzU2libGluZyk7d2hpbGUoYyl9aWYoMT09PWEubm9kZVR5cGUpe3ZhciBlPXt9O2Uuc3RyPWEubm9kZU5hbWUudG9Mb3dlckNhc2UoKSxhLmdldEF0dHJpYnV0ZSYmYS5nZXRBdHRyaWJ1dGUoImlkIikmJjE9PT1hLm93bmVyRG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgiIyIrYXhlLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEuaWQpKS5sZW5ndGgmJihlLmlkPWEuZ2V0QXR0cmlidXRlKCJpZCIpKSxkPjEmJihlLmNvdW50PWQpLGIucHVzaChlKX1yZXR1cm4gYn1mdW5jdGlvbiBOKGEpe3JldHVybiBhLnJlZHVjZShmdW5jdGlvbihhLGIpe3JldHVybiBiLmlkPyIvIitiLnN0cisiW0BpZD0nIitiLmlkKyInXSI6YSsoIi8iK2Iuc3RyKSsoYi5jb3VudD4wPyJbIitiLmNvdW50KyJdIjoiIil9LCIiKX1mdW5jdGlvbiBPKGEpeyJ1c2Ugc3RyaWN0IjtpZihkYSYmZGEucGFyZW50Tm9kZSlyZXR1cm4gdm9pZCAwPT09ZGEuc3R5bGVTaGVldD9kYS5hcHBlbmRDaGlsZChkb2N1bWVudC5jcmVhdGVUZXh0Tm9kZShhKSk6ZGEuc3R5bGVTaGVldC5jc3NUZXh0Kz1hLGRhO2lmKGEpe3ZhciBiPWRvY3VtZW50LmhlYWR8fGRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCJoZWFkIilbMF07cmV0dXJuIGRhPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInN0eWxlIiksZGEudHlwZT0idGV4dC9jc3MiLHZvaWQgMD09PWRhLnN0eWxlU2hlZXQ/ZGEuYXBwZW5kQ2hpbGQoZG9jdW1lbnQuY3JlYXRlVGV4dE5vZGUoYSkpOmRhLnN0eWxlU2hlZXQuY3NzVGV4dD1hLGIuYXBwZW5kQ2hpbGQoZGEpLGRhfX1mdW5jdGlvbiBQKGEsYixjKXsidXNlIHN0cmljdCI7dmFyIGQ9YXhlLnV0aWxzLmdldFhwYXRoKGIpLGU9e2VsZW1lbnQ6YixzZWxlY3RvcjpjLHhwYXRoOmR9O2EuZm9yRWFjaChmdW5jdGlvbihhKXthLm5vZGU9YXhlLnV0aWxzLkRxRWxlbWVudC5mcm9tRnJhbWUoYS5ub2RlLGUpO3ZhciBiPWF4ZS51dGlscy5nZXRBbGxDaGVja3MoYSk7Yi5sZW5ndGgmJmIuZm9yRWFjaChmdW5jdGlvbihhKXthLnJlbGF0ZWROb2Rlcz1hLnJlbGF0ZWROb2Rlcy5tYXAoZnVuY3Rpb24oYSl7cmV0dXJuIGF4ZS51dGlscy5EcUVsZW1lbnQuZnJvbUZyYW1lKGEsZSl9KX0pfSl9ZnVuY3Rpb24gUShhLGIpeyJ1c2Ugc3RyaWN0Ijtmb3IodmFyIGMsZCxlPWJbMF0ubm9kZSxmPTAsZz1hLmxlbmd0aDtmPGc7ZisrKWlmKGQ9YVtmXS5ub2RlLGM9YXhlLnV0aWxzLm5vZGVTb3J0ZXIoZC5lbGVtZW50LGUuZWxlbWVudCksYz4wfHwwPT09YyYmZS5zZWxlY3Rvci5sZW5ndGg8ZC5zZWxlY3Rvci5sZW5ndGgpcmV0dXJuIHZvaWQgYS5zcGxpY2UuYXBwbHkoYSxbZiwwXS5jb25jYXQoYikpO2EucHVzaC5hcHBseShhLGIpfWZ1bmN0aW9uIFIoYSl7InVzZSBzdHJpY3QiO3JldHVybiBhJiZhLnJlc3VsdHM/QXJyYXkuaXNBcnJheShhLnJlc3VsdHMpP2EucmVzdWx0cy5sZW5ndGg/YS5yZXN1bHRzOm51bGw6W2EucmVzdWx0c106bnVsbH1mdW5jdGlvbiBTKGEsYil7InVzZSBzdHJpY3QiO3JldHVybiBmdW5jdGlvbihjKXt2YXIgZD1hW2MuaWRdfHx7fSxlPWQubWVzc2FnZXN8fHt9LGY9T2JqZWN0LmFzc2lnbih7fSxkKTtkZWxldGUgZi5tZXNzYWdlcyxmLm1lc3NhZ2U9Yy5yZXN1bHQ9PT1iP2UucGFzczplLmZhaWwsYXhlLnV0aWxzLmV4dGVuZE1ldGFEYXRhKGMsZil9fWZ1bmN0aW9uIFQoYSxiKXsidXNlIHN0cmljdCI7dmFyIGMsZCxlLGY9YXhlLl9hdWRpdCYmYXhlLl9hdWRpdC50YWdFeGNsdWRlP2F4ZS5fYXVkaXQudGFnRXhjbHVkZTpbXTtyZXR1cm4gYi5pbmNsdWRlfHxiLmV4Y2x1ZGU/KGM9Yi5pbmNsdWRlfHxbXSxjPUFycmF5LmlzQXJyYXkoYyk/YzpbY10sZD1iLmV4Y2x1ZGV8fFtdLGQ9QXJyYXkuaXNBcnJheShkKT9kOltkXSxkPWQuY29uY2F0KGYuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBjLmluZGV4T2YoYSk9PT0tMX0pKSk6KGM9QXJyYXkuaXNBcnJheShiKT9iOltiXSxkPWYuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBjLmluZGV4T2YoYSk9PT0tMX0pKSxlPWMuc29tZShmdW5jdGlvbihiKXtyZXR1cm4gYS50YWdzLmluZGV4T2YoYikhPT0tMX0pLCEhKGV8fDA9PT1jLmxlbmd0aCYmYS5lbmFibGVkIT09ITEpJiZkLmV2ZXJ5KGZ1bmN0aW9uKGIpe3JldHVybiBhLnRhZ3MuaW5kZXhPZihiKT09PS0xfSl9ZnVuY3Rpb24gVShhKXsidXNlIHN0cmljdCI7cmV0dXJuIGEuc29ydChmdW5jdGlvbihhLGIpe3JldHVybiBheGUudXRpbHMuY29udGFpbnMoYSxiKT8xOi0xfSlbMF19ZnVuY3Rpb24gVihhLGIpeyJ1c2Ugc3RyaWN0Ijt2YXIgYz1iLmluY2x1ZGUmJlUoYi5pbmNsdWRlLmZpbHRlcihmdW5jdGlvbihiKXtyZXR1cm4gYXhlLnV0aWxzLmNvbnRhaW5zKGIsYSl9KSksZD1iLmV4Y2x1ZGUmJlUoYi5leGNsdWRlLmZpbHRlcihmdW5jdGlvbihiKXtyZXR1cm4gYXhlLnV0aWxzLmNvbnRhaW5zKGIsYSl9KSk7cmV0dXJuISEoIWQmJmN8fGQmJmF4ZS51dGlscy5jb250YWlucyhkLGMpKX1mdW5jdGlvbiBXKGEsYixjKXsidXNlIHN0cmljdCI7Zm9yKHZhciBkPTAsZT1iLmxlbmd0aDtkPGU7ZCsrKWEuaW5kZXhPZihiW2RdKT09PS0xJiZWKGJbZF0sYykmJmEucHVzaChiW2RdKX12YXIgZG9jdW1lbnQ9d2luZG93LmRvY3VtZW50LFg9ImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmInN5bWJvbCI9PXR5cGVvZiBTeW1ib2wuaXRlcmF0b3I/ZnVuY3Rpb24oYSl7cmV0dXJuIHR5cGVvZiBhfTpmdW5jdGlvbihhKXtyZXR1cm4gYSYmImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmYS5jb25zdHJ1Y3Rvcj09PVN5bWJvbCYmYSE9PVN5bWJvbC5wcm90b3R5cGU/InN5bWJvbCI6dHlwZW9mIGF9LGF4ZT1heGV8fHt9O2F4ZS52ZXJzaW9uPSIyLjEuNyIsImZ1bmN0aW9uIj09dHlwZW9mIGRlZmluZSYmZGVmaW5lLmFtZCYmZGVmaW5lKFtdLGZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO3JldHVybiBheGV9KSwib2JqZWN0Ij09PSgidW5kZWZpbmVkIj09dHlwZW9mIG1vZHVsZT8idW5kZWZpbmVkIjpYKG1vZHVsZSkpJiZtb2R1bGUuZXhwb3J0cyYmImZ1bmN0aW9uIj09dHlwZW9mIGEudG9TdHJpbmcmJihheGUuc291cmNlPSIoIithLnRvU3RyaW5nKCkrIikodGhpcywgdGhpcy5kb2N1bWVudCk7Iixtb2R1bGUuZXhwb3J0cz1heGUpLCJmdW5jdGlvbiI9PXR5cGVvZiB3aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZSYmKHdpbmRvdy5heGU9YXhlKTt2YXIgY29tbW9ucyx1dGlscz1heGUudXRpbHM9e30sWT17fSxYPSJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJiJzeW1ib2wiPT10eXBlb2YgU3ltYm9sLml0ZXJhdG9yP2Z1bmN0aW9uKGEpe3JldHVybiB0eXBlb2YgYX06ZnVuY3Rpb24oYSl7cmV0dXJuIGEmJiJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJmEuY29uc3RydWN0b3I9PT1TeW1ib2wmJmEhPT1TeW1ib2wucHJvdG90eXBlPyJzeW1ib2wiOnR5cGVvZiBhfTtkLnByb3RvdHlwZS5faW5pdD1mdW5jdGlvbigpe3ZhciBhPWIodGhpcy5kZWZhdWx0Q29uZmlnKTtheGUuY29tbW9ucz1jb21tb25zPWEuY29tbW9ucyx0aGlzLnJlcG9ydGVyPWEucmVwb3J0ZXIsdGhpcy5jb21tYW5kcz17fSx0aGlzLnJ1bGVzPVtdLHRoaXMuY2hlY2tzPXt9LGMoYS5ydWxlcyx0aGlzLCJhZGRSdWxlIiksYyhhLmNoZWNrcyx0aGlzLCJhZGRDaGVjayIpLHRoaXMuZGF0YT17fSx0aGlzLmRhdGEuY2hlY2tzPWEuZGF0YSYmYS5kYXRhLmNoZWNrc3x8e30sdGhpcy5kYXRhLnJ1bGVzPWEuZGF0YSYmYS5kYXRhLnJ1bGVzfHx7fSx0aGlzLmRhdGEuZmFpbHVyZVN1bW1hcmllcz1hLmRhdGEmJmEuZGF0YS5mYWlsdXJlU3VtbWFyaWVzfHx7fSx0aGlzLl9jb25zdHJ1Y3RIZWxwVXJscygpfSxkLnByb3RvdHlwZS5yZWdpc3RlckNvbW1hbmQ9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3RoaXMuY29tbWFuZHNbYS5pZF09YS5jYWxsYmFja30sZC5wcm90b3R5cGUuYWRkUnVsZT1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7YS5tZXRhZGF0YSYmKHRoaXMuZGF0YS5ydWxlc1thLmlkXT1hLm1ldGFkYXRhKTt2YXIgYj10aGlzLmdldFJ1bGUoYS5pZCk7Yj9iLmNvbmZpZ3VyZShhKTp0aGlzLnJ1bGVzLnB1c2gobmV3IG8oYSx0aGlzKSl9LGQucHJvdG90eXBlLmFkZENoZWNrPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1hLm1ldGFkYXRhOyJvYmplY3QiPT09KCJ1bmRlZmluZWQiPT10eXBlb2YgYj8idW5kZWZpbmVkIjpYKGIpKSYmKHRoaXMuZGF0YS5jaGVja3NbYS5pZF09Yiwib2JqZWN0Ij09PVgoYi5tZXNzYWdlcykmJk9iamVjdC5rZXlzKGIubWVzc2FnZXMpLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4gYi5tZXNzYWdlcy5oYXNPd25Qcm9wZXJ0eShhKSYmInN0cmluZyI9PXR5cGVvZiBiLm1lc3NhZ2VzW2FdfSkuZm9yRWFjaChmdW5jdGlvbihhKXswPT09Yi5tZXNzYWdlc1thXS5pbmRleE9mKCJmdW5jdGlvbiIpJiYoYi5tZXNzYWdlc1thXT1uZXcgRnVuY3Rpb24oInJldHVybiAiK2IubWVzc2FnZXNbYV0rIjsiKSgpKX0pKSx0aGlzLmNoZWNrc1thLmlkXT90aGlzLmNoZWNrc1thLmlkXS5jb25maWd1cmUoYSk6dGhpcy5jaGVja3NbYS5pZF09bmV3IGcoYSl9LGQucHJvdG90eXBlLnJ1bj1mdW5jdGlvbihhLGIsYyxkKXsidXNlIHN0cmljdCI7dGhpcy52YWxpZGF0ZU9wdGlvbnMoYik7dmFyIGU9YXhlLnV0aWxzLnF1ZXVlKCk7dGhpcy5ydWxlcy5mb3JFYWNoKGZ1bmN0aW9uKGMpe2F4ZS51dGlscy5ydWxlU2hvdWxkUnVuKGMsYSxiKSYmZS5kZWZlcihmdW5jdGlvbihkLGUpe2MucnVuKGEsYixkLGZ1bmN0aW9uKGEpe2lmKGIuZGVidWcpZShhKTtlbHNle3ZhciBmPU9iamVjdC5hc3NpZ24obmV3IG4oYykse3Jlc3VsdDpheGUuY29uc3RhbnRzLkNBTlRURUxMLGRlc2NyaXB0aW9uOiJBbiBlcnJvciBvY2N1cmVkIHdoaWxlIHJ1bm5pbmcgdGhpcyBydWxlIixtZXNzYWdlOmEubWVzc2FnZSxoZWxwOmEuc3RhY2t8fGEubWVzc2FnZSxlcnJvcjphfSk7ZChmKX19KX0pfSksZS50aGVuKGZ1bmN0aW9uKGEpe2MoYS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuISFhfSkpfSlbImNhdGNoIl0oZCl9LGQucHJvdG90eXBlLmFmdGVyPWZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO3ZhciBjPXRoaXMucnVsZXM7cmV0dXJuIGEubWFwKGZ1bmN0aW9uKGEpe3ZhciBkPWF4ZS51dGlscy5maW5kQnkoYywiaWQiLGEuaWQpO3JldHVybiBkLmFmdGVyKGEsYil9KX0sZC5wcm90b3R5cGUuZ2V0UnVsZT1mdW5jdGlvbihhKXtyZXR1cm4gdGhpcy5ydWxlcy5maW5kKGZ1bmN0aW9uKGIpe3JldHVybiBiLmlkPT09YX0pfSxkLnByb3RvdHlwZS52YWxpZGF0ZU9wdGlvbnM9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPXRoaXM7aWYoIm9iamVjdCI9PT1YKGEucnVuT25seSkpe3ZhciBjPWEucnVuT25seTtpZigicnVsZSI9PT1jLnR5cGUmJkFycmF5LmlzQXJyYXkoYy52YWx1ZSkpYy52YWx1ZS5mb3JFYWNoKGZ1bmN0aW9uKGEpe2lmKCFiLmdldFJ1bGUoYSkpdGhyb3cgbmV3IEVycm9yKCJ1bmtub3duIHJ1bGUgYCIrYSsiYCBpbiBvcHRpb25zLnJ1bk9ubHkiKX0pO2Vsc2UgaWYoQXJyYXkuaXNBcnJheShjLnZhbHVlKSYmYy52YWx1ZS5sZW5ndGg+MCl7dmFyIGQ9W10uY29uY2F0KGMudmFsdWUpO2lmKGIucnVsZXMuZm9yRWFjaChmdW5jdGlvbihhKXt2YXIgYixjLGU7aWYoZClmb3IoYz0wLGU9YS50YWdzLmxlbmd0aDtjPGU7YysrKWI9ZC5pbmRleE9mKGEudGFnc1tjXSksYiE9PS0xJiZkLnNwbGljZShiLDEpfSksMCE9PWQubGVuZ3RoKXRocm93IG5ldyBFcnJvcigiY291bGQgbm90IGZpbmQgdGFncyBgIitkLmpvaW4oImAsIGAiKSsiYCIpfX1yZXR1cm4ib2JqZWN0Ij09PVgoYS5ydWxlcykmJk9iamVjdC5rZXlzKGEucnVsZXMpLmZvckVhY2goZnVuY3Rpb24oYSl7aWYoIWIuZ2V0UnVsZShhKSl0aHJvdyBuZXcgRXJyb3IoInVua25vd24gcnVsZSBgIithKyJgIGluIG9wdGlvbnMucnVsZXMiKX0pLGF9LGQucHJvdG90eXBlLnNldEJyYW5kaW5nPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjthJiZhLmhhc093blByb3BlcnR5KCJicmFuZCIpJiZhLmJyYW5kJiYic3RyaW5nIj09dHlwZW9mIGEuYnJhbmQmJih0aGlzLmJyYW5kPWEuYnJhbmQpLGEmJmEuaGFzT3duUHJvcGVydHkoImFwcGxpY2F0aW9uIikmJmEuYXBwbGljYXRpb24mJiJzdHJpbmciPT10eXBlb2YgYS5hcHBsaWNhdGlvbiYmKHRoaXMuYXBwbGljYXRpb249YS5hcHBsaWNhdGlvbiksdGhpcy5fY29uc3RydWN0SGVscFVybHMoKX0sZC5wcm90b3R5cGUuX2NvbnN0cnVjdEhlbHBVcmxzPWZ1bmN0aW9uKCl7dmFyIGE9dGhpcyxiPWF4ZS52ZXJzaW9uLnN1YnN0cmluZygwLGF4ZS52ZXJzaW9uLmxhc3RJbmRleE9mKCIuIikpO3RoaXMucnVsZXMuZm9yRWFjaChmdW5jdGlvbihjKXthLmRhdGEucnVsZXNbYy5pZF09YS5kYXRhLnJ1bGVzW2MuaWRdfHx7fSxhLmRhdGEucnVsZXNbYy5pZF0uaGVscFVybD0iaHR0cHM6Ly9kZXF1ZXVuaXZlcnNpdHkuY29tL3J1bGVzLyIrYS5icmFuZCsiLyIrYisiLyIrYy5pZCsiP2FwcGxpY2F0aW9uPSIrYS5hcHBsaWNhdGlvbn0pfSxkLnByb3RvdHlwZS5yZXNldFJ1bGVzQW5kQ2hlY2tzPWZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO3RoaXMuX2luaXQoKX0sZy5wcm90b3R5cGUuZW5hYmxlZD0hMCxnLnByb3RvdHlwZS5ydW49ZnVuY3Rpb24oYSxiLGMsZCl7InVzZSBzdHJpY3QiO2I9Ynx8e307dmFyIGY9Yi5oYXNPd25Qcm9wZXJ0eSgiZW5hYmxlZCIpP2IuZW5hYmxlZDp0aGlzLmVuYWJsZWQsZz1iLm9wdGlvbnN8fHRoaXMub3B0aW9ucztpZihmKXt2YXIgaCxpPW5ldyBlKHRoaXMpLGo9YXhlLnV0aWxzLmNoZWNrSGVscGVyKGksYyxkKTt0cnl7aD10aGlzLmV2YWx1YXRlLmNhbGwoaixhLGcpfWNhdGNoKGspe3JldHVybiB2b2lkIGQoayl9ai5pc0FzeW5jfHwoaS5yZXN1bHQ9aCxzZXRUaW1lb3V0KGZ1bmN0aW9uKCl7YyhpKX0sMCkpfWVsc2UgYyhudWxsKX0sZy5wcm90b3R5cGUuY29uZmlndXJlPWZ1bmN0aW9uKGEpe3ZhciBiPXRoaXM7WyJvcHRpb25zIiwiZW5hYmxlZCJdLmZpbHRlcihmdW5jdGlvbihiKXtyZXR1cm4gYS5oYXNPd25Qcm9wZXJ0eShiKX0pLmZvckVhY2goZnVuY3Rpb24oYyl7cmV0dXJuIGJbY109YVtjXX0pLFsiZXZhbHVhdGUiLCJhZnRlciJdLmZpbHRlcihmdW5jdGlvbihiKXtyZXR1cm4gYS5oYXNPd25Qcm9wZXJ0eShiKX0pLmZvckVhY2goZnVuY3Rpb24oYyl7cmV0dXJuIGJbY109ZihhW2NdKX0pfTt2YXIgWD0iZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiYic3ltYm9sIj09dHlwZW9mIFN5bWJvbC5pdGVyYXRvcj9mdW5jdGlvbihhKXtyZXR1cm4gdHlwZW9mIGF9OmZ1bmN0aW9uKGEpe3JldHVybiBhJiYiZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiZhLmNvbnN0cnVjdG9yPT09U3ltYm9sJiZhIT09U3ltYm9sLnByb3RvdHlwZT8ic3ltYm9sIjp0eXBlb2YgYX07by5wcm90b3R5cGUubWF0Y2hlcz1mdW5jdGlvbigpeyJ1c2Ugc3RyaWN0IjtyZXR1cm4hMH0sby5wcm90b3R5cGUuZ2F0aGVyPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1heGUudXRpbHMuc2VsZWN0KHRoaXMuc2VsZWN0b3IsYSk7cmV0dXJuIHRoaXMuZXhjbHVkZUhpZGRlbj9iLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4hYXhlLnV0aWxzLmlzSGlkZGVuKGEpfSk6Yn0sby5wcm90b3R5cGUucnVuQ2hlY2tzPWZ1bmN0aW9uKGEsYixjLGQsZSl7InVzZSBzdHJpY3QiO3ZhciBmPXRoaXMsZz1heGUudXRpbHMucXVldWUoKTt0aGlzW2FdLmZvckVhY2goZnVuY3Rpb24oYSl7dmFyIGQ9Zi5fYXVkaXQuY2hlY2tzW2EuaWR8fGFdLGU9YXhlLnV0aWxzLmdldENoZWNrT3B0aW9uKGQsZi5pZCxjKTtnLmRlZmVyKGZ1bmN0aW9uKGEsYyl7ZC5ydW4oYixlLGEsYyl9KX0pLGcudGhlbihmdW5jdGlvbihiKXtiPWIuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBhfSksZCh7dHlwZTphLHJlc3VsdHM6Yn0pfSlbImNhdGNoIl0oZSl9LG8ucHJvdG90eXBlLnJ1bj1mdW5jdGlvbihhLGIsYyxkKXsidXNlIHN0cmljdCI7dmFyIGUsZj10aGlzLmdhdGhlcihhKSxnPWF4ZS51dGlscy5xdWV1ZSgpLGg9dGhpcztlPW5ldyBuKHRoaXMpLGYuZm9yRWFjaChmdW5jdGlvbihhKXtoLm1hdGNoZXMoYSkmJmcuZGVmZXIoZnVuY3Rpb24oYyxkKXt2YXIgZj1heGUudXRpbHMucXVldWUoKTtmLmRlZmVyKGZ1bmN0aW9uKGMsZCl7aC5ydW5DaGVja3MoImFueSIsYSxiLGMsZCl9KSxmLmRlZmVyKGZ1bmN0aW9uKGMsZCl7aC5ydW5DaGVja3MoImFsbCIsYSxiLGMsZCl9KSxmLmRlZmVyKGZ1bmN0aW9uKGMsZCl7aC5ydW5DaGVja3MoIm5vbmUiLGEsYixjLGQpfSksZi50aGVuKGZ1bmN0aW9uKGIpe2lmKGIubGVuZ3RoKXt2YXIgZD0hMSxmPXt9O2IuZm9yRWFjaChmdW5jdGlvbihhKXt2YXIgYj1hLnJlc3VsdHMuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBhfSk7ZlthLnR5cGVdPWIsYi5sZW5ndGgmJihkPSEwKX0pLGQmJihmLm5vZGU9bmV3IGF4ZS51dGlscy5EcUVsZW1lbnQoYSksZS5ub2Rlcy5wdXNoKGYpKX1jKCl9KVsiY2F0Y2giXShkKX0pfSksZy50aGVuKGZ1bmN0aW9uKCl7YyhlKX0pWyJjYXRjaCJdKGQpfSxvLnByb3RvdHlwZS5hZnRlcj1mdW5jdGlvbihhLGIpeyJ1c2Ugc3RyaWN0Ijt2YXIgYz1wKHRoaXMpLGQ9dGhpcy5pZDtyZXR1cm4gYy5mb3JFYWNoKGZ1bmN0aW9uKGMpe3ZhciBlPXEoYS5ub2RlcyxjLmlkKSxmPWF4ZS51dGlscy5nZXRDaGVja09wdGlvbihjLGQsYiksZz1jLmFmdGVyKGUsZik7ZS5mb3JFYWNoKGZ1bmN0aW9uKGEpe2cuaW5kZXhPZihhKT09PS0xJiYoYS5maWx0ZXJlZD0hMCl9KX0pLGEubm9kZXM9cyhhKSxhfSxvLnByb3RvdHlwZS5jb25maWd1cmU9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO2EuaGFzT3duUHJvcGVydHkoInNlbGVjdG9yIikmJih0aGlzLnNlbGVjdG9yPWEuc2VsZWN0b3IpLGEuaGFzT3duUHJvcGVydHkoImV4Y2x1ZGVIaWRkZW4iKSYmKHRoaXMuZXhjbHVkZUhpZGRlbj0iYm9vbGVhbiIhPXR5cGVvZiBhLmV4Y2x1ZGVIaWRkZW58fGEuZXhjbHVkZUhpZGRlbiksYS5oYXNPd25Qcm9wZXJ0eSgiZW5hYmxlZCIpJiYodGhpcy5lbmFibGVkPSJib29sZWFuIiE9dHlwZW9mIGEuZW5hYmxlZHx8YS5lbmFibGVkKSxhLmhhc093blByb3BlcnR5KCJwYWdlTGV2ZWwiKSYmKHRoaXMucGFnZUxldmVsPSJib29sZWFuIj09dHlwZW9mIGEucGFnZUxldmVsJiZhLnBhZ2VMZXZlbCksYS5oYXNPd25Qcm9wZXJ0eSgiYW55IikmJih0aGlzLmFueT1hLmFueSksYS5oYXNPd25Qcm9wZXJ0eSgiYWxsIikmJih0aGlzLmFsbD1hLmFsbCksYS5oYXNPd25Qcm9wZXJ0eSgibm9uZSIpJiYodGhpcy5ub25lPWEubm9uZSksYS5oYXNPd25Qcm9wZXJ0eSgidGFncyIpJiYodGhpcy50YWdzPWEudGFncyksYS5oYXNPd25Qcm9wZXJ0eSgibWF0Y2hlcyIpJiYoInN0cmluZyI9PXR5cGVvZiBhLm1hdGNoZXM/dGhpcy5tYXRjaGVzPW5ldyBGdW5jdGlvbigicmV0dXJuICIrYS5tYXRjaGVzKyI7IikoKTp0aGlzLm1hdGNoZXM9YS5tYXRjaGVzKX0sZnVuY3Rpb24oYXhlKXt2YXIgYT1be25hbWU6Ik5BIix2YWx1ZToiaW5hcHBsaWNhYmxlIixwcmlvcml0eTowLGdyb3VwOiJpbmFwcGxpY2FibGUifSx7bmFtZToiUEFTUyIsdmFsdWU6InBhc3NlZCIscHJpb3JpdHk6MSxncm91cDoicGFzc2VzIn0se25hbWU6IkNBTlRURUxMIix2YWx1ZToiY2FudFRlbGwiLHByaW9yaXR5OjIsZ3JvdXA6ImluY29tcGxldGUifSx7bmFtZToiRkFJTCIsdmFsdWU6ImZhaWxlZCIscHJpb3JpdHk6Myxncm91cDoidmlvbGF0aW9ucyJ9XSxiPXtyZXN1bHRzOltdLHJlc3VsdEdyb3VwczpbXSxyZXN1bHRHcm91cE1hcDp7fSxpbXBhY3Q6T2JqZWN0LmZyZWV6ZShbIm1pbm9yIiwibW9kZXJhdGUiLCJzZXJpb3VzIiwiY3JpdGljYWwiXSl9O2EuZm9yRWFjaChmdW5jdGlvbihhKXt2YXIgYz1hLm5hbWUsZD1hLnZhbHVlLGU9YS5wcmlvcml0eSxmPWEuZ3JvdXA7YltjXT1kLGJbYysiX1BSSU8iXT1lLGJbYysiX0dST1VQIl09ZixiLnJlc3VsdHNbZV09ZCxiLnJlc3VsdEdyb3Vwc1tlXT1mLGIucmVzdWx0R3JvdXBNYXBbZF09Zn0pLE9iamVjdC5mcmVlemUoYi5yZXN1bHRzKSxPYmplY3QuZnJlZXplKGIucmVzdWx0R3JvdXBzKSxPYmplY3QuZnJlZXplKGIucmVzdWx0R3JvdXBNYXApLE9iamVjdC5mcmVlemUoYiksT2JqZWN0LmRlZmluZVByb3BlcnR5KGF4ZSwiY29uc3RhbnRzIix7dmFsdWU6YixlbnVtZXJhYmxlOiEwLGNvbmZpZ3VyYWJsZTohMSx3cml0YWJsZTohMX0pfShheGUpO3ZhciBYPSJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJiJzeW1ib2wiPT10eXBlb2YgU3ltYm9sLml0ZXJhdG9yP2Z1bmN0aW9uKGEpe3JldHVybiB0eXBlb2YgYX06ZnVuY3Rpb24oYSl7cmV0dXJuIGEmJiJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJmEuY29uc3RydWN0b3I9PT1TeW1ib2wmJmEhPT1TeW1ib2wucHJvdG90eXBlPyJzeW1ib2wiOnR5cGVvZiBhfTtheGUubG9nPWZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiOyJvYmplY3QiPT09KCJ1bmRlZmluZWQiPT10eXBlb2YgY29uc29sZT8idW5kZWZpbmVkIjpYKGNvbnNvbGUpKSYmY29uc29sZS5sb2cmJkZ1bmN0aW9uLnByb3RvdHlwZS5hcHBseS5jYWxsKGNvbnNvbGUubG9nLGNvbnNvbGUsYXJndW1lbnRzKX07dmFyIFg9ImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmInN5bWJvbCI9PXR5cGVvZiBTeW1ib2wuaXRlcmF0b3I/ZnVuY3Rpb24oYSl7cmV0dXJuIHR5cGVvZiBhfTpmdW5jdGlvbihhKXtyZXR1cm4gYSYmImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmYS5jb25zdHJ1Y3Rvcj09PVN5bWJvbCYmYSE9PVN5bWJvbC5wcm90b3R5cGU/InN5bWJvbCI6dHlwZW9mIGF9O2F4ZS5hMTF5Q2hlY2s9ZnVuY3Rpb24oYSxiLGMpeyJ1c2Ugc3RyaWN0IjsiZnVuY3Rpb24iPT10eXBlb2YgYiYmKGM9YixiPXt9KSxiJiYib2JqZWN0Ij09PSgidW5kZWZpbmVkIj09dHlwZW9mIGI/InVuZGVmaW5lZCI6WChiKSl8fChiPXt9KTt2YXIgZD1heGUuX2F1ZGl0O2lmKCFkKXRocm93IG5ldyBFcnJvcigiTm8gYXVkaXQgY29uZmlndXJlZCIpO2IucmVwb3J0ZXI9Yi5yZXBvcnRlcnx8ZC5yZXBvcnRlcnx8InYyIjt2YXIgZT1heGUuZ2V0UmVwb3J0ZXIoYi5yZXBvcnRlcik7YXhlLl9ydW5SdWxlcyhhLGIsZnVuY3Rpb24oYSl7dmFyIGQ9ZShhLGIsYyk7dm9pZCAwIT09ZCYmYyhkKX0sYXhlLmxvZyl9LGF4ZS5jbGVhbnVwPXQsYXhlLmNvbmZpZ3VyZT11LGF4ZS5nZXRSdWxlcz1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7YT1hfHxbXTt2YXIgYj1hLmxlbmd0aD9heGUuX2F1ZGl0LnJ1bGVzLmZpbHRlcihmdW5jdGlvbihiKXtyZXR1cm4hIWEuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBiLnRhZ3MuaW5kZXhPZihhKSE9PS0xfSkubGVuZ3RofSk6YXhlLl9hdWRpdC5ydWxlcyxjPWF4ZS5fYXVkaXQuZGF0YS5ydWxlc3x8e307cmV0dXJuIGIubWFwKGZ1bmN0aW9uKGEpe3ZhciBiPWNbYS5pZF18fHt9O3JldHVybntydWxlSWQ6YS5pZCxkZXNjcmlwdGlvbjpiLmRlc2NyaXB0aW9uLGhlbHA6Yi5oZWxwLGhlbHBVcmw6Yi5oZWxwVXJsLHRhZ3M6YS50YWdzfX0pfSxheGUuX2xvYWQ9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO2F4ZS51dGlscy5yZXNwb25kYWJsZS5zdWJzY3JpYmUoImF4ZS5waW5nIixmdW5jdGlvbihhLGIsYyl7Yyh7YXhlOiEwfSl9KSxheGUudXRpbHMucmVzcG9uZGFibGUuc3Vic2NyaWJlKCJheGUuc3RhcnQiLHYpLGF4ZS5fYXVkaXQ9bmV3IGQoYSl9O3ZhciBheGU9YXhlfHx7fTtheGUucGx1Z2lucz17fSx3LnByb3RvdHlwZS5ydW49ZnVuY3Rpb24oKXsidXNlIHN0cmljdCI7cmV0dXJuIHRoaXMuX3J1bi5hcHBseSh0aGlzLGFyZ3VtZW50cyl9LHcucHJvdG90eXBlLmNvbGxlY3Q9ZnVuY3Rpb24oKXsidXNlIHN0cmljdCI7cmV0dXJuIHRoaXMuX2NvbGxlY3QuYXBwbHkodGhpcyxhcmd1bWVudHMpfSx3LnByb3RvdHlwZS5jbGVhbnVwPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1heGUudXRpbHMucXVldWUoKSxjPXRoaXM7T2JqZWN0LmtleXModGhpcy5fcmVnaXN0cnkpLmZvckVhY2goZnVuY3Rpb24oYSl7Yi5kZWZlcihmdW5jdGlvbihiKXtjLl9yZWdpc3RyeVthXS5jbGVhbnVwKGIpfSl9KSxiLnRoZW4oZnVuY3Rpb24oKXthKCl9KX0sdy5wcm90b3R5cGUuYWRkPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt0aGlzLl9yZWdpc3RyeVthLmlkXT1hfSxheGUucmVnaXN0ZXJQbHVnaW49ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO2F4ZS5wbHVnaW5zW2EuaWRdPW5ldyB3KGEpfTt2YXIgWiwkPXt9O2F4ZS5nZXRSZXBvcnRlcj1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7cmV0dXJuInN0cmluZyI9PXR5cGVvZiBhJiYkW2FdPyRbYV06ImZ1bmN0aW9uIj09dHlwZW9mIGE/YTpafSxheGUuYWRkUmVwb3J0ZXI9ZnVuY3Rpb24oYSxiLGMpeyJ1c2Ugc3RyaWN0IjskW2FdPWIsYyYmKFo9Yil9LGF4ZS5yZXNldD14LGF4ZS5fcnVuUnVsZXM9eTt2YXIgWD0iZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiYic3ltYm9sIj09dHlwZW9mIFN5bWJvbC5pdGVyYXRvcj9mdW5jdGlvbihhKXtyZXR1cm4gdHlwZW9mIGF9OmZ1bmN0aW9uKGEpe3JldHVybiBhJiYiZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiZhLmNvbnN0cnVjdG9yPT09U3ltYm9sJiZhIT09U3ltYm9sLnByb3RvdHlwZT8ic3ltYm9sIjp0eXBlb2YgYX0sXz1mdW5jdGlvbigpe307YXhlLnJ1bj1mdW5jdGlvbihhLGIsYyl7InVzZSBzdHJpY3QiO2lmKCFheGUuX2F1ZGl0KXRocm93IG5ldyBFcnJvcigiTm8gYXVkaXQgY29uZmlndXJlZCIpO3ZhciBkPUEoYSxiLGMpO2E9ZC5jb250ZXh0LGI9ZC5vcHRpb25zLGM9ZC5jYWxsYmFjayxiLnJlcG9ydGVyPWIucmVwb3J0ZXJ8fGF4ZS5fYXVkaXQucmVwb3J0ZXJ8fCJ2MSI7dmFyIGU9dm9pZCAwLGY9XyxnPV87cmV0dXJuIHdpbmRvdy5Qcm9taXNlJiZjPT09XyYmKGU9bmV3IFByb21pc2UoZnVuY3Rpb24oYSxiKXtmPWIsZz1hfSkpLGF4ZS5fcnVuUnVsZXMoYSxiLGZ1bmN0aW9uKGEpe3ZhciBkPWZ1bmN0aW9uKGEpe3RyeXtjKG51bGwsYSl9Y2F0Y2goYil7YXhlLmxvZyhiKX1nKGEpfTt0cnl7dmFyIGU9YXhlLmdldFJlcG9ydGVyKGIucmVwb3J0ZXIpLGg9ZShhLGIsZCk7dm9pZCAwIT09aCYmZChoKX1jYXRjaChpKXtjKGkpLGYoaSl9fSxmdW5jdGlvbihhKXtjKGEpLGYoYSl9KSxlfSxZLmZhaWx1cmVTdW1tYXJ5PWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj17fTtyZXR1cm4gYi5ub25lPWEubm9uZS5jb25jYXQoYS5hbGwpLGIuYW55PWEuYW55LE9iamVjdC5rZXlzKGIpLm1hcChmdW5jdGlvbihhKXtpZihiW2FdLmxlbmd0aCl7dmFyIGM9YXhlLl9hdWRpdC5kYXRhLmZhaWx1cmVTdW1tYXJpZXNbYV07cmV0dXJuIGMmJiJmdW5jdGlvbiI9PXR5cGVvZiBjLmZhaWx1cmVNZXNzYWdlP2MuZmFpbHVyZU1lc3NhZ2UoYlthXS5tYXAoZnVuY3Rpb24oYSl7cmV0dXJuIGEubWVzc2FnZXx8IiJ9KSk6dm9pZCAwfX0pLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4gdm9pZCAwIT09YX0pLmpvaW4oIlxuXG4iKX07dmFyIFg9ImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmInN5bWJvbCI9PXR5cGVvZiBTeW1ib2wuaXRlcmF0b3I/ZnVuY3Rpb24oYSl7cmV0dXJuIHR5cGVvZiBhfTpmdW5jdGlvbihhKXtyZXR1cm4gYSYmImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmYS5jb25zdHJ1Y3Rvcj09PVN5bWJvbCYmYSE9PVN5bWJvbC5wcm90b3R5cGU/InN5bWJvbCI6dHlwZW9mIGF9LGFhPWF4ZS5jb25zdGFudHMucmVzdWx0R3JvdXBzO1kucHJvY2Vzc0FnZ3JlZ2F0ZT1mdW5jdGlvbihhLGIpe3ZhciBjPWF4ZS51dGlscy5hZ2dyZWdhdGVSZXN1bHQoYSk7cmV0dXJuIGMudGltZXN0YW1wPShuZXcgRGF0ZSkudG9JU09TdHJpbmcoKSxjLnVybD13aW5kb3cubG9jYXRpb24uaHJlZixhYS5mb3JFYWNoKGZ1bmN0aW9uKGEpe2NbYV09KGNbYV18fFtdKS5tYXAoZnVuY3Rpb24oYSl7cmV0dXJuIGE9T2JqZWN0LmFzc2lnbih7fSxhKSxBcnJheS5pc0FycmF5KGEubm9kZXMpJiZhLm5vZGVzLmxlbmd0aD4wJiYoYS5ub2Rlcz1hLm5vZGVzLm1hcChmdW5jdGlvbihhKXtyZXR1cm4ib2JqZWN0Ij09PVgoYS5ub2RlKSYmKGEuaHRtbD1hLm5vZGUuc291cmNlLGEudGFyZ2V0PWEubm9kZS5zZWxlY3RvcixiLnhwYXRoJiYoYS54cGF0aD1hLm5vZGUueHBhdGgpKSxkZWxldGUgYS5yZXN1bHQsZGVsZXRlIGEubm9kZSxCKGEsYi54cGF0aCksYX0pKSxhYS5mb3JFYWNoKGZ1bmN0aW9uKGIpe3JldHVybiBkZWxldGUgYVtiXX0pLGRlbGV0ZSBhLnBhZ2VMZXZlbCxkZWxldGUgYS5yZXN1bHQsYX0pfSksY30sYXhlLmFkZFJlcG9ydGVyKCJuYSIsZnVuY3Rpb24oYSxiLGMpeyJ1c2Ugc3RyaWN0IjsiZnVuY3Rpb24iPT10eXBlb2YgYiYmKGM9YixiPXt9KTt2YXIgZD1ZLnByb2Nlc3NBZ2dyZWdhdGUoYSxiKTtjKHt2aW9sYXRpb25zOmQudmlvbGF0aW9ucyxwYXNzZXM6ZC5wYXNzZXMsaW5jb21wbGV0ZTpkLmluY29tcGxldGUsaW5hcHBsaWNhYmxlOmQuaW5hcHBsaWNhYmxlLHRpbWVzdGFtcDpkLnRpbWVzdGFtcCx1cmw6ZC51cmx9KX0pLGF4ZS5hZGRSZXBvcnRlcigibm8tcGFzc2VzIixmdW5jdGlvbihhLGIsYyl7InVzZSBzdHJpY3QiOyJmdW5jdGlvbiI9PXR5cGVvZiBiJiYoYz1iLGI9e30pO3ZhciBkPVkucHJvY2Vzc0FnZ3JlZ2F0ZShhLGIpO2Moe3Zpb2xhdGlvbnM6ZC52aW9sYXRpb25zLHRpbWVzdGFtcDpkLnRpbWVzdGFtcCx1cmw6ZC51cmx9KX0pLGF4ZS5hZGRSZXBvcnRlcigicmF3IixmdW5jdGlvbihhLGIsYyl7InVzZSBzdHJpY3QiOyJmdW5jdGlvbiI9PXR5cGVvZiBiJiYoYz1iLGI9e30pLGMoYSl9KSxheGUuYWRkUmVwb3J0ZXIoInYxIixmdW5jdGlvbihhLGIsYyl7InVzZSBzdHJpY3QiOyJmdW5jdGlvbiI9PXR5cGVvZiBiJiYoYz1iLGI9e30pO3ZhciBkPVkucHJvY2Vzc0FnZ3JlZ2F0ZShhLGIpO2QudmlvbGF0aW9ucy5mb3JFYWNoKGZ1bmN0aW9uKGEpe3JldHVybiBhLm5vZGVzLmZvckVhY2goZnVuY3Rpb24oYSl7YS5mYWlsdXJlU3VtbWFyeT1ZLmZhaWx1cmVTdW1tYXJ5KGEpfSl9KSxjKHt2aW9sYXRpb25zOmQudmlvbGF0aW9ucyxwYXNzZXM6ZC5wYXNzZXMsaW5jb21wbGV0ZTpkLmluY29tcGxldGUsaW5hcHBsaWNhYmxlOmQuaW5hcHBsaWNhYmxlLHRpbWVzdGFtcDpkLnRpbWVzdGFtcCx1cmw6ZC51cmx9KX0pLGF4ZS5hZGRSZXBvcnRlcigidjIiLGZ1bmN0aW9uKGEsYixjKXsidXNlIHN0cmljdCI7ImZ1bmN0aW9uIj09dHlwZW9mIGImJihjPWIsYj17fSk7dmFyIGQ9WS5wcm9jZXNzQWdncmVnYXRlKGEsYik7Yyh7dmlvbGF0aW9uczpkLnZpb2xhdGlvbnMscGFzc2VzOmQucGFzc2VzLGluY29tcGxldGU6ZC5pbmNvbXBsZXRlLGluYXBwbGljYWJsZTpkLmluYXBwbGljYWJsZSx0aW1lc3RhbXA6ZC50aW1lc3RhbXAsdXJsOmQudXJsfSl9LCEwKSxheGUudXRpbHMuYWdncmVnYXRlPWZ1bmN0aW9uKGEsYixjKXtiPWIuc2xpY2UoKSxjJiZiLnB1c2goYyk7dmFyIGQ9Yi5tYXAoZnVuY3Rpb24oYil7cmV0dXJuIGEuaW5kZXhPZihiKX0pLnNvcnQoKTtyZXR1cm4gYVtkLnBvcCgpXX07dmFyIGJhPVtdO2JhW2F4ZS5jb25zdGFudHMuUEFTU19QUklPXT0hMCxiYVtheGUuY29uc3RhbnRzLkNBTlRURUxMX1BSSU9dPW51bGwsYmFbYXhlLmNvbnN0YW50cy5GQUlMX1BSSU9dPSExO3ZhciBjYT1bImFueSIsImFsbCIsIm5vbmUiXTtheGUudXRpbHMuYWdncmVnYXRlQ2hlY2tzPWZ1bmN0aW9uKGEpe3ZhciBiPU9iamVjdC5hc3NpZ24oe30sYSk7QyhiLGZ1bmN0aW9uKGEsYil7dmFyIGM9YmEuaW5kZXhPZihhLnJlc3VsdCk7YS5wcmlvcml0eT1jIT09LTE/YzpheGUuY29uc3RhbnRzLkNBTlRURUxMX1BSSU8sIm5vbmUiPT09YiYmKGEucHJpb3JpdHk9NC1hLnByaW9yaXR5KX0pO3ZhciBjPUMoYixmdW5jdGlvbihhKXtyZXR1cm4gYS5wcmlvcml0eX0pO2IucHJpb3JpdHk9TWF0aC5tYXgoYy5hbGwucmVkdWNlKGZ1bmN0aW9uKGEsYil7cmV0dXJuIE1hdGgubWF4KGEsYil9LDApLGMubm9uZS5yZWR1Y2UoZnVuY3Rpb24oYSxiKXtyZXR1cm4gTWF0aC5tYXgoYSxiKX0sMCksYy5hbnkucmVkdWNlKGZ1bmN0aW9uKGEsYil7cmV0dXJuIE1hdGgubWluKGEsYil9LDQpJTQpO3ZhciBkPVtdO3JldHVybiBjYS5mb3JFYWNoKGZ1bmN0aW9uKGEpe2JbYV09YlthXS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuIGEucHJpb3JpdHk9PT1iLnByaW9yaXR5fSksYlthXS5mb3JFYWNoKGZ1bmN0aW9uKGEpe3JldHVybiBkLnB1c2goYS5pbXBhY3QpfSl9KSxiLnByaW9yaXR5PT09YXhlLmNvbnN0YW50cy5GQUlMX1BSSU8/Yi5pbXBhY3Q9YXhlLnV0aWxzLmFnZ3JlZ2F0ZShheGUuY29uc3RhbnRzLmltcGFjdCxkKTpiLmltcGFjdD1udWxsLEMoYixmdW5jdGlvbihhKXtkZWxldGUgYS5yZXN1bHQsZGVsZXRlIGEucHJpb3JpdHl9KSxiLnJlc3VsdD1heGUuY29uc3RhbnRzLnJlc3VsdHNbYi5wcmlvcml0eV0sZGVsZXRlIGIucHJpb3JpdHksYn0sYXhlLnV0aWxzLmFnZ3JlZ2F0ZVJlc3VsdD1mdW5jdGlvbihhKXt2YXIgYj17fTtyZXR1cm4gYXhlLmNvbnN0YW50cy5yZXN1bHRHcm91cHMuZm9yRWFjaChmdW5jdGlvbihhKXtyZXR1cm4gYlthXT1bXX0pLGEuZm9yRWFjaChmdW5jdGlvbihhKXthLmVycm9yP0QoYixhLGF4ZS5jb25zdGFudHMuQ0FOVFRFTExfR1JPVVApOmEucmVzdWx0PT09YXhlLmNvbnN0YW50cy5OQT9EKGIsYSxheGUuY29uc3RhbnRzLk5BX0dST1VQKTpheGUuY29uc3RhbnRzLnJlc3VsdEdyb3Vwcy5mb3JFYWNoKGZ1bmN0aW9uKGMpe0FycmF5LmlzQXJyYXkoYVtjXSkmJmFbY10ubGVuZ3RoPjAmJkQoYixhLGMpfSl9KSxifSxmdW5jdGlvbigpe2F4ZS51dGlscy5hZ2dyZWdhdGVSdWxlPWZ1bmN0aW9uKGEpe3ZhciBiPXt9O2E9YS5tYXAoZnVuY3Rpb24oYSl7aWYoYS5hbnkmJmEuYWxsJiZhLm5vbmUpcmV0dXJuIGF4ZS51dGlscy5hZ2dyZWdhdGVDaGVja3MoYSk7aWYoQXJyYXkuaXNBcnJheShhLm5vZGUpKXJldHVybiBheGUudXRpbHMuZmluYWxpemVSdWxlUmVzdWx0KGEpO3Rocm93IG5ldyBUeXBlRXJyb3IoIkludmFsaWQgUmVzdWx0IHR5cGUiKX0pO3ZhciBjPWEubWFwKGZ1bmN0aW9uKGEpe3JldHVybiBhLnJlc3VsdH0pO2IucmVzdWx0PWF4ZS51dGlscy5hZ2dyZWdhdGUoYXhlLmNvbnN0YW50cy5yZXN1bHRzLGMsYi5yZXN1bHQpLGF4ZS5jb25zdGFudHMucmVzdWx0R3JvdXBzLmZvckVhY2goZnVuY3Rpb24oYSl7cmV0dXJuIGJbYV09W119KSxhLmZvckVhY2goZnVuY3Rpb24oYSl7dmFyIGM9YXhlLmNvbnN0YW50cy5yZXN1bHRHcm91cE1hcFthLnJlc3VsdF07YltjXS5wdXNoKGEpfSk7dmFyIGQ9YXhlLmNvbnN0YW50cy5GQUlMX0dST1VQO2lmKGJbZF0ubGVuZ3RoPjApe3ZhciBlPWJbZF0ubWFwKGZ1bmN0aW9uKGEpe3JldHVybiBhLmltcGFjdH0pO2IuaW1wYWN0PWF4ZS51dGlscy5hZ2dyZWdhdGUoYXhlLmNvbnN0YW50cy5pbXBhY3QsZSl8fG51bGx9ZWxzZSBiLmltcGFjdD1udWxsO3JldHVybiBifX0oKSxheGUudXRpbHMuYXJlU3R5bGVzU2V0PUUsYXhlLnV0aWxzLmNoZWNrSGVscGVyPWZ1bmN0aW9uKGEsYixjKXsidXNlIHN0cmljdCI7cmV0dXJue2lzQXN5bmM6ITEsYXN5bmM6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5pc0FzeW5jPSEwLGZ1bmN0aW9uKGQpe2QgaW5zdGFuY2VvZiBFcnJvcj09ITE/KGEudmFsdWU9ZCxiKGEpKTpjKGQpfX0sZGF0YTpmdW5jdGlvbihiKXthLmRhdGE9Yn0scmVsYXRlZE5vZGVzOmZ1bmN0aW9uKGIpe2I9YiBpbnN0YW5jZW9mIE5vZGU/W2JdOmF4ZS51dGlscy50b0FycmF5KGIpLGEucmVsYXRlZE5vZGVzPWIubWFwKGZ1bmN0aW9uKGEpe3JldHVybiBuZXcgYXhlLnV0aWxzLkRxRWxlbWVudChhKX0pfX19O3ZhciBYPSJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJiJzeW1ib2wiPT10eXBlb2YgU3ltYm9sLml0ZXJhdG9yP2Z1bmN0aW9uKGEpe3JldHVybiB0eXBlb2YgYX06ZnVuY3Rpb24oYSl7cmV0dXJuIGEmJiJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJmEuY29uc3RydWN0b3I9PT1TeW1ib2wmJmEhPT1TeW1ib2wucHJvdG90eXBlPyJzeW1ib2wiOnR5cGVvZiBhfTtheGUudXRpbHMuY2xvbmU9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiLGMsZD1hO2lmKG51bGwhPT1hJiYib2JqZWN0Ij09PSgidW5kZWZpbmVkIj09dHlwZW9mIGE/InVuZGVmaW5lZCI6WChhKSkpaWYoQXJyYXkuaXNBcnJheShhKSlmb3IoZD1bXSxiPTAsYz1hLmxlbmd0aDtiPGM7YisrKWRbYl09YXhlLnV0aWxzLmNsb25lKGFbYl0pO2Vsc2V7ZD17fTtmb3IoYiBpbiBhKWRbYl09YXhlLnV0aWxzLmNsb25lKGFbYl0pfXJldHVybiBkfSxheGUudXRpbHMuc2VuZENvbW1hbmRUb0ZyYW1lPWZ1bmN0aW9uKGEsYixjLGQpeyJ1c2Ugc3RyaWN0Ijt2YXIgZT1hLmNvbnRlbnRXaW5kb3c7aWYoIWUpcmV0dXJuIGF4ZS5sb2coIkZyYW1lIGRvZXMgbm90IGhhdmUgYSBjb250ZW50IHdpbmRvdyIsYSksdm9pZCBjKG51bGwpO3ZhciBmPXNldFRpbWVvdXQoZnVuY3Rpb24oKXtmPXNldFRpbWVvdXQoZnVuY3Rpb24oKXt2YXIgZT1GKCJObyByZXNwb25zZSBmcm9tIGZyYW1lIixhKTtiLmRlYnVnP2QoZSk6KGF4ZS5sb2coZSksYyhudWxsKSl9LDApfSw1MDApO2F4ZS51dGlscy5yZXNwb25kYWJsZShlLCJheGUucGluZyIsbnVsbCx2b2lkIDAsZnVuY3Rpb24oKXtjbGVhclRpbWVvdXQoZiksZj1zZXRUaW1lb3V0KGZ1bmN0aW9uKCl7ZChGKCJBeGUgaW4gZnJhbWUgdGltZWQgb3V0IixhKSl9LDNlNCksYXhlLnV0aWxzLnJlc3BvbmRhYmxlKGUsImF4ZS5zdGFydCIsYix2b2lkIDAsZnVuY3Rpb24oYSl7Y2xlYXJUaW1lb3V0KGYpLGEgaW5zdGFuY2VvZiBFcnJvcj09ITE/YyhhKTpkKGEpfSl9KX0sYXhlLnV0aWxzLmNvbGxlY3RSZXN1bHRzRnJvbUZyYW1lcz1HLGF4ZS51dGlscy5jb250YWlucz1mdW5jdGlvbihhLGIpeyJ1c2Ugc3RyaWN0IjtyZXR1cm4iZnVuY3Rpb24iPT10eXBlb2YgYS5jb250YWlucz9hLmNvbnRhaW5zKGIpOiEhKDE2JmEuY29tcGFyZURvY3VtZW50UG9zaXRpb24oYikpfSxKLnByb3RvdHlwZS50b0pTT049ZnVuY3Rpb24oKXsidXNlIHN0cmljdCI7cmV0dXJue3NlbGVjdG9yOnRoaXMuc2VsZWN0b3Isc291cmNlOnRoaXMuc291cmNlLHhwYXRoOnRoaXMueHBhdGh9fSxKLmZyb21GcmFtZT1mdW5jdGlvbihhLGIpe3JldHVybiBhLnNlbGVjdG9yLnVuc2hpZnQoYi5zZWxlY3RvciksYS54cGF0aC51bnNoaWZ0KGIueHBhdGgpLG5ldyBheGUudXRpbHMuRHFFbGVtZW50KGIuZWxlbWVudCxhKX0sYXhlLnV0aWxzLkRxRWxlbWVudD1KLGF4ZS51dGlscy5tYXRjaGVzU2VsZWN0b3I9ZnVuY3Rpb24oKXsidXNlIHN0cmljdCI7ZnVuY3Rpb24gYShhKXt2YXIgYixjLGQ9YS5FbGVtZW50LnByb3RvdHlwZSxlPVsibWF0Y2hlcyIsIm1hdGNoZXNTZWxlY3RvciIsIm1vek1hdGNoZXNTZWxlY3RvciIsIndlYmtpdE1hdGNoZXNTZWxlY3RvciIsIm1zTWF0Y2hlc1NlbGVjdG9yIl0sZj1lLmxlbmd0aDtmb3IoYj0wO2I8ZjtiKyspaWYoYz1lW2JdLGRbY10pcmV0dXJuIGN9dmFyIGI7cmV0dXJuIGZ1bmN0aW9uKGMsZCl7cmV0dXJuIGImJmNbYl18fChiPWEoYy5vd25lckRvY3VtZW50LmRlZmF1bHRWaWV3KSksY1tiXShkKX19KCksYXhlLnV0aWxzLmVzY2FwZVNlbGVjdG9yPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijtmb3IodmFyIGIsYz1TdHJpbmcoYSksZD1jLmxlbmd0aCxlPS0xLGY9IiIsZz1jLmNoYXJDb2RlQXQoMCk7KytlPGQ7KXtpZihiPWMuY2hhckNvZGVBdChlKSwwPT1iKXRocm93IG5ldyBFcnJvcigiSU5WQUxJRF9DSEFSQUNURVJfRVJSIik7Zis9Yj49MSYmYjw9MzF8fGI+PTEyNyYmYjw9MTU5fHwwPT1lJiZiPj00OCYmYjw9NTd8fDE9PWUmJmI+PTQ4JiZiPD01NyYmNDU9PWc/IlxcIitiLnRvU3RyaW5nKDE2KSsiICI6KDEhPWV8fDQ1IT1ifHw0NSE9ZykmJihiPj0xMjh8fDQ1PT1ifHw5NT09Ynx8Yj49NDgmJmI8PTU3fHxiPj02NSYmYjw9OTB8fGI+PTk3JiZiPD0xMjIpP2MuY2hhckF0KGUpOiJcXCIrYy5jaGFyQXQoZSl9cmV0dXJuIGZ9LGF4ZS51dGlscy5leHRlbmRNZXRhRGF0YT1mdW5jdGlvbihhLGIpe09iamVjdC5hc3NpZ24oYSxiKSxPYmplY3Qua2V5cyhiKS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuImZ1bmN0aW9uIj09dHlwZW9mIGJbYV19KS5mb3JFYWNoKGZ1bmN0aW9uKGMpe2FbY109bnVsbDt0cnl7YVtjXT1iW2NdKGEpfWNhdGNoKGQpe319KX0sYXhlLnV0aWxzLmZpbmFsaXplUnVsZVJlc3VsdD1mdW5jdGlvbihhKXtyZXR1cm4gT2JqZWN0LmFzc2lnbihhLGF4ZS51dGlscy5hZ2dyZWdhdGVSdWxlKGEubm9kZXMpKSxkZWxldGUgYS5ub2RlcyxhfTt2YXIgWD0iZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiYic3ltYm9sIj09dHlwZW9mIFN5bWJvbC5pdGVyYXRvcj9mdW5jdGlvbihhKXtyZXR1cm4gdHlwZW9mIGF9OmZ1bmN0aW9uKGEpe3JldHVybiBhJiYiZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiZhLmNvbnN0cnVjdG9yPT09U3ltYm9sJiZhIT09U3ltYm9sLnByb3RvdHlwZT8ic3ltYm9sIjp0eXBlb2YgYX07YXhlLnV0aWxzLmZpbmRCeT1mdW5jdGlvbihhLGIsYyl7aWYoQXJyYXkuaXNBcnJheShhKSlyZXR1cm4gYS5maW5kKGZ1bmN0aW9uKGEpe3JldHVybiJvYmplY3QiPT09KCJ1bmRlZmluZWQiPT10eXBlb2YgYT8idW5kZWZpbmVkIjpYKGEpKSYmYVtiXT09PWN9KX0sYXhlLnV0aWxzLmdldEFsbENoZWNrcz1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7dmFyIGI9W107cmV0dXJuIGIuY29uY2F0KGEuYW55fHxbXSkuY29uY2F0KGEuYWxsfHxbXSkuY29uY2F0KGEubm9uZXx8W10pfSxheGUudXRpbHMuZ2V0Q2hlY2tPcHRpb249ZnVuY3Rpb24oYSxiLGMpeyJ1c2Ugc3RyaWN0Ijt2YXIgZD0oKGMucnVsZXMmJmMucnVsZXNbYl18fHt9KS5jaGVja3N8fHt9KVthLmlkXSxlPShjLmNoZWNrc3x8e30pW2EuaWRdLGY9YS5lbmFibGVkLGc9YS5vcHRpb25zO3JldHVybiBlJiYoZS5oYXNPd25Qcm9wZXJ0eSgiZW5hYmxlZCIpJiYoZj1lLmVuYWJsZWQpLGUuaGFzT3duUHJvcGVydHkoIm9wdGlvbnMiKSYmKGc9ZS5vcHRpb25zKSksZCYmKGQuaGFzT3duUHJvcGVydHkoImVuYWJsZWQiKSYmKGY9ZC5lbmFibGVkKSxkLmhhc093blByb3BlcnR5KCJvcHRpb25zIikmJihnPWQub3B0aW9ucykpLHtlbmFibGVkOmYsb3B0aW9uczpnfX0sYXhlLnV0aWxzLmdldFNlbGVjdG9yPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiBiKGEpe3JldHVybiBheGUudXRpbHMuZXNjYXBlU2VsZWN0b3IoYSl9Zm9yKHZhciBjLGQ9W107YS5wYXJlbnROb2RlOyl7aWYoYz0iIixhLmlkJiYxPT09ZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgiIyIrYXhlLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEuaWQpKS5sZW5ndGgpe2QudW5zaGlmdCgiIyIrYXhlLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEuaWQpKTticmVha31pZihhLmNsYXNzTmFtZSYmInN0cmluZyI9PXR5cGVvZiBhLmNsYXNzTmFtZSYmKGM9Ii4iK2EuY2xhc3NOYW1lLnRyaW0oKS5zcGxpdCgvXHMrLykubWFwKGIpLmpvaW4oIi4iKSwoIi4iPT09Y3x8TChhLGMpKSYmKGM9IiIpKSwhYyl7aWYoYz1heGUudXRpbHMuZXNjYXBlU2VsZWN0b3IoYS5ub2RlTmFtZSkudG9Mb3dlckNhc2UoKSwiaHRtbCI9PT1jfHwiYm9keSI9PT1jKXtkLnVuc2hpZnQoYyk7YnJlYWt9TChhLGMpJiYoYys9IjpudGgtb2YtdHlwZSgiK0soYSkrIikiKX1kLnVuc2hpZnQoYyksYT1hLnBhcmVudE5vZGV9cmV0dXJuIGQuam9pbigiID4gIil9LGF4ZS51dGlscy5nZXRYcGF0aD1mdW5jdGlvbihhKXt2YXIgYj1NKGEpO3JldHVybiBOKGIpfTt2YXIgZGE7YXhlLnV0aWxzLmluamVjdFN0eWxlPU8sYXhlLnV0aWxzLmlzSGlkZGVuPWZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO2lmKDk9PT1hLm5vZGVUeXBlKXJldHVybiExO3ZhciBjPXdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGEsbnVsbCk7cmV0dXJuIWN8fCFhLnBhcmVudE5vZGV8fCJub25lIj09PWMuZ2V0UHJvcGVydHlWYWx1ZSgiZGlzcGxheSIpfHwhYiYmImhpZGRlbiI9PT1jLmdldFByb3BlcnR5VmFsdWUoInZpc2liaWxpdHkiKXx8InRydWUiPT09YS5nZXRBdHRyaWJ1dGUoImFyaWEtaGlkZGVuIil8fGF4ZS51dGlscy5pc0hpZGRlbihhLnBhcmVudE5vZGUsITApfSxheGUudXRpbHMubWVyZ2VSZXN1bHRzPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1bXTtyZXR1cm4gYS5mb3JFYWNoKGZ1bmN0aW9uKGEpe3ZhciBjPVIoYSk7YyYmYy5sZW5ndGgmJmMuZm9yRWFjaChmdW5jdGlvbihjKXtjLm5vZGVzJiZhLmZyYW1lJiZQKGMubm9kZXMsYS5mcmFtZUVsZW1lbnQsYS5mcmFtZSk7dmFyIGQ9YXhlLnV0aWxzLmZpbmRCeShiLCJpZCIsYy5pZCk7ZD9jLm5vZGVzLmxlbmd0aCYmUShkLm5vZGVzLGMubm9kZXMpOmIucHVzaChjKX0pfSksYn0sYXhlLnV0aWxzLm5vZGVTb3J0ZXI9ZnVuY3Rpb24oYSxiKXsidXNlIHN0cmljdCI7cmV0dXJuIGE9PT1iPzA6NCZhLmNvbXBhcmVEb2N1bWVudFBvc2l0aW9uKGIpPy0xOjF9LCJmdW5jdGlvbiIhPXR5cGVvZiBPYmplY3QuYXNzaWduJiYhZnVuY3Rpb24oKXtPYmplY3QuYXNzaWduPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtpZih2b2lkIDA9PT1hfHxudWxsPT09YSl0aHJvdyBuZXcgVHlwZUVycm9yKCJDYW5ub3QgY29udmVydCB1bmRlZmluZWQgb3IgbnVsbCB0byBvYmplY3QiKTtmb3IodmFyIGI9T2JqZWN0KGEpLGM9MTtjPGFyZ3VtZW50cy5sZW5ndGg7YysrKXt2YXIgZD1hcmd1bWVudHNbY107aWYodm9pZCAwIT09ZCYmbnVsbCE9PWQpZm9yKHZhciBlIGluIGQpZC5oYXNPd25Qcm9wZXJ0eShlKSYmKGJbZV09ZFtlXSl9cmV0dXJuIGJ9fSgpLEFycmF5LnByb3RvdHlwZS5maW5kfHwoQXJyYXkucHJvdG90eXBlLmZpbmQ9ZnVuY3Rpb24oYSl7aWYobnVsbD09PXRoaXMpdGhyb3cgbmV3IFR5cGVFcnJvcigiQXJyYXkucHJvdG90eXBlLmZpbmQgY2FsbGVkIG9uIG51bGwgb3IgdW5kZWZpbmVkIik7aWYoImZ1bmN0aW9uIiE9dHlwZW9mIGEpdGhyb3cgbmV3IFR5cGVFcnJvcigicHJlZGljYXRlIG11c3QgYmUgYSBmdW5jdGlvbiIpOwpmb3IodmFyIGIsYz1PYmplY3QodGhpcyksZD1jLmxlbmd0aD4+PjAsZT1hcmd1bWVudHNbMV0sZj0wO2Y8ZDtmKyspaWYoYj1jW2ZdLGEuY2FsbChlLGIsZixjKSlyZXR1cm4gYn0pLGF4ZS51dGlscy5wb2xseWZpbGxFbGVtZW50c0Zyb21Qb2ludD1mdW5jdGlvbigpe2lmKGRvY3VtZW50LmVsZW1lbnRzRnJvbVBvaW50KXJldHVybiBkb2N1bWVudC5lbGVtZW50c0Zyb21Qb2ludDtpZihkb2N1bWVudC5tc0VsZW1lbnRzRnJvbVBvaW50KXJldHVybiBkb2N1bWVudC5tc0VsZW1lbnRzRnJvbVBvaW50O3ZhciBhPWZ1bmN0aW9uKCl7dmFyIGE9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgieCIpO3JldHVybiBhLnN0eWxlLmNzc1RleHQ9InBvaW50ZXItZXZlbnRzOmF1dG8iLCJhdXRvIj09PWEuc3R5bGUucG9pbnRlckV2ZW50c30oKSxiPWE/InBvaW50ZXItZXZlbnRzIjoidmlzaWJpbGl0eSIsYz1hPyJub25lIjoiaGlkZGVuIixkPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInN0eWxlIik7cmV0dXJuIGQuaW5uZXJIVE1MPWE/IiogeyBwb2ludGVyLWV2ZW50czogYWxsIH0iOiIqIHsgdmlzaWJpbGl0eTogdmlzaWJsZSB9IixmdW5jdGlvbihhLGUpe3ZhciBmLGcsaCxpPVtdLGo9W107Zm9yKGRvY3VtZW50LmhlYWQuYXBwZW5kQ2hpbGQoZCk7KGY9ZG9jdW1lbnQuZWxlbWVudEZyb21Qb2ludChhLGUpKSYmaS5pbmRleE9mKGYpPT09LTE7KWkucHVzaChmKSxqLnB1c2goe3ZhbHVlOmYuc3R5bGUuZ2V0UHJvcGVydHlWYWx1ZShiKSxwcmlvcml0eTpmLnN0eWxlLmdldFByb3BlcnR5UHJpb3JpdHkoYil9KSxmLnN0eWxlLnNldFByb3BlcnR5KGIsYywiaW1wb3J0YW50Iik7Zm9yKGc9ai5sZW5ndGg7aD1qWy0tZ107KWlbZ10uc3R5bGUuc2V0UHJvcGVydHkoYixoLnZhbHVlP2gudmFsdWU6IiIsaC5wcmlvcml0eSk7cmV0dXJuIGRvY3VtZW50LmhlYWQucmVtb3ZlQ2hpbGQoZCksaX19LCJmdW5jdGlvbiI9PXR5cGVvZiB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lciYmKGRvY3VtZW50LmVsZW1lbnRzRnJvbVBvaW50PWF4ZS51dGlscy5wb2xseWZpbGxFbGVtZW50c0Zyb21Qb2ludCgpKSxBcnJheS5wcm90b3R5cGUuaW5jbHVkZXN8fChBcnJheS5wcm90b3R5cGUuaW5jbHVkZXM9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPU9iamVjdCh0aGlzKSxjPXBhcnNlSW50KGIubGVuZ3RoLDEwKXx8MDtpZigwPT09YylyZXR1cm4hMTt2YXIgZCxlPXBhcnNlSW50KGFyZ3VtZW50c1sxXSwxMCl8fDA7ZT49MD9kPWU6KGQ9YytlLGQ8MCYmKGQ9MCkpO2Zvcih2YXIgZjtkPGM7KXtpZihmPWJbZF0sYT09PWZ8fGEhPT1hJiZmIT09ZilyZXR1cm4hMDtkKyt9cmV0dXJuITF9KSxBcnJheS5wcm90b3R5cGUuc29tZXx8KEFycmF5LnByb3RvdHlwZS5zb21lPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtpZihudWxsPT10aGlzKXRocm93IG5ldyBUeXBlRXJyb3IoIkFycmF5LnByb3RvdHlwZS5zb21lIGNhbGxlZCBvbiBudWxsIG9yIHVuZGVmaW5lZCIpO2lmKCJmdW5jdGlvbiIhPXR5cGVvZiBhKXRocm93IG5ldyBUeXBlRXJyb3I7Zm9yKHZhciBiPU9iamVjdCh0aGlzKSxjPWIubGVuZ3RoPj4+MCxkPWFyZ3VtZW50cy5sZW5ndGg+PTI/YXJndW1lbnRzWzFdOnZvaWQgMCxlPTA7ZTxjO2UrKylpZihlIGluIGImJmEuY2FsbChkLGJbZV0sZSxiKSlyZXR1cm4hMDtyZXR1cm4hMX0pLGF4ZS51dGlscy5wdWJsaXNoTWV0YURhdGE9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPWF4ZS5fYXVkaXQuZGF0YS5jaGVja3N8fHt9LGM9YXhlLl9hdWRpdC5kYXRhLnJ1bGVzfHx7fSxkPWF4ZS51dGlscy5maW5kQnkoYXhlLl9hdWRpdC5ydWxlcywiaWQiLGEuaWQpfHx7fTthLnRhZ3M9YXhlLnV0aWxzLmNsb25lKGQudGFnc3x8W10pO3ZhciBlPVMoYiwhMCksZj1TKGIsITEpO2Eubm9kZXMuZm9yRWFjaChmdW5jdGlvbihhKXthLmFueS5mb3JFYWNoKGUpLGEuYWxsLmZvckVhY2goZSksYS5ub25lLmZvckVhY2goZil9KSxheGUudXRpbHMuZXh0ZW5kTWV0YURhdGEoYSxheGUudXRpbHMuY2xvbmUoY1thLmlkXXx8e30pKX07dmFyIFg9ImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmInN5bWJvbCI9PXR5cGVvZiBTeW1ib2wuaXRlcmF0b3I/ZnVuY3Rpb24oYSl7cmV0dXJuIHR5cGVvZiBhfTpmdW5jdGlvbihhKXtyZXR1cm4gYSYmImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmYS5jb25zdHJ1Y3Rvcj09PVN5bWJvbCYmYSE9PVN5bWJvbC5wcm90b3R5cGU/InN5bWJvbCI6dHlwZW9mIGF9OyFmdW5jdGlvbigpeyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiBhKCl7fWZ1bmN0aW9uIGIoYSl7aWYoImZ1bmN0aW9uIiE9dHlwZW9mIGEpdGhyb3cgbmV3IFR5cGVFcnJvcigiUXVldWUgbWV0aG9kcyByZXF1aXJlIGZ1bmN0aW9ucyBhcyBhcmd1bWVudHMiKX1mdW5jdGlvbiBjKCl7ZnVuY3Rpb24gYyhiKXtyZXR1cm4gZnVuY3Rpb24oYyl7Z1tiXT1jLGktPTEsaXx8aj09PWF8fChrPSEwLGooZykpfX1mdW5jdGlvbiBkKGIpe3JldHVybiBqPWEsbShiKSxnfWZ1bmN0aW9uIGUoKXtmb3IodmFyIGE9Zy5sZW5ndGg7aDxhO2grKyl7dmFyIGI9Z1toXTt0cnl7Yi5jYWxsKG51bGwsYyhoKSxkKX1jYXRjaChlKXtkKGUpfX19dmFyIGYsZz1bXSxoPTAsaT0wLGo9YSxrPSExLGw9ZnVuY3Rpb24oYSl7Zj1hLHNldFRpbWVvdXQoZnVuY3Rpb24oKXt2b2lkIDAhPT1mJiZudWxsIT09ZiYmYXhlLmxvZygiVW5jYXVnaHQgZXJyb3IgKG9mIHF1ZXVlKSIsZil9LDEpfSxtPWwsbj17ZGVmZXI6ZnVuY3Rpb24gbyhhKXtpZigib2JqZWN0Ij09PSgidW5kZWZpbmVkIj09dHlwZW9mIGE/InVuZGVmaW5lZCI6WChhKSkmJmEudGhlbiYmYVsiY2F0Y2giXSl7dmFyIG89YTthPWZ1bmN0aW9uKGEsYil7by50aGVuKGEpWyJjYXRjaCJdKGIpfX1pZihiKGEpLHZvaWQgMD09PWYpe2lmKGspdGhyb3cgbmV3IEVycm9yKCJRdWV1ZSBhbHJlYWR5IGNvbXBsZXRlZCIpO3JldHVybiBnLnB1c2goYSksKytpLGUoKSxufX0sdGhlbjpmdW5jdGlvbihjKXtpZihiKGMpLGohPT1hKXRocm93IG5ldyBFcnJvcigicXVldWUgYHRoZW5gIGFscmVhZHkgc2V0Iik7cmV0dXJuIGZ8fChqPWMsaXx8KGs9ITAsaihnKSkpLG59LCJjYXRjaCI6ZnVuY3Rpb24oYSl7aWYoYihhKSxtIT09bCl0aHJvdyBuZXcgRXJyb3IoInF1ZXVlIGBjYXRjaGAgYWxyZWFkeSBzZXQiKTtyZXR1cm4gZj8oYShmKSxmPW51bGwpOm09YSxufSxhYm9ydDpkfTtyZXR1cm4gbn1heGUudXRpbHMucXVldWU9Y30oKTt2YXIgWD0iZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiYic3ltYm9sIj09dHlwZW9mIFN5bWJvbC5pdGVyYXRvcj9mdW5jdGlvbihhKXtyZXR1cm4gdHlwZW9mIGF9OmZ1bmN0aW9uKGEpe3JldHVybiBhJiYiZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiZhLmNvbnN0cnVjdG9yPT09U3ltYm9sJiZhIT09U3ltYm9sLnByb3RvdHlwZT8ic3ltYm9sIjp0eXBlb2YgYX07IWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiBiKCl7dmFyIGEsYj0iYXhlIixjPSIiO3JldHVybiJ1bmRlZmluZWQiIT10eXBlb2YgYXhlJiZheGUuX2F1ZGl0JiYhYXhlLl9hdWRpdC5hcHBsaWNhdGlvbiYmKGI9YXhlLl9hdWRpdC5hcHBsaWNhdGlvbiksInVuZGVmaW5lZCIhPXR5cGVvZiBheGUmJihjPWF4ZS52ZXJzaW9uKSxhPWIrIi4iK2N9ZnVuY3Rpb24gYyhhKXtpZigib2JqZWN0Ij09PSgidW5kZWZpbmVkIj09dHlwZW9mIGE/InVuZGVmaW5lZCI6WChhKSkmJiJzdHJpbmciPT10eXBlb2YgYS51dWlkJiZhLl9yZXNwb25kYWJsZT09PSEwKXt2YXIgYz1iKCk7cmV0dXJuIGEuX3NvdXJjZT09PWN8fCJheGUueC55LnoiPT09YS5fc291cmNlfHwiYXhlLngueS56Ij09PWN9cmV0dXJuITF9ZnVuY3Rpb24gZChhLGMsZCxlLGYsZyl7dmFyIGg7ZCBpbnN0YW5jZW9mIEVycm9yJiYoaD17bmFtZTpkLm5hbWUsbWVzc2FnZTpkLm1lc3NhZ2Usc3RhY2s6ZC5zdGFja30sZD12b2lkIDApO3ZhciBpPXt1dWlkOmUsdG9waWM6YyxtZXNzYWdlOmQsZXJyb3I6aCxfcmVzcG9uZGFibGU6ITAsX3NvdXJjZTpiKCksX2tlZXBhbGl2ZTpmfTsiZnVuY3Rpb24iPT10eXBlb2YgZyYmKGpbZV09ZyksYS5wb3N0TWVzc2FnZShKU09OLnN0cmluZ2lmeShpKSwiKiIpfWZ1bmN0aW9uIGUoYSxiLGMsZSxmKXt2YXIgZz1lYS52MSgpO2QoYSxiLGMsZyxlLGYpfWZ1bmN0aW9uIGYoYSxiLGMpe3JldHVybiBmdW5jdGlvbihlLGYsZyl7ZChhLGIsZSxjLGYsZyl9fWZ1bmN0aW9uIGcoYSxiLGMpe3ZhciBkPWIudG9waWMsZT1rW2RdO2lmKGUpe3ZhciBnPWYoYSxudWxsLGIudXVpZCk7ZShiLm1lc3NhZ2UsYyxnKX19ZnVuY3Rpb24gaChhKXt2YXIgYj1hLm1lc3NhZ2V8fCJVbmtub3duIGVycm9yIG9jY3VycmVkIixjPXdpbmRvd1thLm5hbWVdfHxFcnJvcjtyZXR1cm4gYS5zdGFjayYmKGIrPSJcbiIrYS5zdGFjay5yZXBsYWNlKGEubWVzc2FnZSwiIikpLG5ldyBjKGIpfWZ1bmN0aW9uIGkoYSl7dmFyIGI7aWYoInN0cmluZyI9PXR5cGVvZiBhKXt0cnl7Yj1KU09OLnBhcnNlKGEpfWNhdGNoKGQpe31pZihjKGIpKXJldHVybiJvYmplY3QiPT09WChiLmVycm9yKT9iLmVycm9yPWgoYi5lcnJvcik6Yi5lcnJvcj12b2lkIDAsYn19dmFyIGo9e30saz17fTtlLnN1YnNjcmliZT1mdW5jdGlvbihhLGIpe2tbYV09Yn0sZS5pc0luRnJhbWU9ZnVuY3Rpb24oYSl7cmV0dXJuIGE9YXx8d2luZG93LCEhYS5mcmFtZUVsZW1lbnR9LCJmdW5jdGlvbiI9PXR5cGVvZiB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lciYmd2luZG93LmFkZEV2ZW50TGlzdGVuZXIoIm1lc3NhZ2UiLGZ1bmN0aW9uKGEpe3ZhciBiPWkoYS5kYXRhKTtpZihiKXt2YXIgYz1iLnV1aWQsZT1iLl9rZWVwYWxpdmUsaD1qW2NdO2lmKGgpe3ZhciBrPWIuZXJyb3J8fGIubWVzc2FnZSxsPWYoYS5zb3VyY2UsYi50b3BpYyxjKTtoKGssZSxsKSxlfHxkZWxldGUgaltjXX1pZighYi5lcnJvcil0cnl7ZyhhLnNvdXJjZSxiLGUpfWNhdGNoKG0pe2QoYS5zb3VyY2UsYi50b3BpYyxtLGMsITEpfX19LCExKSxhLnJlc3BvbmRhYmxlPWV9KHV0aWxzKSxheGUudXRpbHMucnVsZVNob3VsZFJ1bj1mdW5jdGlvbihhLGIsYyl7InVzZSBzdHJpY3QiO3ZhciBkPWMucnVuT25seXx8e30sZT0oYy5ydWxlc3x8e30pW2EuaWRdO3JldHVybiEoYS5wYWdlTGV2ZWwmJiFiLnBhZ2UpJiYoInJ1bGUiPT09ZC50eXBlP2QudmFsdWVzLmluZGV4T2YoYS5pZCkhPT0tMTplJiYiYm9vbGVhbiI9PXR5cGVvZiBlLmVuYWJsZWQ/ZS5lbmFibGVkOiJ0YWciPT09ZC50eXBlJiZkLnZhbHVlcz9UKGEsZC52YWx1ZXMpOlQoYSxbXSkpfSxheGUudXRpbHMuc2VsZWN0PWZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO2Zvcih2YXIgYyxkPVtdLGU9MCxmPWIuaW5jbHVkZS5sZW5ndGg7ZTxmO2UrKyljPWIuaW5jbHVkZVtlXSxjLm5vZGVUeXBlPT09Yy5FTEVNRU5UX05PREUmJmF4ZS51dGlscy5tYXRjaGVzU2VsZWN0b3IoYyxhKSYmVyhkLFtjXSxiKSxXKGQsYy5xdWVyeVNlbGVjdG9yQWxsKGEpLGIpO3JldHVybiBkLnNvcnQoYXhlLnV0aWxzLm5vZGVTb3J0ZXIpfSxheGUudXRpbHMudG9BcnJheT1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7cmV0dXJuIEFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKGEpfTt2YXIgZWE7IWZ1bmN0aW9uKGEpe2Z1bmN0aW9uIGIoYSxiLGMpe3ZhciBkPWImJmN8fDAsZT0wO2ZvcihiPWJ8fFtdLGEudG9Mb3dlckNhc2UoKS5yZXBsYWNlKC9bMC05YS1mXXsyfS9nLGZ1bmN0aW9uKGEpe2U8MTYmJihiW2QrZSsrXT1sW2FdKX0pO2U8MTY7KWJbZCtlKytdPTA7cmV0dXJuIGJ9ZnVuY3Rpb24gYyhhLGIpe3ZhciBjPWJ8fDAsZD1rO3JldHVybiBkW2FbYysrXV0rZFthW2MrK11dK2RbYVtjKytdXStkW2FbYysrXV0rIi0iK2RbYVtjKytdXStkW2FbYysrXV0rIi0iK2RbYVtjKytdXStkW2FbYysrXV0rIi0iK2RbYVtjKytdXStkW2FbYysrXV0rIi0iK2RbYVtjKytdXStkW2FbYysrXV0rZFthW2MrK11dK2RbYVtjKytdXStkW2FbYysrXV0rZFthW2MrK11dfWZ1bmN0aW9uIGQoYSxiLGQpe3ZhciBlPWImJmR8fDAsZj1ifHxbXTthPWF8fHt9O3ZhciBnPW51bGwhPWEuY2xvY2tzZXE/YS5jbG9ja3NlcTpwLGg9bnVsbCE9YS5tc2Vjcz9hLm1zZWNzOihuZXcgRGF0ZSkuZ2V0VGltZSgpLGk9bnVsbCE9YS5uc2Vjcz9hLm5zZWNzOnIrMSxqPWgtcSsoaS1yKS8xZTQ7aWYoajwwJiZudWxsPT1hLmNsb2Nrc2VxJiYoZz1nKzEmMTYzODMpLChqPDB8fGg+cSkmJm51bGw9PWEubnNlY3MmJihpPTApLGk+PTFlNCl0aHJvdyBuZXcgRXJyb3IoInV1aWQudjEoKTogQ2FuJ3QgY3JlYXRlIG1vcmUgdGhhbiAxME0gdXVpZHMvc2VjIik7cT1oLHI9aSxwPWcsaCs9MTIyMTkyOTI4ZTU7dmFyIGs9KDFlNCooMjY4NDM1NDU1JmgpK2kpJTQyOTQ5NjcyOTY7ZltlKytdPWs+Pj4yNCYyNTUsZltlKytdPWs+Pj4xNiYyNTUsZltlKytdPWs+Pj44JjI1NSxmW2UrK109MjU1Jms7dmFyIGw9aC80Mjk0OTY3Mjk2KjFlNCYyNjg0MzU0NTU7ZltlKytdPWw+Pj44JjI1NSxmW2UrK109MjU1JmwsZltlKytdPWw+Pj4yNCYxNXwxNixmW2UrK109bD4+PjE2JjI1NSxmW2UrK109Zz4+Pjh8MTI4LGZbZSsrXT0yNTUmZztmb3IodmFyIG09YS5ub2RlfHxvLG49MDtuPDY7bisrKWZbZStuXT1tW25dO3JldHVybiBiP2I6YyhmKX1mdW5jdGlvbiBlKGEsYixkKXt2YXIgZT1iJiZkfHwwOyJzdHJpbmciPT10eXBlb2YgYSYmKGI9ImJpbmFyeSI9PWE/bmV3IGooMTYpOm51bGwsYT1udWxsKSxhPWF8fHt9O3ZhciBnPWEucmFuZG9tfHwoYS5ybmd8fGYpKCk7aWYoZ1s2XT0xNSZnWzZdfDY0LGdbOF09NjMmZ1s4XXwxMjgsYilmb3IodmFyIGg9MDtoPDE2O2grKyliW2UraF09Z1toXTtyZXR1cm4gYnx8YyhnKX12YXIgZixnPWEuY3J5cHRvfHxhLm1zQ3J5cHRvO2lmKCFmJiZnJiZnLmdldFJhbmRvbVZhbHVlcyl7dmFyIGg9bmV3IFVpbnQ4QXJyYXkoMTYpO2Y9ZnVuY3Rpb24oKXtyZXR1cm4gZy5nZXRSYW5kb21WYWx1ZXMoaCksaH19aWYoIWYpe3ZhciBpPW5ldyBBcnJheSgxNik7Zj1mdW5jdGlvbigpe2Zvcih2YXIgYSxiPTA7YjwxNjtiKyspMD09PSgzJmIpJiYoYT00Mjk0OTY3Mjk2Kk1hdGgucmFuZG9tKCkpLGlbYl09YT4+PigoMyZiKTw8MykmMjU1O3JldHVybiBpfX1mb3IodmFyIGo9ImZ1bmN0aW9uIj09dHlwZW9mIGEuQnVmZmVyP2EuQnVmZmVyOkFycmF5LGs9W10sbD17fSxtPTA7bTwyNTY7bSsrKWtbbV09KG0rMjU2KS50b1N0cmluZygxNikuc3Vic3RyKDEpLGxba1ttXV09bTt2YXIgbj1mKCksbz1bMXxuWzBdLG5bMV0sblsyXSxuWzNdLG5bNF0sbls1XV0scD0xNjM4MyYobls2XTw8OHxuWzddKSxxPTAscj0wO2VhPWUsZWEudjE9ZCxlYS52ND1lLGVhLnBhcnNlPWIsZWEudW5wYXJzZT1jLGVhLkJ1ZmZlckNsYXNzPWp9KHdpbmRvdyksYXhlLl9sb2FkKHtkYXRhOntydWxlczp7YWNjZXNza2V5czp7ZGVzY3JpcHRpb246IkVuc3VyZXMgZXZlcnkgYWNjZXNza2V5IGF0dHJpYnV0ZSB2YWx1ZSBpcyB1bmlxdWUiLGhlbHA6ImFjY2Vzc2tleSBhdHRyaWJ1dGUgdmFsdWUgbXVzdCBiZSB1bmlxdWUifSwiYXJlYS1hbHQiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyA8YXJlYT4gZWxlbWVudHMgb2YgaW1hZ2UgbWFwcyBoYXZlIGFsdGVybmF0ZSB0ZXh0IixoZWxwOiJBY3RpdmUgPGFyZWE+IGVsZW1lbnRzIG11c3QgaGF2ZSBhbHRlcm5hdGUgdGV4dCJ9LCJhcmlhLWFsbG93ZWQtYXR0ciI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIEFSSUEgYXR0cmlidXRlcyBhcmUgYWxsb3dlZCBmb3IgYW4gZWxlbWVudCdzIHJvbGUiLGhlbHA6IkVsZW1lbnRzIG11c3Qgb25seSB1c2UgYWxsb3dlZCBBUklBIGF0dHJpYnV0ZXMifSwiYXJpYS1yZXF1aXJlZC1hdHRyIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgZWxlbWVudHMgd2l0aCBBUklBIHJvbGVzIGhhdmUgYWxsIHJlcXVpcmVkIEFSSUEgYXR0cmlidXRlcyIsaGVscDoiUmVxdWlyZWQgQVJJQSBhdHRyaWJ1dGVzIG11c3QgYmUgcHJvdmlkZWQifSwiYXJpYS1yZXF1aXJlZC1jaGlsZHJlbiI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGVsZW1lbnRzIHdpdGggYW4gQVJJQSByb2xlIHRoYXQgcmVxdWlyZSBjaGlsZCByb2xlcyBjb250YWluIHRoZW0iLGhlbHA6IkNlcnRhaW4gQVJJQSByb2xlcyBtdXN0IGNvbnRhaW4gcGFydGljdWxhciBjaGlsZHJlbiJ9LCJhcmlhLXJlcXVpcmVkLXBhcmVudCI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGVsZW1lbnRzIHdpdGggYW4gQVJJQSByb2xlIHRoYXQgcmVxdWlyZSBwYXJlbnQgcm9sZXMgYXJlIGNvbnRhaW5lZCBieSB0aGVtIixoZWxwOiJDZXJ0YWluIEFSSUEgcm9sZXMgbXVzdCBiZSBjb250YWluZWQgYnkgcGFydGljdWxhciBwYXJlbnRzIn0sImFyaWEtcm9sZXMiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBhbGwgZWxlbWVudHMgd2l0aCBhIHJvbGUgYXR0cmlidXRlIHVzZSBhIHZhbGlkIHZhbHVlIixoZWxwOiJBUklBIHJvbGVzIHVzZWQgbXVzdCBjb25mb3JtIHRvIHZhbGlkIHZhbHVlcyJ9LCJhcmlhLXZhbGlkLWF0dHItdmFsdWUiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBhbGwgQVJJQSBhdHRyaWJ1dGVzIGhhdmUgdmFsaWQgdmFsdWVzIixoZWxwOiJBUklBIGF0dHJpYnV0ZXMgbXVzdCBjb25mb3JtIHRvIHZhbGlkIHZhbHVlcyJ9LCJhcmlhLXZhbGlkLWF0dHIiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBhdHRyaWJ1dGVzIHRoYXQgYmVnaW4gd2l0aCBhcmlhLSBhcmUgdmFsaWQgQVJJQSBhdHRyaWJ1dGVzIixoZWxwOiJBUklBIGF0dHJpYnV0ZXMgbXVzdCBjb25mb3JtIHRvIHZhbGlkIG5hbWVzIn0sImF1ZGlvLWNhcHRpb24iOntkZXNjcmlwdGlvbjoiRW5zdXJlcyA8YXVkaW8+IGVsZW1lbnRzIGhhdmUgY2FwdGlvbnMiLGhlbHA6IjxhdWRpbz4gZWxlbWVudHMgbXVzdCBoYXZlIGEgY2FwdGlvbnMgdHJhY2sifSxibGluazp7ZGVzY3JpcHRpb246IkVuc3VyZXMgPGJsaW5rPiBlbGVtZW50cyBhcmUgbm90IHVzZWQiLGhlbHA6IjxibGluaz4gZWxlbWVudHMgYXJlIGRlcHJlY2F0ZWQgYW5kIG11c3Qgbm90IGJlIHVzZWQifSwiYnV0dG9uLW5hbWUiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBidXR0b25zIGhhdmUgZGlzY2VybmlibGUgdGV4dCIsaGVscDoiQnV0dG9ucyBtdXN0IGhhdmUgZGlzY2VybmlibGUgdGV4dCJ9LGJ5cGFzczp7ZGVzY3JpcHRpb246IkVuc3VyZXMgZWFjaCBwYWdlIGhhcyBhdCBsZWFzdCBvbmUgbWVjaGFuaXNtIGZvciBhIHVzZXIgdG8gYnlwYXNzIG5hdmlnYXRpb24gYW5kIGp1bXAgc3RyYWlnaHQgdG8gdGhlIGNvbnRlbnQiLGhlbHA6IlBhZ2UgbXVzdCBoYXZlIG1lYW5zIHRvIGJ5cGFzcyByZXBlYXRlZCBibG9ja3MifSxjaGVja2JveGdyb3VwOntkZXNjcmlwdGlvbjonRW5zdXJlcyByZWxhdGVkIDxpbnB1dCB0eXBlPSJjaGVja2JveCI+IGVsZW1lbnRzIGhhdmUgYSBncm91cCBhbmQgdGhhdCB0aGF0IGdyb3VwIGRlc2lnbmF0aW9uIGlzIGNvbnNpc3RlbnQnLGhlbHA6IkNoZWNrYm94IGlucHV0cyB3aXRoIHRoZSBzYW1lIG5hbWUgYXR0cmlidXRlIHZhbHVlIG11c3QgYmUgcGFydCBvZiBhIGdyb3VwIn0sImNvbG9yLWNvbnRyYXN0Ijp7ZGVzY3JpcHRpb246IkVuc3VyZXMgdGhlIGNvbnRyYXN0IGJldHdlZW4gZm9yZWdyb3VuZCBhbmQgYmFja2dyb3VuZCBjb2xvcnMgbWVldHMgV0NBRyAyIEFBIGNvbnRyYXN0IHJhdGlvIHRocmVzaG9sZHMiLGhlbHA6IkVsZW1lbnRzIG11c3QgaGF2ZSBzdWZmaWNpZW50IGNvbG9yIGNvbnRyYXN0In0sImRlZmluaXRpb24tbGlzdCI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxkbD4gZWxlbWVudHMgYXJlIHN0cnVjdHVyZWQgY29ycmVjdGx5IixoZWxwOiI8ZGw+IGVsZW1lbnRzIG11c3Qgb25seSBkaXJlY3RseSBjb250YWluIHByb3Blcmx5LW9yZGVyZWQgPGR0PiBhbmQgPGRkPiBncm91cHMsIDxzY3JpcHQ+IG9yIDx0ZW1wbGF0ZT4gZWxlbWVudHMifSxkbGl0ZW06e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxkdD4gYW5kIDxkZD4gZWxlbWVudHMgYXJlIGNvbnRhaW5lZCBieSBhIDxkbD4iLGhlbHA6IjxkdD4gYW5kIDxkZD4gZWxlbWVudHMgbXVzdCBiZSBjb250YWluZWQgYnkgYSA8ZGw+In0sImRvY3VtZW50LXRpdGxlIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgZWFjaCBIVE1MIGRvY3VtZW50IGNvbnRhaW5zIGEgbm9uLWVtcHR5IDx0aXRsZT4gZWxlbWVudCIsaGVscDoiRG9jdW1lbnRzIG11c3QgaGF2ZSA8dGl0bGU+IGVsZW1lbnQgdG8gYWlkIGluIG5hdmlnYXRpb24ifSwiZHVwbGljYXRlLWlkIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgZXZlcnkgaWQgYXR0cmlidXRlIHZhbHVlIGlzIHVuaXF1ZSIsaGVscDoiaWQgYXR0cmlidXRlIHZhbHVlIG11c3QgYmUgdW5pcXVlIn0sImVtcHR5LWhlYWRpbmciOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBoZWFkaW5ncyBoYXZlIGRpc2Nlcm5pYmxlIHRleHQiLGhlbHA6IkhlYWRpbmdzIG11c3Qgbm90IGJlIGVtcHR5In0sImZyYW1lLXRpdGxlLXVuaXF1ZSI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxpZnJhbWU+IGFuZCA8ZnJhbWU+IGVsZW1lbnRzIGNvbnRhaW4gYSB1bmlxdWUgdGl0bGUgYXR0cmlidXRlIixoZWxwOiJGcmFtZXMgbXVzdCBoYXZlIGEgdW5pcXVlIHRpdGxlIGF0dHJpYnV0ZSJ9LCJmcmFtZS10aXRsZSI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxpZnJhbWU+IGFuZCA8ZnJhbWU+IGVsZW1lbnRzIGNvbnRhaW4gYSBub24tZW1wdHkgdGl0bGUgYXR0cmlidXRlIixoZWxwOiJGcmFtZXMgbXVzdCBoYXZlIHRpdGxlIGF0dHJpYnV0ZSJ9LCJoZWFkaW5nLW9yZGVyIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgdGhlIG9yZGVyIG9mIGhlYWRpbmdzIGlzIHNlbWFudGljYWxseSBjb3JyZWN0IixoZWxwOiJIZWFkaW5nIGxldmVscyBzaG91bGQgb25seSBpbmNyZWFzZSBieSBvbmUifSwiaHJlZi1uby1oYXNoIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgdGhhdCBocmVmIHZhbHVlcyBhcmUgdmFsaWQgbGluayByZWZlcmVuY2VzIHRvIHByb21vdGUgb25seSB1c2luZyBhbmNob3JzIGFzIGxpbmtzIixoZWxwOiJBbmNob3JzIG11c3Qgb25seSBiZSB1c2VkIGFzIGxpbmtzIGFuZCBtdXN0IHRoZXJlZm9yZSBoYXZlIGFuIGhyZWYgdmFsdWUgdGhhdCBpcyBhIHZhbGlkIHJlZmVyZW5jZS4gT3RoZXJ3aXNlIHlvdSBzaG91bGQgcHJvYmFibHkgdXNhIGEgYnV0dG9uIn0sImh0bWwtaGFzLWxhbmciOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBldmVyeSBIVE1MIGRvY3VtZW50IGhhcyBhIGxhbmcgYXR0cmlidXRlIixoZWxwOiI8aHRtbD4gZWxlbWVudCBtdXN0IGhhdmUgYSBsYW5nIGF0dHJpYnV0ZSJ9LCJodG1sLWxhbmctdmFsaWQiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyB0aGUgbGFuZyBhdHRyaWJ1dGUgb2YgdGhlIDxodG1sPiBlbGVtZW50IGhhcyBhIHZhbGlkIHZhbHVlIixoZWxwOiI8aHRtbD4gZWxlbWVudCBtdXN0IGhhdmUgYSB2YWxpZCB2YWx1ZSBmb3IgdGhlIGxhbmcgYXR0cmlidXRlIn0sImltYWdlLWFsdCI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxpbWc+IGVsZW1lbnRzIGhhdmUgYWx0ZXJuYXRlIHRleHQgb3IgYSByb2xlIG9mIG5vbmUgb3IgcHJlc2VudGF0aW9uIixoZWxwOiJJbWFnZXMgbXVzdCBoYXZlIGFsdGVybmF0ZSB0ZXh0In0sImltYWdlLXJlZHVuZGFudC1hbHQiOntkZXNjcmlwdGlvbjoiRW5zdXJlIGJ1dHRvbiBhbmQgbGluayB0ZXh0IGlzIG5vdCByZXBlYXRlZCBhcyBpbWFnZSBhbHRlcm5hdGl2ZSIsaGVscDoiVGV4dCBvZiBidXR0b25zIGFuZCBsaW5rcyBzaG91bGQgbm90IGJlIHJlcGVhdGVkIGluIHRoZSBpbWFnZSBhbHRlcm5hdGl2ZSJ9LCJpbnB1dC1pbWFnZS1hbHQiOntkZXNjcmlwdGlvbjonRW5zdXJlcyA8aW5wdXQgdHlwZT0iaW1hZ2UiPiBlbGVtZW50cyBoYXZlIGFsdGVybmF0ZSB0ZXh0JyxoZWxwOiJJbWFnZSBidXR0b25zIG11c3QgaGF2ZSBhbHRlcm5hdGUgdGV4dCJ9LCJsYWJlbC10aXRsZS1vbmx5Ijp7ZGVzY3JpcHRpb246IkVuc3VyZXMgdGhhdCBldmVyeSBmb3JtIGVsZW1lbnQgaXMgbm90IHNvbGVseSBsYWJlbGVkIHVzaW5nIHRoZSB0aXRsZSBvciBhcmlhLWRlc2NyaWJlZGJ5IGF0dHJpYnV0ZXMiLGhlbHA6IkZvcm0gZWxlbWVudHMgc2hvdWxkIGhhdmUgYSB2aXNpYmxlIGxhYmVsIn0sbGFiZWw6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGV2ZXJ5IGZvcm0gZWxlbWVudCBoYXMgYSBsYWJlbCIsaGVscDoiRm9ybSBlbGVtZW50cyBtdXN0IGhhdmUgbGFiZWxzIn0sImxheW91dC10YWJsZSI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIHByZXNlbnRhdGlvbmFsIDx0YWJsZT4gZWxlbWVudHMgZG8gbm90IHVzZSA8dGg+LCA8Y2FwdGlvbj4gZWxlbWVudHMgb3IgdGhlIHN1bW1hcnkgYXR0cmlidXRlIixoZWxwOiJMYXlvdXQgdGFibGVzIG11c3Qgbm90IHVzZSBkYXRhIHRhYmxlIGVsZW1lbnRzIn0sImxpbmstaW4tdGV4dC1ibG9jayI6e2Rlc2NyaXB0aW9uOiJMaW5rcyBjYW4gYmUgZGlzdGluZ3Vpc2hlZCB3aXRob3V0IHJlbHlpbmcgb24gY29sb3IiLGhlbHA6IkxpbmtzIG11c3QgYmUgZGlzdGluZ3Vpc2hlZCBmcm9tIHN1cnJvdW5kaW5nIHRleHQgaW4gYSB3YXkgdGhhdCBkb2VzIG5vdCByZWx5IG9uIGNvbG9yIn0sImxpbmstbmFtZSI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGxpbmtzIGhhdmUgZGlzY2VybmlibGUgdGV4dCIsaGVscDoiTGlua3MgbXVzdCBoYXZlIGRpc2Nlcm5pYmxlIHRleHQifSxsaXN0OntkZXNjcmlwdGlvbjoiRW5zdXJlcyB0aGF0IGxpc3RzIGFyZSBzdHJ1Y3R1cmVkIGNvcnJlY3RseSIsaGVscDoiPHVsPiBhbmQgPG9sPiBtdXN0IG9ubHkgZGlyZWN0bHkgY29udGFpbiA8bGk+LCA8c2NyaXB0PiBvciA8dGVtcGxhdGU+IGVsZW1lbnRzIn0sbGlzdGl0ZW06e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxsaT4gZWxlbWVudHMgYXJlIHVzZWQgc2VtYW50aWNhbGx5IixoZWxwOiI8bGk+IGVsZW1lbnRzIG11c3QgYmUgY29udGFpbmVkIGluIGEgPHVsPiBvciA8b2w+In0sbWFycXVlZTp7ZGVzY3JpcHRpb246IkVuc3VyZXMgPG1hcnF1ZWU+IGVsZW1lbnRzIGFyZSBub3QgdXNlZCIsaGVscDoiPG1hcnF1ZWU+IGVsZW1lbnRzIGFyZSBkZXByZWNhdGVkIGFuZCBtdXN0IG5vdCBiZSB1c2VkIn0sIm1ldGEtcmVmcmVzaCI6e2Rlc2NyaXB0aW9uOidFbnN1cmVzIDxtZXRhIGh0dHAtZXF1aXY9InJlZnJlc2giPiBpcyBub3QgdXNlZCcsaGVscDoiVGltZWQgcmVmcmVzaCBtdXN0IG5vdCBleGlzdCJ9LCJtZXRhLXZpZXdwb3J0LWxhcmdlIjp7ZGVzY3JpcHRpb246J0Vuc3VyZXMgPG1ldGEgbmFtZT0idmlld3BvcnQiPiBjYW4gc2NhbGUgYSBzaWduaWZpY2FudCBhbW91bnQnLGhlbHA6IlVzZXJzIHNob3VsZCBiZSBhYmxlIHRvIHpvb20gYW5kIHNjYWxlIHRoZSB0ZXh0IHVwIHRvIDUwMCUifSwibWV0YS12aWV3cG9ydCI6e2Rlc2NyaXB0aW9uOidFbnN1cmVzIDxtZXRhIG5hbWU9InZpZXdwb3J0Ij4gZG9lcyBub3QgZGlzYWJsZSB0ZXh0IHNjYWxpbmcgYW5kIHpvb21pbmcnLGhlbHA6Ilpvb21pbmcgYW5kIHNjYWxpbmcgbXVzdCBub3QgYmUgZGlzYWJsZWQifSwib2JqZWN0LWFsdCI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxvYmplY3Q+IGVsZW1lbnRzIGhhdmUgYWx0ZXJuYXRlIHRleHQiLGhlbHA6IjxvYmplY3Q+IGVsZW1lbnRzIG11c3QgaGF2ZSBhbHRlcm5hdGUgdGV4dCJ9LHJhZGlvZ3JvdXA6e2Rlc2NyaXB0aW9uOidFbnN1cmVzIHJlbGF0ZWQgPGlucHV0IHR5cGU9InJhZGlvIj4gZWxlbWVudHMgaGF2ZSBhIGdyb3VwIGFuZCB0aGF0IHRoZSBncm91cCBkZXNpZ25hdGlvbiBpcyBjb25zaXN0ZW50JyxoZWxwOiJSYWRpbyBpbnB1dHMgd2l0aCB0aGUgc2FtZSBuYW1lIGF0dHJpYnV0ZSB2YWx1ZSBtdXN0IGJlIHBhcnQgb2YgYSBncm91cCJ9LHJlZ2lvbjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgYWxsIGNvbnRlbnQgaXMgY29udGFpbmVkIHdpdGhpbiBhIGxhbmRtYXJrIHJlZ2lvbiIsaGVscDoiQ29udGVudCBzaG91bGQgYmUgY29udGFpbmVkIGluIGEgbGFuZG1hcmsgcmVnaW9uIn0sInNjb3BlLWF0dHItdmFsaWQiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyB0aGUgc2NvcGUgYXR0cmlidXRlIGlzIHVzZWQgY29ycmVjdGx5IG9uIHRhYmxlcyIsaGVscDoic2NvcGUgYXR0cmlidXRlIHNob3VsZCBiZSB1c2VkIGNvcnJlY3RseSJ9LCJzZXJ2ZXItc2lkZS1pbWFnZS1tYXAiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyB0aGF0IHNlcnZlci1zaWRlIGltYWdlIG1hcHMgYXJlIG5vdCB1c2VkIixoZWxwOiJTZXJ2ZXItc2lkZSBpbWFnZSBtYXBzIG11c3Qgbm90IGJlIHVzZWQifSwic2tpcC1saW5rIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgdGhlIGZpcnN0IGxpbmsgb24gdGhlIHBhZ2UgaXMgYSBza2lwIGxpbmsiLGhlbHA6IlRoZSBwYWdlIHNob3VsZCBoYXZlIGEgc2tpcCBsaW5rIGFzIGl0cyBmaXJzdCBsaW5rIn0sdGFiaW5kZXg6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIHRhYmluZGV4IGF0dHJpYnV0ZSB2YWx1ZXMgYXJlIG5vdCBncmVhdGVyIHRoYW4gMCIsaGVscDoiRWxlbWVudHMgc2hvdWxkIG5vdCBoYXZlIHRhYmluZGV4IGdyZWF0ZXIgdGhhbiB6ZXJvIn0sInRhYmxlLWR1cGxpY2F0ZS1uYW1lIjp7ZGVzY3JpcHRpb246IkVuc3VyZSB0aGF0IHRhYmxlcyBkbyBub3QgaGF2ZSB0aGUgc2FtZSBzdW1tYXJ5IGFuZCBjYXB0aW9uIixoZWxwOiJUaGUgPGNhcHRpb24+IGVsZW1lbnQgc2hvdWxkIG5vdCBjb250YWluIHRoZSBzYW1lIHRleHQgYXMgdGhlIHN1bW1hcnkgYXR0cmlidXRlIn0sInRhYmxlLWZha2UtY2FwdGlvbiI6e2Rlc2NyaXB0aW9uOiJFbnN1cmUgdGhhdCB0YWJsZXMgd2l0aCBhIGNhcHRpb24gdXNlIHRoZSA8Y2FwdGlvbj4gZWxlbWVudC4iLGhlbHA6IkRhdGEgb3IgaGVhZGVyIGNlbGxzIHNob3VsZCBub3QgYmUgdXNlZCB0byBnaXZlIGNhcHRpb24gdG8gYSBkYXRhIHRhYmxlLiJ9LCJ0ZC1oYXMtaGVhZGVyIjp7ZGVzY3JpcHRpb246IkVuc3VyZSB0aGF0IGVhY2ggbm9uLWVtcHR5IGRhdGEgY2VsbCBpbiBhIGxhcmdlIHRhYmxlIGhhcyBvbmUgb3IgbW9yZSB0YWJsZSBoZWFkZXJzIixoZWxwOiJBbGwgbm9uLWVtcHR5IHRkIGVsZW1lbnQgaW4gdGFibGUgbGFyZ2VyIHRoYW4gMyBieSAzIG11c3QgaGF2ZSBhbiBhc3NvY2lhdGVkIHRhYmxlIGhlYWRlciJ9LCJ0ZC1oZWFkZXJzLWF0dHIiOntkZXNjcmlwdGlvbjoiRW5zdXJlIHRoYXQgZWFjaCBjZWxsIGluIGEgdGFibGUgdXNpbmcgdGhlIGhlYWRlcnMgcmVmZXJzIHRvIGFub3RoZXIgY2VsbCBpbiB0aGF0IHRhYmxlIixoZWxwOiJBbGwgY2VsbHMgaW4gYSB0YWJsZSBlbGVtZW50IHRoYXQgdXNlIHRoZSBoZWFkZXJzIGF0dHJpYnV0ZSBtdXN0IG9ubHkgcmVmZXIgdG8gb3RoZXIgY2VsbHMgb2YgdGhhdCBzYW1lIHRhYmxlIn0sInRoLWhhcy1kYXRhLWNlbGxzIjp7ZGVzY3JpcHRpb246IkVuc3VyZSB0aGF0IGVhY2ggdGFibGUgaGVhZGVyIGluIGEgZGF0YSB0YWJsZSByZWZlcnMgdG8gZGF0YSBjZWxscyIsaGVscDoiQWxsIHRoIGVsZW1lbnQgYW5kIGVsZW1lbnRzIHdpdGggcm9sZT1jb2x1bW5oZWFkZXIvcm93aGVhZGVyIG11c3QgZGF0YSBjZWxscyB3aGljaCBpdCBkZXNjcmliZXMifSwidmFsaWQtbGFuZyI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGxhbmcgYXR0cmlidXRlcyBoYXZlIHZhbGlkIHZhbHVlcyIsaGVscDoibGFuZyBhdHRyaWJ1dGUgbXVzdCBoYXZlIGEgdmFsaWQgdmFsdWUifSwidmlkZW8tY2FwdGlvbiI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDx2aWRlbz4gZWxlbWVudHMgaGF2ZSBjYXB0aW9ucyIsaGVscDoiPHZpZGVvPiBlbGVtZW50cyBtdXN0IGhhdmUgY2FwdGlvbnMifSwidmlkZW8tZGVzY3JpcHRpb24iOntkZXNjcmlwdGlvbjoiRW5zdXJlcyA8dmlkZW8+IGVsZW1lbnRzIGhhdmUgYXVkaW8gZGVzY3JpcHRpb25zIixoZWxwOiI8dmlkZW8+IGVsZW1lbnRzIG11c3QgaGF2ZSBhbiBhdWRpbyBkZXNjcmlwdGlvbiB0cmFjayJ9fSxjaGVja3M6e2FjY2Vzc2tleXM6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJBY2Nlc3NrZXkgYXR0cmlidXRlIHZhbHVlIGlzIHVuaXF1ZSI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkRvY3VtZW50IGhhcyBtdWx0aXBsZSBlbGVtZW50cyB3aXRoIHRoZSBzYW1lIGFjY2Vzc2tleSI7cmV0dXJuIGJ9fX0sIm5vbi1lbXB0eS1hbHQiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBoYXMgYSBub24tZW1wdHkgYWx0IGF0dHJpYnV0ZSI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaGFzIG5vIGFsdCBhdHRyaWJ1dGUgb3IgdGhlIGFsdCBhdHRyaWJ1dGUgaXMgZW1wdHkiO3JldHVybiBifX19LCJub24tZW1wdHktdGl0bGUiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBoYXMgYSB0aXRsZSBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGhhcyBubyB0aXRsZSBhdHRyaWJ1dGUgb3IgdGhlIHRpdGxlIGF0dHJpYnV0ZSBpcyBlbXB0eSI7cmV0dXJuIGJ9fX0sImFyaWEtbGFiZWwiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iYXJpYS1sYWJlbCBhdHRyaWJ1dGUgZXhpc3RzIGFuZCBpcyBub3QgZW1wdHkiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJhcmlhLWxhYmVsIGF0dHJpYnV0ZSBkb2VzIG5vdCBleGlzdCBvciBpcyBlbXB0eSI7cmV0dXJuIGJ9fX0sImFyaWEtbGFiZWxsZWRieSI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJhcmlhLWxhYmVsbGVkYnkgYXR0cmlidXRlIGV4aXN0cyBhbmQgcmVmZXJlbmNlcyBlbGVtZW50cyB0aGF0IGFyZSB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iYXJpYS1sYWJlbGxlZGJ5IGF0dHJpYnV0ZSBkb2VzIG5vdCBleGlzdCwgcmVmZXJlbmNlcyBlbGVtZW50cyB0aGF0IGRvIG5vdCBleGlzdCBvciByZWZlcmVuY2VzIGVsZW1lbnRzIHRoYXQgYXJlIGVtcHR5IG9yIG5vdCB2aXNpYmxlIjtyZXR1cm4gYn19fSwiYXJpYS1hbGxvd2VkLWF0dHIiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQVJJQSBhdHRyaWJ1dGVzIGFyZSB1c2VkIGNvcnJlY3RseSBmb3IgdGhlIGRlZmluZWQgcm9sZSI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkFSSUEgYXR0cmlidXRlIisoYS5kYXRhJiZhLmRhdGEubGVuZ3RoPjE/InMgYXJlIjoiIGlzIikrIiBub3QgYWxsb3dlZDoiLGM9YS5kYXRhO2lmKGMpZm9yKHZhciBkLGU9LTEsZj1jLmxlbmd0aC0xO2U8ZjspZD1jW2UrPTFdLGIrPSIgIitkO3JldHVybiBifX19LCJhcmlhLXJlcXVpcmVkLWF0dHIiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQWxsIHJlcXVpcmVkIEFSSUEgYXR0cmlidXRlcyBhcmUgcHJlc2VudCI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlJlcXVpcmVkIEFSSUEgYXR0cmlidXRlIisoYS5kYXRhJiZhLmRhdGEubGVuZ3RoPjE/InMiOiIiKSsiIG5vdCBwcmVzZW50OiIsYz1hLmRhdGE7aWYoYylmb3IodmFyIGQsZT0tMSxmPWMubGVuZ3RoLTE7ZTxmOylkPWNbZSs9MV0sYis9IiAiK2Q7cmV0dXJuIGJ9fX0sImFyaWEtcmVxdWlyZWQtY2hpbGRyZW4iOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iUmVxdWlyZWQgQVJJQSBjaGlsZHJlbiBhcmUgcHJlc2VudCI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlJlcXVpcmVkIEFSSUEgIisoYS5kYXRhJiZhLmRhdGEubGVuZ3RoPjE/ImNoaWxkcmVuIjoiY2hpbGQiKSsiIHJvbGUgbm90IHByZXNlbnQ6IixjPWEuZGF0YTtpZihjKWZvcih2YXIgZCxlPS0xLGY9Yy5sZW5ndGgtMTtlPGY7KWQ9Y1tlKz0xXSxiKz0iICIrZDtyZXR1cm4gYn19fSwiYXJpYS1yZXF1aXJlZC1wYXJlbnQiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iUmVxdWlyZWQgQVJJQSBwYXJlbnQgcm9sZSBwcmVzZW50IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iUmVxdWlyZWQgQVJJQSBwYXJlbnQiKyhhLmRhdGEmJmEuZGF0YS5sZW5ndGg+MT8icyI6IiIpKyIgcm9sZSBub3QgcHJlc2VudDoiLGM9YS5kYXRhO2lmKGMpZm9yKHZhciBkLGU9LTEsZj1jLmxlbmd0aC0xO2U8ZjspZD1jW2UrPTFdLGIrPSIgIitkO3JldHVybiBifX19LGludmFsaWRyb2xlOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQVJJQSByb2xlIGlzIHZhbGlkIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iUm9sZSBtdXN0IGJlIG9uZSBvZiB0aGUgdmFsaWQgQVJJQSByb2xlcyI7cmV0dXJuIGJ9fX0sYWJzdHJhY3Ryb2xlOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJBYnN0cmFjdCByb2xlcyBhcmUgbm90IHVzZWQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJBYnN0cmFjdCByb2xlcyBjYW5ub3QgYmUgZGlyZWN0bHkgdXNlZCI7cmV0dXJuIGJ9fX0sImFyaWEtdmFsaWQtYXR0ci12YWx1ZSI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJBUklBIGF0dHJpYnV0ZSB2YWx1ZXMgYXJlIHZhbGlkIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iSW52YWxpZCBBUklBIGF0dHJpYnV0ZSB2YWx1ZSIrKGEuZGF0YSYmYS5kYXRhLmxlbmd0aD4xPyJzIjoiIikrIjoiLGM9YS5kYXRhO2lmKGMpZm9yKHZhciBkLGU9LTEsZj1jLmxlbmd0aC0xO2U8ZjspZD1jW2UrPTFdLGIrPSIgIitkO3JldHVybiBifX19LCJhcmlhLXZhbGlkLWF0dHIiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQVJJQSBhdHRyaWJ1dGUgbmFtZSIrKGEuZGF0YSYmYS5kYXRhLmxlbmd0aD4xPyJzIjoiIikrIiBhcmUgdmFsaWQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJJbnZhbGlkIEFSSUEgYXR0cmlidXRlIG5hbWUiKyhhLmRhdGEmJmEuZGF0YS5sZW5ndGg+MT8icyI6IiIpKyI6IixjPWEuZGF0YTtpZihjKWZvcih2YXIgZCxlPS0xLGY9Yy5sZW5ndGgtMTtlPGY7KWQ9Y1tlKz0xXSxiKz0iICIrZDtyZXR1cm4gYn19fSxjYXB0aW9uOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iVGhlIG11bHRpbWVkaWEgZWxlbWVudCBoYXMgYSBjYXB0aW9ucyB0cmFjayI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlRoZSBtdWx0aW1lZGlhIGVsZW1lbnQgZG9lcyBub3QgaGF2ZSBhIGNhcHRpb25zIHRyYWNrIjtyZXR1cm4gYn19fSwiaXMtb24tc2NyZWVuIjp7aW1wYWN0OiJtaW5vciIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaXMgbm90IHZpc2libGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGlzIHZpc2libGUiO3JldHVybiBifX19LCJub24tZW1wdHktaWYtcHJlc2VudCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50ICI7cmV0dXJuIGIrPWEuZGF0YT8iaGFzIGEgbm9uLWVtcHR5IHZhbHVlIGF0dHJpYnV0ZSI6ImRvZXMgbm90IGhhdmUgYSB2YWx1ZSBhdHRyaWJ1dGUifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGhhcyBhIHZhbHVlIGF0dHJpYnV0ZSBhbmQgdGhlIHZhbHVlIGF0dHJpYnV0ZSBpcyBlbXB0eSI7cmV0dXJuIGJ9fX0sIm5vbi1lbXB0eS12YWx1ZSI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGhhcyBhIG5vbi1lbXB0eSB2YWx1ZSBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGhhcyBubyB2YWx1ZSBhdHRyaWJ1dGUgb3IgdGhlIHZhbHVlIGF0dHJpYnV0ZSBpcyBlbXB0eSI7cmV0dXJuIGJ9fX0sImJ1dHRvbi1oYXMtdmlzaWJsZS10ZXh0Ijp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaGFzIGlubmVyIHRleHQgdGhhdCBpcyB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBkb2VzIG5vdCBoYXZlIGlubmVyIHRleHQgdGhhdCBpcyB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjtyZXR1cm4gYn19fSwicm9sZS1wcmVzZW50YXRpb24iOntpbXBhY3Q6Im1vZGVyYXRlIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0nRWxlbWVudFwncyBkZWZhdWx0IHNlbWFudGljcyB3ZXJlIG92ZXJyaWRlbiB3aXRoIHJvbGU9InByZXNlbnRhdGlvbiInO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSdFbGVtZW50XCdzIGRlZmF1bHQgc2VtYW50aWNzIHdlcmUgbm90IG92ZXJyaWRkZW4gd2l0aCByb2xlPSJwcmVzZW50YXRpb24iJztyZXR1cm4gYn19fSwicm9sZS1ub25lIjp7aW1wYWN0OiJtb2RlcmF0ZSIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9J0VsZW1lbnRcJ3MgZGVmYXVsdCBzZW1hbnRpY3Mgd2VyZSBvdmVycmlkZW4gd2l0aCByb2xlPSJub25lIic7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9J0VsZW1lbnRcJ3MgZGVmYXVsdCBzZW1hbnRpY3Mgd2VyZSBub3Qgb3ZlcnJpZGRlbiB3aXRoIHJvbGU9Im5vbmUiJztyZXR1cm4gYn19fSwiZm9jdXNhYmxlLW5vLW5hbWUiOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGlzIG5vdCBpbiB0YWIgb3JkZXIgb3IgaGFzIGFjY2Vzc2libGUgdGV4dCI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaXMgaW4gdGFiIG9yZGVyIGFuZCBkb2VzIG5vdCBoYXZlIGFjY2Vzc2libGUgdGV4dCI7cmV0dXJuIGJ9fX0sImludGVybmFsLWxpbmstcHJlc2VudCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJWYWxpZCBza2lwIGxpbmsgZm91bmQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJObyB2YWxpZCBza2lwIGxpbmsgZm91bmQiO3JldHVybiBifX19LCJoZWFkZXItcHJlc2VudCI6e2ltcGFjdDoibW9kZXJhdGUiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJQYWdlIGhhcyBhIGhlYWRlciI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlBhZ2UgZG9lcyBub3QgaGF2ZSBhIGhlYWRlciI7cmV0dXJuIGJ9fX0sbGFuZG1hcms6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IlBhZ2UgaGFzIGEgbGFuZG1hcmsgcmVnaW9uIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iUGFnZSBkb2VzIG5vdCBoYXZlIGEgbGFuZG1hcmsgcmVnaW9uIjtyZXR1cm4gYn19fSwiZ3JvdXAtbGFiZWxsZWRieSI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSdBbGwgZWxlbWVudHMgd2l0aCB0aGUgbmFtZSAiJythLmRhdGEubmFtZSsnIiByZWZlcmVuY2UgdGhlIHNhbWUgZWxlbWVudCB3aXRoIGFyaWEtbGFiZWxsZWRieSc7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9J0FsbCBlbGVtZW50cyB3aXRoIHRoZSBuYW1lICInK2EuZGF0YS5uYW1lKyciIGRvIG5vdCByZWZlcmVuY2UgdGhlIHNhbWUgZWxlbWVudCB3aXRoIGFyaWEtbGFiZWxsZWRieSc7cmV0dXJuIGJ9fX0sZmllbGRzZXQ6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGlzIGNvbnRhaW5lZCBpbiBhIGZpZWxkc2V0IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iIixjPWEuZGF0YSYmYS5kYXRhLmZhaWx1cmVDb2RlO3JldHVybiBiKz0ibm8tbGVnZW5kIj09PWM/IkZpZWxkc2V0IGRvZXMgbm90IGhhdmUgYSBsZWdlbmQgYXMgaXRzIGZpcnN0IGNoaWxkIjoiZW1wdHktbGVnZW5kIj09PWM/IkxlZ2VuZCBkb2VzIG5vdCBoYXZlIHRleHQgdGhhdCBpcyB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjoibWl4ZWQtaW5wdXRzIj09PWM/IkZpZWxkc2V0IGNvbnRhaW5zIHVucmVsYXRlZCBpbnB1dHMiOiJuby1ncm91cC1sYWJlbCI9PT1jPyJBUklBIGdyb3VwIGRvZXMgbm90IGhhdmUgYXJpYS1sYWJlbCBvciBhcmlhLWxhYmVsbGVkYnkiOiJncm91cC1taXhlZC1pbnB1dHMiPT09Yz8iQVJJQSBncm91cCBjb250YWlucyB1bnJlbGF0ZWQgaW5wdXRzIjoiRWxlbWVudCBkb2VzIG5vdCBoYXZlIGEgY29udGFpbmluZyBmaWVsZHNldCBvciBBUklBIGdyb3VwIn19fSwiY29sb3ItY29udHJhc3QiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iIjtyZXR1cm4gYis9YS5kYXRhJiZhLmRhdGEuY29udHJhc3RSYXRpbz8iRWxlbWVudCBoYXMgc3VmZmljaWVudCBjb2xvciBjb250cmFzdCBvZiAiK2EuZGF0YS5jb250cmFzdFJhdGlvOiJVbmFibGUgdG8gZGV0ZXJtaW5lIGNvbnRyYXN0IHJhdGlvIn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBoYXMgaW5zdWZmaWNpZW50IGNvbG9yIGNvbnRyYXN0IG9mICIrYS5kYXRhLmNvbnRyYXN0UmF0aW8rIiAoZm9yZWdyb3VuZCBjb2xvcjogIithLmRhdGEuZmdDb2xvcisiLCBiYWNrZ3JvdW5kIGNvbG9yOiAiK2EuZGF0YS5iZ0NvbG9yKyIsIGZvbnQgc2l6ZTogIithLmRhdGEuZm9udFNpemUrIiwgZm9udCB3ZWlnaHQ6ICIrYS5kYXRhLmZvbnRXZWlnaHQrIikiO3JldHVybiBifX19LCJzdHJ1Y3R1cmVkLWRsaXRlbXMiOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJXaGVuIG5vdCBlbXB0eSwgZWxlbWVudCBoYXMgYm90aCA8ZHQ+IGFuZCA8ZGQ+IGVsZW1lbnRzIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iV2hlbiBub3QgZW1wdHksIGVsZW1lbnQgZG9lcyBub3QgaGF2ZSBhdCBsZWFzdCBvbmUgPGR0PiBlbGVtZW50IGZvbGxvd2VkIGJ5IGF0IGxlYXN0IG9uZSA8ZGQ+IGVsZW1lbnQiO3JldHVybiBifX19LCJvbmx5LWRsaXRlbXMiOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJMaXN0IGVsZW1lbnQgb25seSBoYXMgZGlyZWN0IGNoaWxkcmVuIHRoYXQgYXJlIGFsbG93ZWQgaW5zaWRlIDxkdD4gb3IgPGRkPiBlbGVtZW50cyI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9Ikxpc3QgZWxlbWVudCBoYXMgZGlyZWN0IGNoaWxkcmVuIHRoYXQgYXJlIG5vdCBhbGxvd2VkIGluc2lkZSA8ZHQ+IG9yIDxkZD4gZWxlbWVudHMiO3JldHVybiBifX19LGRsaXRlbTp7aW1wYWN0OiJzZXJpb3VzIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iRGVzY3JpcHRpb24gbGlzdCBpdGVtIGhhcyBhIDxkbD4gcGFyZW50IGVsZW1lbnQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJEZXNjcmlwdGlvbiBsaXN0IGl0ZW0gZG9lcyBub3QgaGF2ZSBhIDxkbD4gcGFyZW50IGVsZW1lbnQiO3JldHVybiBifX19LCJkb2MtaGFzLXRpdGxlIjp7aW1wYWN0OiJtb2RlcmF0ZSIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkRvY3VtZW50IGhhcyBhIG5vbi1lbXB0eSA8dGl0bGU+IGVsZW1lbnQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJEb2N1bWVudCBkb2VzIG5vdCBoYXZlIGEgbm9uLWVtcHR5IDx0aXRsZT4gZWxlbWVudCI7cmV0dXJuIGJ9fX0sImR1cGxpY2F0ZS1pZCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJEb2N1bWVudCBoYXMgbm8gZWxlbWVudHMgdGhhdCBzaGFyZSB0aGUgc2FtZSBpZCBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJEb2N1bWVudCBoYXMgbXVsdGlwbGUgZWxlbWVudHMgd2l0aCB0aGUgc2FtZSBpZCBhdHRyaWJ1dGU6ICIrYS5kYXRhO3JldHVybiBifX19LCJoYXMtdmlzaWJsZS10ZXh0Ijp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaGFzIHRleHQgdGhhdCBpcyB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBkb2VzIG5vdCBoYXZlIHRleHQgdGhhdCBpcyB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjtyZXR1cm4gYn19fSwidW5pcXVlLWZyYW1lLXRpdGxlIjp7aW1wYWN0OiJzZXJpb3VzIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCdzIHRpdGxlIGF0dHJpYnV0ZSBpcyB1bmlxdWUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50J3MgdGl0bGUgYXR0cmlidXRlIGlzIG5vdCB1bmlxdWUiO3JldHVybiBifX19LCJoZWFkaW5nLW9yZGVyIjp7aW1wYWN0OiJtaW5vciIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkhlYWRpbmcgb3JkZXIgdmFsaWQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJIZWFkaW5nIG9yZGVyIGludmFsaWQiO3JldHVybiBifX19LCJocmVmLW5vLWhhc2giOntpbXBhY3Q6Im1vZGVyYXRlIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQW5jaG9yIGRvZXMgbm90IGhhdmUgYSBocmVmIHF1YWxzICMiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJBbmNob3IgaGFzIGEgaHJlZiBxdWFscyAjIjtyZXR1cm4gYn19fSwiaGFzLWxhbmciOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJUaGUgPGh0bWw+IGVsZW1lbnQgaGFzIGEgbGFuZyBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJUaGUgPGh0bWw+IGVsZW1lbnQgZG9lcyBub3QgaGF2ZSBhIGxhbmcgYXR0cmlidXRlIjtyZXR1cm4gYn19fSwidmFsaWQtbGFuZyI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IlZhbHVlIG9mIGxhbmcgYXR0cmlidXRlIGlzIGluY2x1ZGVkIGluIHRoZSBsaXN0IG9mIHZhbGlkIGxhbmd1YWdlcyI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlZhbHVlIG9mIGxhbmcgYXR0cmlidXRlIG5vdCBpbmNsdWRlZCBpbiB0aGUgbGlzdCBvZiB2YWxpZCBsYW5ndWFnZXMiO3JldHVybiBifX19LCJoYXMtYWx0Ijp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaGFzIGFuIGFsdCBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGRvZXMgbm90IGhhdmUgYW4gYWx0IGF0dHJpYnV0ZSI7cmV0dXJuIGJ9fX0sImR1cGxpY2F0ZS1pbWctbGFiZWwiOntpbXBhY3Q6Im1pbm9yIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBkb2VzIG5vdCBkdXBsaWNhdGUgZXhpc3RpbmcgdGV4dCBpbiA8aW1nPiBhbHQgdGV4dCI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgY29udGFpbnMgPGltZz4gZWxlbWVudCB3aXRoIGFsdCB0ZXh0IHRoYXQgZHVwbGljYXRlcyBleGlzdGluZyB0ZXh0IjtyZXR1cm4gYn19fSwidGl0bGUtb25seSI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkZvcm0gZWxlbWVudCBkb2VzIG5vdCBzb2xlbHkgdXNlIHRpdGxlIGF0dHJpYnV0ZSBmb3IgaXRzIGxhYmVsIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iT25seSB0aXRsZSB1c2VkIHRvIGdlbmVyYXRlIGxhYmVsIGZvciBmb3JtIGVsZW1lbnQiO3JldHVybiBifX19LCJpbXBsaWNpdC1sYWJlbCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJGb3JtIGVsZW1lbnQgaGFzIGFuIGltcGxpY2l0ICh3cmFwcGVkKSA8bGFiZWw+IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRm9ybSBlbGVtZW50IGRvZXMgbm90IGhhdmUgYW4gaW1wbGljaXQgKHdyYXBwZWQpIDxsYWJlbD4iO3JldHVybiBifX19LCJleHBsaWNpdC1sYWJlbCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJGb3JtIGVsZW1lbnQgaGFzIGFuIGV4cGxpY2l0IDxsYWJlbD4iO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJGb3JtIGVsZW1lbnQgZG9lcyBub3QgaGF2ZSBhbiBleHBsaWNpdCA8bGFiZWw+IjtyZXR1cm4gYn19fSwiaGVscC1zYW1lLWFzLWxhYmVsIjp7aW1wYWN0OiJtaW5vciIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkhlbHAgdGV4dCAodGl0bGUgb3IgYXJpYS1kZXNjcmliZWRieSkgZG9lcyBub3QgZHVwbGljYXRlIGxhYmVsIHRleHQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJIZWxwIHRleHQgKHRpdGxlIG9yIGFyaWEtZGVzY3JpYmVkYnkpIHRleHQgaXMgdGhlIHNhbWUgYXMgdGhlIGxhYmVsIHRleHQiO3JldHVybiBifX19LCJtdWx0aXBsZS1sYWJlbCI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkZvcm0gZWxlbWVudCBkb2VzIG5vdCBoYXZlIG11bHRpcGxlIDxsYWJlbD4gZWxlbWVudHMiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJGb3JtIGVsZW1lbnQgaGFzIG11bHRpcGxlIDxsYWJlbD4gZWxlbWVudHMiO3JldHVybiBifX19LCJoYXMtdGgiOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJMYXlvdXQgdGFibGUgZG9lcyBub3QgdXNlIDx0aD4gZWxlbWVudHMiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJMYXlvdXQgdGFibGUgdXNlcyA8dGg+IGVsZW1lbnRzIjtyZXR1cm4gYn19fSwiaGFzLWNhcHRpb24iOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJMYXlvdXQgdGFibGUgZG9lcyBub3QgdXNlIDxjYXB0aW9uPiBlbGVtZW50IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iTGF5b3V0IHRhYmxlIHVzZXMgPGNhcHRpb24+IGVsZW1lbnQiO3JldHVybiBifX19LCJoYXMtc3VtbWFyeSI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkxheW91dCB0YWJsZSBkb2VzIG5vdCB1c2Ugc3VtbWFyeSBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJMYXlvdXQgdGFibGUgdXNlcyBzdW1tYXJ5IGF0dHJpYnV0ZSI7cmV0dXJuIGJ9fX0sImxpbmstaW4tdGV4dC1ibG9jayI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJMaW5rcyBjYW4gYmUgZGlzdGluZ3Vpc2hlZCBmcm9tIHN1cnJvdW5kaW5nIHRleHQgaW4gYSB3YXkgdGhhdCBkb2VzIG5vdCByZWx5IG9uIGNvbG9yIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iTGlua3MgY2FuIG5vdCBiZSBkaXN0aW5ndWlzaGVkIGZyb20gc3Vycm91bmRpbmcgdGV4dCBpbiBhIHdheSB0aGF0IGRvZXMgbm90IHJlbHkgb24gY29sb3IiO3JldHVybiBifX19LCJvbmx5LWxpc3RpdGVtcyI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9Ikxpc3QgZWxlbWVudCBvbmx5IGhhcyBkaXJlY3QgY2hpbGRyZW4gdGhhdCBhcmUgYWxsb3dlZCBpbnNpZGUgPGxpPiBlbGVtZW50cyI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9Ikxpc3QgZWxlbWVudCBoYXMgZGlyZWN0IGNoaWxkcmVuIHRoYXQgYXJlIG5vdCBhbGxvd2VkIGluc2lkZSA8bGk+IGVsZW1lbnRzIjtyZXR1cm4gYn19fSxsaXN0aXRlbTp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9J0xpc3QgaXRlbSBoYXMgYSA8dWw+LCA8b2w+IG9yIHJvbGU9Imxpc3QiIHBhcmVudCBlbGVtZW50JztyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0nTGlzdCBpdGVtIGRvZXMgbm90IGhhdmUgYSA8dWw+LCA8b2w+IG9yIHJvbGU9Imxpc3QiIHBhcmVudCBlbGVtZW50JztyZXR1cm4gYn19fSwibWV0YS1yZWZyZXNoIjp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IjxtZXRhPiB0YWcgZG9lcyBub3QgaW1tZWRpYXRlbHkgcmVmcmVzaCB0aGUgcGFnZSI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IjxtZXRhPiB0YWcgZm9yY2VzIHRpbWVkIHJlZnJlc2ggb2YgcGFnZSI7cmV0dXJuIGJ9fX0sIm1ldGEtdmlld3BvcnQtbGFyZ2UiOntpbXBhY3Q6Im1pbm9yIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iPG1ldGE+IHRhZyBkb2VzIG5vdCBwcmV2ZW50IHNpZ25pZmljYW50IHpvb21pbmciO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSI8bWV0YT4gdGFnIGxpbWl0cyB6b29taW5nIjtyZXR1cm4gYn19fSwibWV0YS12aWV3cG9ydCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSI8bWV0YT4gdGFnIGRvZXMgbm90IGRpc2FibGUgem9vbWluZyI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IjxtZXRhPiB0YWcgZGlzYWJsZXMgem9vbWluZyI7cmV0dXJuIGJ9fX0scmVnaW9uOntpbXBhY3Q6Im1vZGVyYXRlIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQ29udGVudCBjb250YWluZWQgYnkgQVJJQSBsYW5kbWFyayI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkNvbnRlbnQgbm90IGNvbnRhaW5lZCBieSBhbiBBUklBIGxhbmRtYXJrIjtyZXR1cm4gYn19fSwiaHRtbDUtc2NvcGUiOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJTY29wZSBhdHRyaWJ1dGUgaXMgb25seSB1c2VkIG9uIHRhYmxlIGhlYWRlciBlbGVtZW50cyAoPHRoPikiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJJbiBIVE1MIDUsIHNjb3BlIGF0dHJpYnV0ZXMgbWF5IG9ubHkgYmUgdXNlZCBvbiB0YWJsZSBoZWFkZXIgZWxlbWVudHMgKDx0aD4pIjtyZXR1cm4gYn19fSwic2NvcGUtdmFsdWUiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iU2NvcGUgYXR0cmlidXRlIGlzIHVzZWQgY29ycmVjdGx5IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iVGhlIHZhbHVlIG9mIHRoZSBzY29wZSBhdHRyaWJ1dGUgbWF5IG9ubHkgYmUgJ3Jvdycgb3IgJ2NvbCciO3JldHVybiBifX19LGV4aXN0czp7aW1wYWN0OiJtaW5vciIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgZG9lcyBub3QgZXhpc3QiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGV4aXN0cyI7cmV0dXJuIGJ9fX0sInNraXAtbGluayI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJWYWxpZCBza2lwIGxpbmsgZm91bmQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJObyB2YWxpZCBza2lwIGxpbmsgZm91bmQiO3JldHVybiBifX19LHRhYmluZGV4OntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGRvZXMgbm90IGhhdmUgYSB0YWJpbmRleCBncmVhdGVyIHRoYW4gMCI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaGFzIGEgdGFiaW5kZXggZ3JlYXRlciB0aGFuIDAiO3JldHVybiBifX19LCJzYW1lLWNhcHRpb24tc3VtbWFyeSI6e2ltcGFjdDoibW9kZXJhdGUiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJDb250ZW50IG9mIHN1bW1hcnkgYXR0cmlidXRlIGFuZCA8Y2FwdGlvbj4gYXJlIG5vdCBkdXBsaWNhdGVkIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iQ29udGVudCBvZiBzdW1tYXJ5IGF0dHJpYnV0ZSBhbmQgPGNhcHRpb24+IGVsZW1lbnQgYXJlIGlkZW50aWNhbCI7cmV0dXJuIGJ9fX0sImNhcHRpb24tZmFrZWQiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iVGhlIGZpcnN0IHJvdyBvZiBhIHRhYmxlIGlzIG5vdCB1c2VkIGFzIGEgY2FwdGlvbiI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlRoZSBmaXJzdCByb3cgb2YgdGhlIHRhYmxlIHNob3VsZCBiZSBhIGNhcHRpb24gaW5zdGVhZCBvZiBhIHRhYmxlIGNlbGwiO3JldHVybiBifX19LCJ0ZC1oYXMtaGVhZGVyIjp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkFsbCBub24tZW1wdHkgZGF0YSBjZWxscyBoYXZlIHRhYmxlIGhlYWRlcnMiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJTb21lIG5vbi1lbXB0eSBkYXRhIGNlbGxzIGRvIG5vdCBoYXZlIHRhYmxlIGhlYWRlcnMiO3JldHVybiBifX19LCJ0ZC1oZWFkZXJzLWF0dHIiOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJUaGUgaGVhZGVycyBhdHRyaWJ1dGUgaXMgZXhjbHVzaXZlbHkgdXNlZCB0byByZWZlciB0byBvdGhlciBjZWxscyBpbiB0aGUgdGFibGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJUaGUgaGVhZGVycyBhdHRyaWJ1dGUgaXMgbm90IGV4Y2x1c2l2ZWx5IHVzZWQgdG8gcmVmZXIgdG8gb3RoZXIgY2VsbHMgaW4gdGhlIHRhYmxlIjtyZXR1cm4gYn19fSwidGgtaGFzLWRhdGEtY2VsbHMiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQWxsIHRhYmxlIGhlYWRlciBjZWxscyByZWZlciB0byBkYXRhIGNlbGxzIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iTm90IGFsbCB0YWJsZSBoZWFkZXIgY2VsbHMgcmVmZXIgdG8gZGF0YSBjZWxscyI7cmV0dXJuIGJ9fX0sZGVzY3JpcHRpb246e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IlRoZSBtdWx0aW1lZGlhIGVsZW1lbnQgaGFzIGFuIGF1ZGlvIGRlc2NyaXB0aW9uIHRyYWNrIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iVGhlIG11bHRpbWVkaWEgZWxlbWVudCBkb2VzIG5vdCBoYXZlIGFuIGF1ZGlvIGRlc2NyaXB0aW9uIHRyYWNrIjtyZXR1cm4gYn19fX0sZmFpbHVyZVN1bW1hcmllczp7YW55OntmYWlsdXJlTWVzc2FnZTpmdW5jdGlvbihhKXt2YXIgYj0iRml4IGFueSBvZiB0aGUgZm9sbG93aW5nOiIsYz1hO2lmKGMpZm9yKHZhciBkLGU9LTEsZj1jLmxlbmd0aC0xO2U8ZjspZD1jW2UrPTFdLGIrPSJcbiAgIitkLnNwbGl0KCJcbiIpLmpvaW4oIlxuICAiKTtyZXR1cm4gYn19LG5vbmU6e2ZhaWx1cmVNZXNzYWdlOmZ1bmN0aW9uKGEpe3ZhciBiPSJGaXggYWxsIG9mIHRoZSBmb2xsb3dpbmc6IixjPWE7aWYoYylmb3IodmFyIGQsZT0tMSxmPWMubGVuZ3RoLTE7ZTxmOylkPWNbZSs9MV0sYis9IlxuICAiK2Quc3BsaXQoIlxuIikuam9pbigiXG4gICIpO3JldHVybiBifX19fSxydWxlczpbe2lkOiJhY2Nlc3NrZXlzIixzZWxlY3RvcjoiW2FjY2Vzc2tleV0iLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYSIsIndjYWcyMTEiXSxhbGw6W10sYW55OltdLG5vbmU6WyJhY2Nlc3NrZXlzIl19LHtpZDoiYXJlYS1hbHQiLApzZWxlY3RvcjoibWFwIGFyZWFbaHJlZl0iLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYSIsIndjYWcxMTEiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5hIl0sYWxsOltdLGFueTpbIm5vbi1lbXB0eS1hbHQiLCJub24tZW1wdHktdGl0bGUiLCJhcmlhLWxhYmVsIiwiYXJpYS1sYWJlbGxlZGJ5Il0sbm9uZTpbXX0se2lkOiJhcmlhLWFsbG93ZWQtYXR0ciIsbWF0Y2hlczpmdW5jdGlvbihhKXt2YXIgYj1hLmdldEF0dHJpYnV0ZSgicm9sZSIpO2J8fChiPWF4ZS5jb21tb25zLmFyaWEuaW1wbGljaXRSb2xlKGEpKTt2YXIgYz1heGUuY29tbW9ucy5hcmlhLmFsbG93ZWRBdHRyKGIpO2lmKGImJmMpe3ZhciBkPS9eYXJpYS0vO2lmKGEuaGFzQXR0cmlidXRlcygpKWZvcih2YXIgZT1hLmF0dHJpYnV0ZXMsZj0wLGc9ZS5sZW5ndGg7ZjxnO2YrKylpZihkLnRlc3QoZVtmXS5uYW1lKSlyZXR1cm4hMH1yZXR1cm4hMX0sdGFnczpbIndjYWcyYSIsIndjYWc0MTEiLCJ3Y2FnNDEyIl0sYWxsOltdLGFueTpbImFyaWEtYWxsb3dlZC1hdHRyIl0sbm9uZTpbXX0se2lkOiJhcmlhLXJlcXVpcmVkLWF0dHIiLHNlbGVjdG9yOiJbcm9sZV0iLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnNDExIiwid2NhZzQxMiJdLGFsbDpbXSxhbnk6WyJhcmlhLXJlcXVpcmVkLWF0dHIiXSxub25lOltdfSx7aWQ6ImFyaWEtcmVxdWlyZWQtY2hpbGRyZW4iLHNlbGVjdG9yOiJbcm9sZV0iLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTMxIl0sYWxsOltdLGFueTpbImFyaWEtcmVxdWlyZWQtY2hpbGRyZW4iXSxub25lOltdfSx7aWQ6ImFyaWEtcmVxdWlyZWQtcGFyZW50IixzZWxlY3RvcjoiW3JvbGVdIix0YWdzOlsid2NhZzJhIiwid2NhZzEzMSJdLGFsbDpbXSxhbnk6WyJhcmlhLXJlcXVpcmVkLXBhcmVudCJdLG5vbmU6W119LHtpZDoiYXJpYS1yb2xlcyIsc2VsZWN0b3I6Iltyb2xlXSIsdGFnczpbIndjYWcyYSIsIndjYWcxMzEiLCJ3Y2FnNDExIiwid2NhZzQxMiJdLGFsbDpbXSxhbnk6W10sbm9uZTpbImludmFsaWRyb2xlIiwiYWJzdHJhY3Ryb2xlIl19LHtpZDoiYXJpYS12YWxpZC1hdHRyLXZhbHVlIixtYXRjaGVzOmZ1bmN0aW9uKGEpe3ZhciBiPS9eYXJpYS0vO2lmKGEuaGFzQXR0cmlidXRlcygpKWZvcih2YXIgYz1hLmF0dHJpYnV0ZXMsZD0wLGU9Yy5sZW5ndGg7ZDxlO2QrKylpZihiLnRlc3QoY1tkXS5uYW1lKSlyZXR1cm4hMDtyZXR1cm4hMX0sdGFnczpbIndjYWcyYSIsIndjYWcxMzEiLCJ3Y2FnNDExIiwid2NhZzQxMiJdLGFsbDpbXSxhbnk6W3tvcHRpb25zOltdLGlkOiJhcmlhLXZhbGlkLWF0dHItdmFsdWUifV0sbm9uZTpbXX0se2lkOiJhcmlhLXZhbGlkLWF0dHIiLG1hdGNoZXM6ZnVuY3Rpb24oYSl7dmFyIGI9L15hcmlhLS87aWYoYS5oYXNBdHRyaWJ1dGVzKCkpZm9yKHZhciBjPWEuYXR0cmlidXRlcyxkPTAsZT1jLmxlbmd0aDtkPGU7ZCsrKWlmKGIudGVzdChjW2RdLm5hbWUpKXJldHVybiEwO3JldHVybiExfSx0YWdzOlsid2NhZzJhIiwid2NhZzQxMSJdLGFsbDpbXSxhbnk6W3tvcHRpb25zOltdLGlkOiJhcmlhLXZhbGlkLWF0dHIifV0sbm9uZTpbXX0se2lkOiJhdWRpby1jYXB0aW9uIixzZWxlY3RvcjoiYXVkaW8iLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYSIsIndjYWcxMjIiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5hIl0sYWxsOltdLGFueTpbXSxub25lOlsiY2FwdGlvbiJdfSx7aWQ6ImJsaW5rIixzZWxlY3RvcjoiYmxpbmsiLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYSIsIndjYWcyMjIiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5qIl0sYWxsOltdLGFueTpbXSxub25lOlsiaXMtb24tc2NyZWVuIl19LHtpZDoiYnV0dG9uLW5hbWUiLHNlbGVjdG9yOididXR0b24sIFtyb2xlPSJidXR0b24iXSwgaW5wdXRbdHlwZT0iYnV0dG9uIl0sIGlucHV0W3R5cGU9InN1Ym1pdCJdLCBpbnB1dFt0eXBlPSJyZXNldCJdJyx0YWdzOlsid2NhZzJhIiwid2NhZzQxMiIsInNlY3Rpb241MDgiLCJzZWN0aW9uNTA4LjIyLmEiXSxhbGw6W10sYW55Olsibm9uLWVtcHR5LWlmLXByZXNlbnQiLCJub24tZW1wdHktdmFsdWUiLCJidXR0b24taGFzLXZpc2libGUtdGV4dCIsImFyaWEtbGFiZWwiLCJhcmlhLWxhYmVsbGVkYnkiLCJyb2xlLXByZXNlbnRhdGlvbiIsInJvbGUtbm9uZSJdLG5vbmU6WyJmb2N1c2FibGUtbm8tbmFtZSJdfSx7aWQ6ImJ5cGFzcyIsc2VsZWN0b3I6Imh0bWwiLHBhZ2VMZXZlbDohMCxtYXRjaGVzOmZ1bmN0aW9uKGEpe3JldHVybiEhYS5xdWVyeVNlbGVjdG9yKCJhW2hyZWZdIil9LHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMjQxIiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIubyJdLGFsbDpbXSxhbnk6WyJpbnRlcm5hbC1saW5rLXByZXNlbnQiLCJoZWFkZXItcHJlc2VudCIsImxhbmRtYXJrIl0sbm9uZTpbXX0se2lkOiJjaGVja2JveGdyb3VwIixzZWxlY3RvcjoiaW5wdXRbdHlwZT1jaGVja2JveF1bbmFtZV0iLHRhZ3M6WyJiZXN0LXByYWN0aWNlIl0sYWxsOltdLGFueTpbImdyb3VwLWxhYmVsbGVkYnkiLCJmaWVsZHNldCJdLG5vbmU6W119LHtpZDoiY29sb3ItY29udHJhc3QiLG1hdGNoZXM6ZnVuY3Rpb24oYSl7dmFyIGI9YS5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpLGM9YS50eXBlLGQ9ZG9jdW1lbnQ7aWYoInRydWUiPT09YS5nZXRBdHRyaWJ1dGUoImFyaWEtZGlzYWJsZWQiKSlyZXR1cm4hMTtpZigiSU5QVVQiPT09YilyZXR1cm5bImhpZGRlbiIsInJhbmdlIiwiY29sb3IiLCJjaGVja2JveCIsInJhZGlvIiwiaW1hZ2UiXS5pbmRleE9mKGMpPT09LTEmJiFhLmRpc2FibGVkO2lmKCJTRUxFQ1QiPT09YilyZXR1cm4hIWEub3B0aW9ucy5sZW5ndGgmJiFhLmRpc2FibGVkO2lmKCJURVhUQVJFQSI9PT1iKXJldHVybiFhLmRpc2FibGVkO2lmKCJPUFRJT04iPT09YilyZXR1cm4hMTtpZigiQlVUVE9OIj09PWImJmEuZGlzYWJsZWQpcmV0dXJuITE7aWYoIkxBQkVMIj09PWIpe3ZhciBlPWEuaHRtbEZvciYmZC5nZXRFbGVtZW50QnlJZChhLmh0bWxGb3IpO2lmKGUmJmUuZGlzYWJsZWQpcmV0dXJuITE7dmFyIGU9YS5xdWVyeVNlbGVjdG9yKCdpbnB1dDpub3QoW3R5cGU9ImhpZGRlbiJdKTpub3QoW3R5cGU9ImltYWdlIl0pOm5vdChbdHlwZT0iYnV0dG9uIl0pOm5vdChbdHlwZT0ic3VibWl0Il0pOm5vdChbdHlwZT0icmVzZXQiXSksIHNlbGVjdCwgdGV4dGFyZWEnKTtpZihlJiZlLmRpc2FibGVkKXJldHVybiExfWlmKGEuaWQpe3ZhciBlPWQucXVlcnlTZWxlY3RvcigiW2FyaWEtbGFiZWxsZWRieX49IitheGUuY29tbW9ucy51dGlscy5lc2NhcGVTZWxlY3RvcihhLmlkKSsiXSIpO2lmKGUmJmUuZGlzYWJsZWQpcmV0dXJuITF9aWYoIiI9PT1heGUuY29tbW9ucy50ZXh0LnZpc2libGUoYSwhMSwhMCkpcmV0dXJuITE7dmFyIGYsZyxoPWRvY3VtZW50LmNyZWF0ZVJhbmdlKCksaT1hLmNoaWxkTm9kZXMsaj1pLmxlbmd0aDtmb3IoZz0wO2c8ajtnKyspZj1pW2ddLDM9PT1mLm5vZGVUeXBlJiYiIiE9PWF4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoZi5ub2RlVmFsdWUpJiZoLnNlbGVjdE5vZGVDb250ZW50cyhmKTt2YXIgaz1oLmdldENsaWVudFJlY3RzKCk7Zm9yKGo9ay5sZW5ndGgsZz0wO2c8ajtnKyspaWYoYXhlLmNvbW1vbnMuZG9tLnZpc3VhbGx5T3ZlcmxhcHMoa1tnXSxhKSlyZXR1cm4hMDtyZXR1cm4hMX0sZXhjbHVkZUhpZGRlbjohMSxvcHRpb25zOntub1Njcm9sbDohMX0sdGFnczpbIndjYWcyYWEiLCJ3Y2FnMTQzIl0sYWxsOltdLGFueTpbImNvbG9yLWNvbnRyYXN0Il0sbm9uZTpbXX0se2lkOiJkZWZpbml0aW9uLWxpc3QiLHNlbGVjdG9yOiJkbDpub3QoW3JvbGVdKSIsdGFnczpbIndjYWcyYSIsIndjYWcxMzEiXSxhbGw6W10sYW55OltdLG5vbmU6WyJzdHJ1Y3R1cmVkLWRsaXRlbXMiLCJvbmx5LWRsaXRlbXMiXX0se2lkOiJkbGl0ZW0iLHNlbGVjdG9yOiJkZDpub3QoW3JvbGVdKSwgZHQ6bm90KFtyb2xlXSkiLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTMxIl0sYWxsOltdLGFueTpbImRsaXRlbSJdLG5vbmU6W119LHtpZDoiZG9jdW1lbnQtdGl0bGUiLHNlbGVjdG9yOiJodG1sIixtYXRjaGVzOmZ1bmN0aW9uKGEpe3JldHVybiB3aW5kb3cuc2VsZj09PXdpbmRvdy50b3B9LHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMjQyIl0sYWxsOltdLGFueTpbImRvYy1oYXMtdGl0bGUiXSxub25lOltdfSx7aWQ6ImR1cGxpY2F0ZS1pZCIsc2VsZWN0b3I6IltpZF0iLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYSIsIndjYWc0MTEiXSxhbGw6W10sYW55OlsiZHVwbGljYXRlLWlkIl0sbm9uZTpbXX0se2lkOiJlbXB0eS1oZWFkaW5nIixzZWxlY3RvcjonaDEsIGgyLCBoMywgaDQsIGg1LCBoNiwgW3JvbGU9ImhlYWRpbmciXScsZW5hYmxlZDohMCx0YWdzOlsiYmVzdC1wcmFjdGljZSJdLGFsbDpbXSxhbnk6WyJoYXMtdmlzaWJsZS10ZXh0Iiwicm9sZS1wcmVzZW50YXRpb24iLCJyb2xlLW5vbmUiXSxub25lOltdfSx7aWQ6ImZyYW1lLXRpdGxlLXVuaXF1ZSIsc2VsZWN0b3I6ImZyYW1lW3RpdGxlXTpub3QoW3RpdGxlPScnXSksIGlmcmFtZVt0aXRsZV06bm90KFt0aXRsZT0nJ10pIixtYXRjaGVzOmZ1bmN0aW9uKGEpe3ZhciBiPWEuZ2V0QXR0cmlidXRlKCJ0aXRsZSIpO3JldHVybiEhKGI/YXhlLmNvbW1vbnMudGV4dC5zYW5pdGl6ZShiKS50cmltKCk6IiIpfSx0YWdzOlsiYmVzdC1wcmFjdGljZSJdLGFsbDpbXSxhbnk6W10sbm9uZTpbInVuaXF1ZS1mcmFtZS10aXRsZSJdfSx7aWQ6ImZyYW1lLXRpdGxlIixzZWxlY3RvcjoiZnJhbWUsIGlmcmFtZSIsdGFnczpbIndjYWcyYSIsIndjYWcyNDEiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5pIl0sYWxsOltdLGFueTpbImFyaWEtbGFiZWwiLCJhcmlhLWxhYmVsbGVkYnkiLCJub24tZW1wdHktdGl0bGUiLCJyb2xlLXByZXNlbnRhdGlvbiIsInJvbGUtbm9uZSJdLG5vbmU6W119LHtpZDoiaGVhZGluZy1vcmRlciIsc2VsZWN0b3I6ImgxLGgyLGgzLGg0LGg1LGg2LFtyb2xlPWhlYWRpbmddIixlbmFibGVkOiExLHRhZ3M6WyJiZXN0LXByYWN0aWNlIl0sYWxsOltdLGFueTpbImhlYWRpbmctb3JkZXIiXSxub25lOltdfSx7aWQ6ImhyZWYtbm8taGFzaCIsc2VsZWN0b3I6ImFbaHJlZl0iLGVuYWJsZWQ6ITEsdGFnczpbImJlc3QtcHJhY3RpY2UiXSxhbGw6W10sYW55OlsiaHJlZi1uby1oYXNoIl0sbm9uZTpbXX0se2lkOiJodG1sLWhhcy1sYW5nIixzZWxlY3RvcjoiaHRtbCIsdGFnczpbIndjYWcyYSIsIndjYWczMTEiXSxhbGw6W10sYW55OlsiaGFzLWxhbmciXSxub25lOltdfSx7aWQ6Imh0bWwtbGFuZy12YWxpZCIsc2VsZWN0b3I6Imh0bWxbbGFuZ10iLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMzExIl0sYWxsOltdLGFueTpbXSxub25lOlt7b3B0aW9uczpbImFhIiwiYWIiLCJhZSIsImFmIiwiYWsiLCJhbSIsImFuIiwiYXIiLCJhcyIsImF2IiwiYXkiLCJheiIsImJhIiwiYmUiLCJiZyIsImJoIiwiYmkiLCJibSIsImJuIiwiYm8iLCJiciIsImJzIiwiY2EiLCJjZSIsImNoIiwiY28iLCJjciIsImNzIiwiY3UiLCJjdiIsImN5IiwiZGEiLCJkZSIsImR2IiwiZHoiLCJlZSIsImVsIiwiZW4iLCJlbyIsImVzIiwiZXQiLCJldSIsImZhIiwiZmYiLCJmaSIsImZqIiwiZm8iLCJmciIsImZ5IiwiZ2EiLCJnZCIsImdsIiwiZ24iLCJndSIsImd2IiwiaGEiLCJoZSIsImhpIiwiaG8iLCJociIsImh0IiwiaHUiLCJoeSIsImh6IiwiaWEiLCJpZCIsImllIiwiaWciLCJpaSIsImlrIiwiaW4iLCJpbyIsImlzIiwiaXQiLCJpdSIsIml3IiwiamEiLCJqaSIsImp2IiwianciLCJrYSIsImtnIiwia2kiLCJraiIsImtrIiwia2wiLCJrbSIsImtuIiwia28iLCJrciIsImtzIiwia3UiLCJrdiIsImt3Iiwia3kiLCJsYSIsImxiIiwibGciLCJsaSIsImxuIiwibG8iLCJsdCIsImx1IiwibHYiLCJtZyIsIm1oIiwibWkiLCJtayIsIm1sIiwibW4iLCJtbyIsIm1yIiwibXMiLCJtdCIsIm15IiwibmEiLCJuYiIsIm5kIiwibmUiLCJuZyIsIm5sIiwibm4iLCJubyIsIm5yIiwibnYiLCJueSIsIm9jIiwib2oiLCJvbSIsIm9yIiwib3MiLCJwYSIsInBpIiwicGwiLCJwcyIsInB0IiwicXUiLCJybSIsInJuIiwicm8iLCJydSIsInJ3Iiwic2EiLCJzYyIsInNkIiwic2UiLCJzZyIsInNoIiwic2kiLCJzayIsInNsIiwic20iLCJzbiIsInNvIiwic3EiLCJzciIsInNzIiwic3QiLCJzdSIsInN2Iiwic3ciLCJ0YSIsInRlIiwidGciLCJ0aCIsInRpIiwidGsiLCJ0bCIsInRuIiwidG8iLCJ0ciIsInRzIiwidHQiLCJ0dyIsInR5IiwidWciLCJ1ayIsInVyIiwidXoiLCJ2ZSIsInZpIiwidm8iLCJ3YSIsIndvIiwieGgiLCJ5aSIsInlvIiwiemEiLCJ6aCIsInp1Il0saWQ6InZhbGlkLWxhbmcifV19LHtpZDoiaW1hZ2UtYWx0IixzZWxlY3RvcjoiaW1nIix0YWdzOlsid2NhZzJhIiwid2NhZzExMSIsInNlY3Rpb241MDgiLCJzZWN0aW9uNTA4LjIyLmEiXSxhbGw6W10sYW55OlsiaGFzLWFsdCIsImFyaWEtbGFiZWwiLCJhcmlhLWxhYmVsbGVkYnkiLCJub24tZW1wdHktdGl0bGUiLCJyb2xlLXByZXNlbnRhdGlvbiIsInJvbGUtbm9uZSJdLG5vbmU6W119LHtpZDoiaW1hZ2UtcmVkdW5kYW50LWFsdCIsc2VsZWN0b3I6J2J1dHRvbiwgW3JvbGU9ImJ1dHRvbiJdLCBhW2hyZWZdLCBwLCBsaSwgdGQsIHRoJyx0YWdzOlsiYmVzdC1wcmFjdGljZSJdLGFsbDpbXSxhbnk6W10sbm9uZTpbImR1cGxpY2F0ZS1pbWctbGFiZWwiXX0se2lkOiJpbnB1dC1pbWFnZS1hbHQiLHNlbGVjdG9yOidpbnB1dFt0eXBlPSJpbWFnZSJdJyx0YWdzOlsid2NhZzJhIiwid2NhZzExMSIsInNlY3Rpb241MDgiLCJzZWN0aW9uNTA4LjIyLmEiXSxhbGw6W10sYW55Olsibm9uLWVtcHR5LWFsdCIsImFyaWEtbGFiZWwiLCJhcmlhLWxhYmVsbGVkYnkiLCJub24tZW1wdHktdGl0bGUiXSxub25lOltdfSx7aWQ6ImxhYmVsLXRpdGxlLW9ubHkiLHNlbGVjdG9yOiJpbnB1dDpub3QoW3R5cGU9J2hpZGRlbiddKTpub3QoW3R5cGU9J2ltYWdlJ10pOm5vdChbdHlwZT0nYnV0dG9uJ10pOm5vdChbdHlwZT0nc3VibWl0J10pOm5vdChbdHlwZT0ncmVzZXQnXSksIHNlbGVjdCwgdGV4dGFyZWEiLGVuYWJsZWQ6ITEsdGFnczpbImJlc3QtcHJhY3RpY2UiXSxhbGw6W10sYW55OltdLG5vbmU6WyJ0aXRsZS1vbmx5Il19LHtpZDoibGFiZWwiLHNlbGVjdG9yOiJpbnB1dDpub3QoW3R5cGU9J2hpZGRlbiddKTpub3QoW3R5cGU9J2ltYWdlJ10pOm5vdChbdHlwZT0nYnV0dG9uJ10pOm5vdChbdHlwZT0nc3VibWl0J10pOm5vdChbdHlwZT0ncmVzZXQnXSksIHNlbGVjdCwgdGV4dGFyZWEiLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMzMyIiwid2NhZzEzMSIsInNlY3Rpb241MDgiLCJzZWN0aW9uNTA4LjIyLm4iXSxhbGw6W10sYW55OlsiYXJpYS1sYWJlbCIsImFyaWEtbGFiZWxsZWRieSIsImltcGxpY2l0LWxhYmVsIiwiZXhwbGljaXQtbGFiZWwiLCJub24tZW1wdHktdGl0bGUiXSxub25lOlsiaGVscC1zYW1lLWFzLWxhYmVsIiwibXVsdGlwbGUtbGFiZWwiXX0se2lkOiJsYXlvdXQtdGFibGUiLHNlbGVjdG9yOiJ0YWJsZSIsbWF0Y2hlczpmdW5jdGlvbihhKXtyZXR1cm4hYXhlLmNvbW1vbnMudGFibGUuaXNEYXRhVGFibGUoYSl9LHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTMxIl0sYWxsOltdLGFueTpbXSxub25lOlsiaGFzLXRoIiwiaGFzLWNhcHRpb24iLCJoYXMtc3VtbWFyeSJdfSx7aWQ6ImxpbmstaW4tdGV4dC1ibG9jayIsc2VsZWN0b3I6ImFbaHJlZl06bm90KFtyb2xlXSksICpbcm9sZT1saW5rXSIsbWF0Y2hlczpmdW5jdGlvbihhKXt2YXIgYj1heGUuY29tbW9ucy50ZXh0LnNhbml0aXplKGEudGV4dENvbnRlbnQpO3JldHVybiEhYiYmKCEhYXhlLmNvbW1vbnMuZG9tLmlzVmlzaWJsZShhLCExKSYmYXhlLmNvbW1vbnMuZG9tLmlzSW5UZXh0QmxvY2soYSkpfSxleGNsdWRlSGlkZGVuOiExLGVuYWJsZWQ6ITEsdGFnczpbImV4cGVyaW1lbnRhbCIsIndjYWcyYSIsIndjYWcxNDEiXSxhbGw6WyJsaW5rLWluLXRleHQtYmxvY2siXSxhbnk6W10sbm9uZTpbXX0se2lkOiJsaW5rLW5hbWUiLHNlbGVjdG9yOidhW2hyZWZdOm5vdChbcm9sZT0iYnV0dG9uIl0pLCBbcm9sZT1saW5rXVtocmVmXScsdGFnczpbIndjYWcyYSIsIndjYWcxMTEiLCJ3Y2FnNDEyIiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIuYSJdLGFsbDpbXSxhbnk6WyJoYXMtdmlzaWJsZS10ZXh0IiwiYXJpYS1sYWJlbCIsImFyaWEtbGFiZWxsZWRieSIsInJvbGUtcHJlc2VudGF0aW9uIiwicm9sZS1ub25lIl0sbm9uZTpbImZvY3VzYWJsZS1uby1uYW1lIl19LHtpZDoibGlzdCIsc2VsZWN0b3I6InVsOm5vdChbcm9sZV0pLCBvbDpub3QoW3JvbGVdKSIsdGFnczpbIndjYWcyYSIsIndjYWcxMzEiXSxhbGw6W10sYW55OltdLG5vbmU6WyJvbmx5LWxpc3RpdGVtcyJdfSx7aWQ6Imxpc3RpdGVtIixzZWxlY3RvcjoibGk6bm90KFtyb2xlXSkiLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTMxIl0sYWxsOltdLGFueTpbImxpc3RpdGVtIl0sbm9uZTpbXX0se2lkOiJtYXJxdWVlIixzZWxlY3RvcjoibWFycXVlZSIsZXhjbHVkZUhpZGRlbjohMSx0YWdzOlsid2NhZzJhIiwid2NhZzIyMiJdLGFsbDpbXSxhbnk6W10sbm9uZTpbImlzLW9uLXNjcmVlbiJdfSx7aWQ6Im1ldGEtcmVmcmVzaCIsc2VsZWN0b3I6J21ldGFbaHR0cC1lcXVpdj0icmVmcmVzaCJdJyxleGNsdWRlSGlkZGVuOiExLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMmFhYSIsIndjYWcyMjEiLCJ3Y2FnMjI0Iiwid2NhZzMyNSJdLGFsbDpbXSxhbnk6WyJtZXRhLXJlZnJlc2giXSxub25lOltdfSx7aWQ6Im1ldGEtdmlld3BvcnQtbGFyZ2UiLHNlbGVjdG9yOidtZXRhW25hbWU9InZpZXdwb3J0Il0nLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbImJlc3QtcHJhY3RpY2UiXSxhbGw6W10sYW55Olt7b3B0aW9uczp7c2NhbGVNaW5pbXVtOjUsbG93ZXJCb3VuZDoyfSxpZDoibWV0YS12aWV3cG9ydC1sYXJnZSJ9XSxub25lOltdfSx7aWQ6Im1ldGEtdmlld3BvcnQiLHNlbGVjdG9yOidtZXRhW25hbWU9InZpZXdwb3J0Il0nLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYWEiLCJ3Y2FnMTQ0Il0sYWxsOltdLGFueTpbe29wdGlvbnM6e3NjYWxlTWluaW11bToyfSxpZDoibWV0YS12aWV3cG9ydCJ9XSxub25lOltdfSx7aWQ6Im9iamVjdC1hbHQiLHNlbGVjdG9yOiJvYmplY3QiLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTExIiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIuYSJdLGFsbDpbXSxhbnk6WyJoYXMtdmlzaWJsZS10ZXh0IiwiYXJpYS1sYWJlbCIsImFyaWEtbGFiZWxsZWRieSIsIm5vbi1lbXB0eS10aXRsZSJdLG5vbmU6W119LHtpZDoicmFkaW9ncm91cCIsc2VsZWN0b3I6ImlucHV0W3R5cGU9cmFkaW9dW25hbWVdIix0YWdzOlsiYmVzdC1wcmFjdGljZSJdLGFsbDpbXSxhbnk6WyJncm91cC1sYWJlbGxlZGJ5IiwiZmllbGRzZXQiXSxub25lOltdfSx7aWQ6InJlZ2lvbiIsc2VsZWN0b3I6Imh0bWwiLHBhZ2VMZXZlbDohMCxlbmFibGVkOiExLHRhZ3M6WyJiZXN0LXByYWN0aWNlIl0sYWxsOltdLGFueTpbInJlZ2lvbiJdLG5vbmU6W119LHtpZDoic2NvcGUtYXR0ci12YWxpZCIsc2VsZWN0b3I6InRkW3Njb3BlXSwgdGhbc2NvcGVdIixlbmFibGVkOiEwLHRhZ3M6WyJiZXN0LXByYWN0aWNlIl0sYWxsOlsiaHRtbDUtc2NvcGUiLCJzY29wZS12YWx1ZSJdLGFueTpbXSxub25lOltdfSx7aWQ6InNlcnZlci1zaWRlLWltYWdlLW1hcCIsc2VsZWN0b3I6ImltZ1tpc21hcF0iLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMjExIiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIuZiJdLGFsbDpbXSxhbnk6W10sbm9uZTpbImV4aXN0cyJdfSx7aWQ6InNraXAtbGluayIsc2VsZWN0b3I6ImFbaHJlZl0iLHBhZ2VMZXZlbDohMCxlbmFibGVkOiExLHRhZ3M6WyJiZXN0LXByYWN0aWNlIl0sYWxsOltdLGFueTpbInNraXAtbGluayJdLG5vbmU6W119LHtpZDoidGFiaW5kZXgiLHNlbGVjdG9yOiJbdGFiaW5kZXhdIix0YWdzOlsiYmVzdC1wcmFjdGljZSJdLGFsbDpbXSxhbnk6WyJ0YWJpbmRleCJdLG5vbmU6W119LHtpZDoidGFibGUtZHVwbGljYXRlLW5hbWUiLHNlbGVjdG9yOiJ0YWJsZSIsdGFnczpbImJlc3QtcHJhY3RpY2UiXSxhbGw6W10sYW55OltdLG5vbmU6WyJzYW1lLWNhcHRpb24tc3VtbWFyeSJdfSx7aWQ6InRhYmxlLWZha2UtY2FwdGlvbiIsc2VsZWN0b3I6InRhYmxlIixtYXRjaGVzOmZ1bmN0aW9uKGEpe3JldHVybiBheGUuY29tbW9ucy50YWJsZS5pc0RhdGFUYWJsZShhKX0sdGFnczpbImV4cGVyaW1lbnRhbCIsIndjYWcyYSIsIndjYWcxMzEiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5nIl0sYWxsOlsiY2FwdGlvbi1mYWtlZCJdLGFueTpbXSxub25lOltdfSx7aWQ6InRkLWhhcy1oZWFkZXIiLHNlbGVjdG9yOiJ0YWJsZSIsbWF0Y2hlczpmdW5jdGlvbihhKXtpZihheGUuY29tbW9ucy50YWJsZS5pc0RhdGFUYWJsZShhKSl7dmFyIGI9YXhlLmNvbW1vbnMudGFibGUudG9BcnJheShhKTtyZXR1cm4gYi5sZW5ndGg+PTMmJmJbMF0ubGVuZ3RoPj0zJiZiWzFdLmxlbmd0aD49MyYmYlsyXS5sZW5ndGg+PTN9cmV0dXJuITF9LHRhZ3M6WyJleHBlcmltZW50YWwiLCJ3Y2FnMmEiLCJ3Y2FnMTMxIiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIuZyJdLGFsbDpbInRkLWhhcy1oZWFkZXIiXSxhbnk6W10sbm9uZTpbXX0se2lkOiJ0ZC1oZWFkZXJzLWF0dHIiLHNlbGVjdG9yOiJ0YWJsZSIsdGFnczpbIndjYWcyYSIsIndjYWcxMzEiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5nIl0sYWxsOlsidGQtaGVhZGVycy1hdHRyIl0sYW55OltdLG5vbmU6W119LHtpZDoidGgtaGFzLWRhdGEtY2VsbHMiLHNlbGVjdG9yOiJ0YWJsZSIsbWF0Y2hlczpmdW5jdGlvbihhKXtyZXR1cm4gYXhlLmNvbW1vbnMudGFibGUuaXNEYXRhVGFibGUoYSl9LHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTMxIiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIuZyJdLGFsbDpbInRoLWhhcy1kYXRhLWNlbGxzIl0sYW55OltdLG5vbmU6W119LHtpZDoidmFsaWQtbGFuZyIsc2VsZWN0b3I6IltsYW5nXTpub3QoaHRtbCksIFt4bWxcXDpsYW5nXTpub3QoaHRtbCkiLHRhZ3M6WyJ3Y2FnMmFhIiwid2NhZzMxMiJdLGFsbDpbXSxhbnk6W10sbm9uZTpbe29wdGlvbnM6WyJhYSIsImFiIiwiYWUiLCJhZiIsImFrIiwiYW0iLCJhbiIsImFyIiwiYXMiLCJhdiIsImF5IiwiYXoiLCJiYSIsImJlIiwiYmciLCJiaCIsImJpIiwiYm0iLCJibiIsImJvIiwiYnIiLCJicyIsImNhIiwiY2UiLCJjaCIsImNvIiwiY3IiLCJjcyIsImN1IiwiY3YiLCJjeSIsImRhIiwiZGUiLCJkdiIsImR6IiwiZWUiLCJlbCIsImVuIiwiZW8iLCJlcyIsImV0IiwiZXUiLCJmYSIsImZmIiwiZmkiLCJmaiIsImZvIiwiZnIiLCJmeSIsImdhIiwiZ2QiLCJnbCIsImduIiwiZ3UiLCJndiIsImhhIiwiaGUiLCJoaSIsImhvIiwiaHIiLCJodCIsImh1IiwiaHkiLCJoeiIsImlhIiwiaWQiLCJpZSIsImlnIiwiaWkiLCJpayIsImluIiwiaW8iLCJpcyIsIml0IiwiaXUiLCJpdyIsImphIiwiamkiLCJqdiIsImp3Iiwia2EiLCJrZyIsImtpIiwia2oiLCJrayIsImtsIiwia20iLCJrbiIsImtvIiwia3IiLCJrcyIsImt1Iiwia3YiLCJrdyIsImt5IiwibGEiLCJsYiIsImxnIiwibGkiLCJsbiIsImxvIiwibHQiLCJsdSIsImx2IiwibWciLCJtaCIsIm1pIiwibWsiLCJtbCIsIm1uIiwibW8iLCJtciIsIm1zIiwibXQiLCJteSIsIm5hIiwibmIiLCJuZCIsIm5lIiwibmciLCJubCIsIm5uIiwibm8iLCJuciIsIm52IiwibnkiLCJvYyIsIm9qIiwib20iLCJvciIsIm9zIiwicGEiLCJwaSIsInBsIiwicHMiLCJwdCIsInF1Iiwicm0iLCJybiIsInJvIiwicnUiLCJydyIsInNhIiwic2MiLCJzZCIsInNlIiwic2ciLCJzaCIsInNpIiwic2siLCJzbCIsInNtIiwic24iLCJzbyIsInNxIiwic3IiLCJzcyIsInN0Iiwic3UiLCJzdiIsInN3IiwidGEiLCJ0ZSIsInRnIiwidGgiLCJ0aSIsInRrIiwidGwiLCJ0biIsInRvIiwidHIiLCJ0cyIsInR0IiwidHciLCJ0eSIsInVnIiwidWsiLCJ1ciIsInV6IiwidmUiLCJ2aSIsInZvIiwid2EiLCJ3byIsInhoIiwieWkiLCJ5byIsInphIiwiemgiLCJ6dSJdLGlkOiJ2YWxpZC1sYW5nIn1dfSx7aWQ6InZpZGVvLWNhcHRpb24iLHNlbGVjdG9yOiJ2aWRlbyIsZXhjbHVkZUhpZGRlbjohMSx0YWdzOlsid2NhZzJhIiwid2NhZzEyMiIsIndjYWcxMjMiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5hIl0sYWxsOltdLGFueTpbXSxub25lOlsiY2FwdGlvbiJdfSx7aWQ6InZpZGVvLWRlc2NyaXB0aW9uIixzZWxlY3RvcjoidmlkZW8iLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYWEiLCJ3Y2FnMTI1Iiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIuYiJdLGFsbDpbXSxhbnk6W10sbm9uZTpbImRlc2NyaXB0aW9uIl19XSxjaGVja3M6W3tpZDoiYWJzdHJhY3Ryb2xlIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiJhYnN0cmFjdCI9PT1heGUuY29tbW9ucy5hcmlhLmdldFJvbGVUeXBlKGEuZ2V0QXR0cmlidXRlKCJyb2xlIikpfX0se2lkOiJhcmlhLWFsbG93ZWQtYXR0ciIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYyxkLGUsZj1bXSxnPWEuZ2V0QXR0cmlidXRlKCJyb2xlIiksaD1hLmF0dHJpYnV0ZXM7aWYoZ3x8KGc9YXhlLmNvbW1vbnMuYXJpYS5pbXBsaWNpdFJvbGUoYSkpLGU9YXhlLmNvbW1vbnMuYXJpYS5hbGxvd2VkQXR0cihnKSxnJiZlKWZvcih2YXIgaT0wLGo9aC5sZW5ndGg7aTxqO2krKyljPWhbaV0sZD1jLm5hbWUsYXhlLmNvbW1vbnMuYXJpYS52YWxpZGF0ZUF0dHIoZCkmJmUuaW5kZXhPZihkKT09PS0xJiZmLnB1c2goZCsnPSInK2Mubm9kZVZhbHVlKyciJyk7cmV0dXJuIWYubGVuZ3RofHwodGhpcy5kYXRhKGYpLCExKX19LHtpZDoiaW52YWxpZHJvbGUiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuIWF4ZS5jb21tb25zLmFyaWEuaXNWYWxpZFJvbGUoYS5nZXRBdHRyaWJ1dGUoInJvbGUiKSl9fSx7aWQ6ImFyaWEtcmVxdWlyZWQtYXR0ciIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1bXTtpZihhLmhhc0F0dHJpYnV0ZXMoKSl7dmFyIGQsZT1hLmdldEF0dHJpYnV0ZSgicm9sZSIpLGY9YXhlLmNvbW1vbnMuYXJpYS5yZXF1aXJlZEF0dHIoZSk7aWYoZSYmZilmb3IodmFyIGc9MCxoPWYubGVuZ3RoO2c8aDtnKyspZD1mW2ddLGEuZ2V0QXR0cmlidXRlKGQpfHxjLnB1c2goZCl9cmV0dXJuIWMubGVuZ3RofHwodGhpcy5kYXRhKGMpLCExKX19LHtpZDoiYXJpYS1yZXF1aXJlZC1jaGlsZHJlbiIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtmdW5jdGlvbiBjKGEsYixjKXtpZihudWxsPT09YSlyZXR1cm4hMTt2YXIgZD1nKGIpLGU9Wydbcm9sZT0iJytiKyciXSddO3JldHVybiBkJiYoZT1lLmNvbmNhdChkKSksZT1lLmpvaW4oIiwiKSxjP2goYSxlKXx8ISFhLnF1ZXJ5U2VsZWN0b3IoZSk6ISFhLnF1ZXJ5U2VsZWN0b3IoZSl9ZnVuY3Rpb24gZChhLGIpe3ZhciBkLGU7Zm9yKGQ9MCxlPWEubGVuZ3RoO2Q8ZTtkKyspaWYobnVsbCE9PWFbZF0mJmMoYVtkXSxiLCEwKSlyZXR1cm4hMDtyZXR1cm4hMX1mdW5jdGlvbiBlKGEsYixlKXt2YXIgZixnPWIubGVuZ3RoLGg9W10saj1pKGEsImFyaWEtb3ducyIpO2ZvcihmPTA7ZjxnO2YrKyl7dmFyIGs9YltmXTtpZihjKGEsayl8fGQoaixrKSl7aWYoIWUpcmV0dXJuIG51bGx9ZWxzZSBlJiZoLnB1c2goayl9cmV0dXJuIGgubGVuZ3RoP2g6IWUmJmIubGVuZ3RoP2I6bnVsbH12YXIgZj1heGUuY29tbW9ucy5hcmlhLnJlcXVpcmVkT3duZWQsZz1heGUuY29tbW9ucy5hcmlhLmltcGxpY2l0Tm9kZXMsaD1heGUuY29tbW9ucy51dGlscy5tYXRjaGVzU2VsZWN0b3IsaT1heGUuY29tbW9ucy5kb20uaWRyZWZzLGo9YS5nZXRBdHRyaWJ1dGUoInJvbGUiKSxrPWYoaik7aWYoIWspcmV0dXJuITA7dmFyIGw9ITEsbT1rLm9uZTtpZighbSl7dmFyIGw9ITA7bT1rLmFsbH12YXIgbj1lKGEsbSxsKTtyZXR1cm4hbnx8KHRoaXMuZGF0YShuKSwhMSl9fSx7aWQ6ImFyaWEtcmVxdWlyZWQtcGFyZW50IixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2Z1bmN0aW9uIGMoYSl7dmFyIGI9YXhlLmNvbW1vbnMuYXJpYS5pbXBsaWNpdE5vZGVzKGEpfHxbXTtyZXR1cm4gYi5jb25jYXQoJ1tyb2xlPSInK2ErJyJdJykuam9pbigiLCIpfWZ1bmN0aW9uIGQoYSxiLGQpe3ZhciBlLGYsZz1hLmdldEF0dHJpYnV0ZSgicm9sZSIpLGg9W107aWYoYnx8KGI9YXhlLmNvbW1vbnMuYXJpYS5yZXF1aXJlZENvbnRleHQoZykpLCFiKXJldHVybiBudWxsO2ZvcihlPTAsZj1iLmxlbmd0aDtlPGY7ZSsrKXtpZihkJiZheGUudXRpbHMubWF0Y2hlc1NlbGVjdG9yKGEsYyhiW2VdKSkpcmV0dXJuIG51bGw7aWYoYXhlLmNvbW1vbnMuZG9tLmZpbmRVcChhLGMoYltlXSkpKXJldHVybiBudWxsO2gucHVzaChiW2VdKX1yZXR1cm4gaH1mdW5jdGlvbiBlKGEpe2Zvcih2YXIgYj1bXSxjPW51bGw7YTspYS5pZCYmKGM9ZG9jdW1lbnQucXVlcnlTZWxlY3RvcigiW2FyaWEtb3duc349IitheGUuY29tbW9ucy51dGlscy5lc2NhcGVTZWxlY3RvcihhLmlkKSsiXSIpLGMmJmIucHVzaChjKSksYT1hLnBhcmVudE5vZGU7cmV0dXJuIGIubGVuZ3RoP2I6bnVsbH12YXIgZj1kKGEpO2lmKCFmKXJldHVybiEwO3ZhciBnPWUoYSk7aWYoZylmb3IodmFyIGg9MCxpPWcubGVuZ3RoO2g8aTtoKyspaWYoZj1kKGdbaF0sZiwhMCksIWYpcmV0dXJuITA7cmV0dXJuIHRoaXMuZGF0YShmKSwhMX19LHtpZDoiYXJpYS12YWxpZC1hdHRyLXZhbHVlIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2I9QXJyYXkuaXNBcnJheShiKT9iOltdO2Zvcih2YXIgYyxkLGU9W10sZj0vXmFyaWEtLyxnPWEuYXR0cmlidXRlcyxoPTAsaT1nLmxlbmd0aDtoPGk7aCsrKWM9Z1toXSxkPWMubmFtZSxiLmluZGV4T2YoZCk9PT0tMSYmZi50ZXN0KGQpJiYhYXhlLmNvbW1vbnMuYXJpYS52YWxpZGF0ZUF0dHJWYWx1ZShhLGQpJiZlLnB1c2goZCsnPSInK2Mubm9kZVZhbHVlKyciJyk7cmV0dXJuIWUubGVuZ3RofHwodGhpcy5kYXRhKGUpLCExKX0sb3B0aW9uczpbXX0se2lkOiJhcmlhLXZhbGlkLWF0dHIiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7Yj1BcnJheS5pc0FycmF5KGIpP2I6W107Zm9yKHZhciBjLGQ9W10sZT0vXmFyaWEtLyxmPWEuYXR0cmlidXRlcyxnPTAsaD1mLmxlbmd0aDtnPGg7ZysrKWM9ZltnXS5uYW1lLGIuaW5kZXhPZihjKT09PS0xJiZlLnRlc3QoYykmJiFheGUuY29tbW9ucy5hcmlhLnZhbGlkYXRlQXR0cihjKSYmZC5wdXNoKGMpO3JldHVybiFkLmxlbmd0aHx8KHRoaXMuZGF0YShkKSwhMSl9LG9wdGlvbnM6W119LHtpZDoiY29sb3ItY29udHJhc3QiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7aWYoIWF4ZS5jb21tb25zLmRvbS5pc1Zpc2libGUoYSwhMSkpcmV0dXJuITA7dmFyIGM9ISEoYnx8e30pLm5vU2Nyb2xsLGQ9W10sZT1heGUuY29tbW9ucy5jb2xvci5nZXRCYWNrZ3JvdW5kQ29sb3IoYSxkLGMpLGY9YXhlLmNvbW1vbnMuY29sb3IuZ2V0Rm9yZWdyb3VuZENvbG9yKGEsYyk7aWYobnVsbCE9PWYmJm51bGwhPT1lKXt2YXIgZz13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShhKSxoPXBhcnNlRmxvYXQoZy5nZXRQcm9wZXJ0eVZhbHVlKCJmb250LXNpemUiKSksaT1nLmdldFByb3BlcnR5VmFsdWUoImZvbnQtd2VpZ2h0Iiksaj1bImJvbGQiLCJib2xkZXIiLCI2MDAiLCI3MDAiLCI4MDAiLCI5MDAiXS5pbmRleE9mKGkpIT09LTEsaz1heGUuY29tbW9ucy5jb2xvci5oYXNWYWxpZENvbnRyYXN0UmF0aW8oZSxmLGgsaik7cmV0dXJuIHRoaXMuZGF0YSh7ZmdDb2xvcjpmLnRvSGV4U3RyaW5nKCksYmdDb2xvcjplLnRvSGV4U3RyaW5nKCksY29udHJhc3RSYXRpbzprLmNvbnRyYXN0UmF0aW8udG9GaXhlZCgyKSxmb250U2l6ZTooNzIqaC85NikudG9GaXhlZCgxKSsicHQiLGZvbnRXZWlnaHQ6aj8iYm9sZCI6Im5vcm1hbCJ9KSxrLmlzVmFsaWR8fHRoaXMucmVsYXRlZE5vZGVzKGQpLGsuaXNWYWxpZH19fSx7aWQ6ImxpbmstaW4tdGV4dC1ibG9jayIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtmdW5jdGlvbiBjKGEsYil7dmFyIGM9YS5nZXRSZWxhdGl2ZUx1bWluYW5jZSgpLGQ9Yi5nZXRSZWxhdGl2ZUx1bWluYW5jZSgpO3JldHVybihNYXRoLm1heChjLGQpKy4wNSkvKE1hdGgubWluKGMsZCkrLjA1KX1mdW5jdGlvbiBkKGEpe3ZhciBiPXdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGEpLmdldFByb3BlcnR5VmFsdWUoImRpc3BsYXkiKTtyZXR1cm4gZi5pbmRleE9mKGIpIT09LTF8fCJ0YWJsZS0iPT09Yi5zdWJzdHIoMCw2KX12YXIgZT1heGUuY29tbW9ucy5jb2xvcixmPVsiYmxvY2siLCJsaXN0LWl0ZW0iLCJ0YWJsZSIsImZsZXgiLCJncmlkIiwiaW5saW5lLWJsb2NrIl07aWYoZChhKSlyZXR1cm4hMTtmb3IodmFyIGc9YS5wYXJlbnROb2RlOzE9PT1nLm5vZGVUeXBlJiYhZChnKTspZz1nLnBhcmVudE5vZGU7aWYoZS5lbGVtZW50SXNEaXN0aW5jdChhLGcpKXJldHVybiEwO3ZhciBoLGk7aWYoaD1lLmdldEZvcmVncm91bmRDb2xvcihhKSxpPWUuZ2V0Rm9yZWdyb3VuZENvbG9yKGcpLGgmJmkpe3ZhciBqPWMoaCxpKTtpZigxPT09ailyZXR1cm4hMDtpZighKGo+PTMpJiYoaD1lLmdldEJhY2tncm91bmRDb2xvcihhKSxpPWUuZ2V0QmFja2dyb3VuZENvbG9yKGcpLGgmJmkmJiEoYyhoLGkpPj0zKSkpcmV0dXJuITF9fX0se2lkOiJmaWVsZHNldCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtmdW5jdGlvbiBjKGEsYil7cmV0dXJuIGF4ZS5jb21tb25zLnV0aWxzLnRvQXJyYXkoYS5xdWVyeVNlbGVjdG9yQWxsKCdzZWxlY3QsdGV4dGFyZWEsYnV0dG9uLGlucHV0Om5vdChbbmFtZT0iJytiKyciXSk6bm90KFt0eXBlPSJoaWRkZW4iXSknKSl9ZnVuY3Rpb24gZChhLGIpe3ZhciBkPWEuZmlyc3RFbGVtZW50Q2hpbGQ7aWYoIWR8fCJMRUdFTkQiIT09ZC5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpKXJldHVybiBpLnJlbGF0ZWROb2RlcyhbYV0pLGg9Im5vLWxlZ2VuZCIsITE7aWYoIWF4ZS5jb21tb25zLnRleHQuYWNjZXNzaWJsZVRleHQoZCkpcmV0dXJuIGkucmVsYXRlZE5vZGVzKFtkXSksaD0iZW1wdHktbGVnZW5kIiwhMTt2YXIgZT1jKGEsYik7cmV0dXJuIWUubGVuZ3RofHwoaS5yZWxhdGVkTm9kZXMoZSksaD0ibWl4ZWQtaW5wdXRzIiwhMSl9ZnVuY3Rpb24gZShhLGIpe3ZhciBkPWF4ZS5jb21tb25zLmRvbS5pZHJlZnMoYSwiYXJpYS1sYWJlbGxlZGJ5Iikuc29tZShmdW5jdGlvbihhKXtyZXR1cm4gYSYmYXhlLmNvbW1vbnMudGV4dC5hY2Nlc3NpYmxlVGV4dChhKX0pLGU9YS5nZXRBdHRyaWJ1dGUoImFyaWEtbGFiZWwiKTtpZighKGR8fGUmJmF4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoZSkpKXJldHVybiBpLnJlbGF0ZWROb2RlcyhhKSxoPSJuby1ncm91cC1sYWJlbCIsITE7dmFyIGY9YyhhLGIpO3JldHVybiFmLmxlbmd0aHx8KGkucmVsYXRlZE5vZGVzKGYpLGg9Imdyb3VwLW1peGVkLWlucHV0cyIsITEpfWZ1bmN0aW9uIGYoYSxiKXtyZXR1cm4gYXhlLmNvbW1vbnMudXRpbHMudG9BcnJheShhKS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuIGEhPT1ifSl9ZnVuY3Rpb24gZyhiKXt2YXIgYz1heGUuY29tbW9ucy51dGlscy5lc2NhcGVTZWxlY3RvcihhLm5hbWUpLGc9ZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnaW5wdXRbdHlwZT0iJytheGUuY29tbW9ucy51dGlscy5lc2NhcGVTZWxlY3RvcihhLnR5cGUpKyciXVtuYW1lPSInK2MrJyJdJyk7aWYoZy5sZW5ndGg8MilyZXR1cm4hMDt2YXIgaj1heGUuY29tbW9ucy5kb20uZmluZFVwKGIsImZpZWxkc2V0Iiksaz1heGUuY29tbW9ucy5kb20uZmluZFVwKGIsJ1tyb2xlPSJncm91cCJdJysoInJhZGlvIj09PWEudHlwZT8nLFtyb2xlPSJyYWRpb2dyb3VwIl0nOiIiKSk7cmV0dXJuIGt8fGo/aj9kKGosYyk6ZShrLGMpOihoPSJuby1ncm91cCIsaS5yZWxhdGVkTm9kZXMoZihnLGIpKSwhMSl9dmFyIGgsaT10aGlzLGo9e25hbWU6YS5nZXRBdHRyaWJ1dGUoIm5hbWUiKSx0eXBlOmEuZ2V0QXR0cmlidXRlKCJ0eXBlIil9LGs9ZyhhKTtyZXR1cm4ga3x8KGouZmFpbHVyZUNvZGU9aCksdGhpcy5kYXRhKGopLGt9LGFmdGVyOmZ1bmN0aW9uKGEsYil7dmFyIGM9e307cmV0dXJuIGEuZmlsdGVyKGZ1bmN0aW9uKGEpe2lmKGEucmVzdWx0KXJldHVybiEwO3ZhciBiPWEuZGF0YTtpZihiKXtpZihjW2IudHlwZV09Y1tiLnR5cGVdfHx7fSwhY1tiLnR5cGVdW2IubmFtZV0pcmV0dXJuIGNbYi50eXBlXVtiLm5hbWVdPVtiXSwhMDt2YXIgZD1jW2IudHlwZV1bYi5uYW1lXS5zb21lKGZ1bmN0aW9uKGEpe3JldHVybiBhLmZhaWx1cmVDb2RlPT09Yi5mYWlsdXJlQ29kZX0pO3JldHVybiBkfHxjW2IudHlwZV1bYi5uYW1lXS5wdXNoKGIpLCFkfXJldHVybiExfSl9fSx7aWQ6Imdyb3VwLWxhYmVsbGVkYnkiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dGhpcy5kYXRhKHtuYW1lOmEuZ2V0QXR0cmlidXRlKCJuYW1lIiksdHlwZTphLmdldEF0dHJpYnV0ZSgidHlwZSIpfSk7dmFyIGM9ZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnaW5wdXRbdHlwZT0iJytheGUuY29tbW9ucy51dGlscy5lc2NhcGVTZWxlY3RvcihhLnR5cGUpKyciXVtuYW1lPSInK2F4ZS5jb21tb25zLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEubmFtZSkrJyJdJyk7cmV0dXJuIGMubGVuZ3RoPD0xfHwwIT09W10ubWFwLmNhbGwoYyxmdW5jdGlvbihhKXt2YXIgYj1hLmdldEF0dHJpYnV0ZSgiYXJpYS1sYWJlbGxlZGJ5Iik7cmV0dXJuIGI/Yi5zcGxpdCgvXHMrLyk6W119KS5yZWR1Y2UoZnVuY3Rpb24oYSxiKXtyZXR1cm4gYS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuIGIuaW5kZXhPZihhKSE9PS0xfSl9KS5maWx0ZXIoZnVuY3Rpb24oYSl7dmFyIGI9ZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoYSk7cmV0dXJuIGImJmF4ZS5jb21tb25zLnRleHQuYWNjZXNzaWJsZVRleHQoYil9KS5sZW5ndGh9LGFmdGVyOmZ1bmN0aW9uKGEsYil7dmFyIGM9e307cmV0dXJuIGEuZmlsdGVyKGZ1bmN0aW9uKGEpe3ZhciBiPWEuZGF0YTtyZXR1cm4hKCFifHwoY1tiLnR5cGVdPWNbYi50eXBlXXx8e30sY1tiLnR5cGVdW2IubmFtZV0pKSYmKGNbYi50eXBlXVtiLm5hbWVdPSEwLCEwKX0pfX0se2lkOiJhY2Nlc3NrZXlzIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiBheGUuY29tbW9ucy5kb20uaXNWaXNpYmxlKGEsITEpJiYodGhpcy5kYXRhKGEuZ2V0QXR0cmlidXRlKCJhY2Nlc3NrZXkiKSksdGhpcy5yZWxhdGVkTm9kZXMoW2FdKSksITB9LGFmdGVyOmZ1bmN0aW9uKGEsYil7dmFyIGM9e307cmV0dXJuIGEuZmlsdGVyKGZ1bmN0aW9uKGEpe2lmKCFhLmRhdGEpcmV0dXJuITE7dmFyIGI9YS5kYXRhLnRvVXBwZXJDYXNlKCk7cmV0dXJuIGNbYl0/KGNbYl0ucmVsYXRlZE5vZGVzLnB1c2goYS5yZWxhdGVkTm9kZXNbMF0pLCExKTooY1tiXT1hLGEucmVsYXRlZE5vZGVzPVtdLCEwKX0pLm1hcChmdW5jdGlvbihhKXtyZXR1cm4gYS5yZXN1bHQ9ISFhLnJlbGF0ZWROb2Rlcy5sZW5ndGgsYX0pfX0se2lkOiJmb2N1c2FibGUtbm8tbmFtZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1hLmdldEF0dHJpYnV0ZSgidGFiaW5kZXgiKSxkPWF4ZS5jb21tb25zLmRvbS5pc0ZvY3VzYWJsZShhKSYmYz4tMTtyZXR1cm4hIWQmJiFheGUuY29tbW9ucy50ZXh0LmFjY2Vzc2libGVUZXh0KGEpfX0se2lkOiJ0YWJpbmRleCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4gYS50YWJJbmRleDw9MH19LHtpZDoiZHVwbGljYXRlLWltZy1sYWJlbCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1hLnF1ZXJ5U2VsZWN0b3JBbGwoImltZyIpLGQ9YXhlLmNvbW1vbnMudGV4dC52aXNpYmxlKGEsITApLnRvTG93ZXJDYXNlKCk7aWYoIiI9PT1kKXJldHVybiExO2Zvcih2YXIgZT0wLGY9Yy5sZW5ndGg7ZTxmO2UrKyl7dmFyIGc9Y1tlXSxoPWF4ZS5jb21tb25zLnRleHQuYWNjZXNzaWJsZVRleHQoZykudG9Mb3dlckNhc2UoKTtpZihoPT09ZCYmInByZXNlbnRhdGlvbiIhPT1nLmdldEF0dHJpYnV0ZSgicm9sZSIpJiZheGUuY29tbW9ucy5kb20uaXNWaXNpYmxlKGcpKXJldHVybiEwfXJldHVybiExfX0se2lkOiJleHBsaWNpdC1sYWJlbCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtpZihhLmlkKXt2YXIgYz1kb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdsYWJlbFtmb3I9IicrYXhlLmNvbW1vbnMudXRpbHMuZXNjYXBlU2VsZWN0b3IoYS5pZCkrJyJdJyk7aWYoYylyZXR1cm4hIWF4ZS5jb21tb25zLnRleHQuYWNjZXNzaWJsZVRleHQoYyl9cmV0dXJuITF9fSx7aWQ6ImhlbHAtc2FtZS1hcy1sYWJlbCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1heGUuY29tbW9ucy50ZXh0LmxhYmVsKGEpLGQ9YS5nZXRBdHRyaWJ1dGUoInRpdGxlIik7aWYoIWMpcmV0dXJuITE7aWYoIWQmJihkPSIiLGEuZ2V0QXR0cmlidXRlKCJhcmlhLWRlc2NyaWJlZGJ5IikpKXt2YXIgZT1heGUuY29tbW9ucy5kb20uaWRyZWZzKGEsImFyaWEtZGVzY3JpYmVkYnkiKTtkPWUubWFwKGZ1bmN0aW9uKGEpe3JldHVybiBhP2F4ZS5jb21tb25zLnRleHQuYWNjZXNzaWJsZVRleHQoYSk6IiJ9KS5qb2luKCIiKX1yZXR1cm4gYXhlLmNvbW1vbnMudGV4dC5zYW5pdGl6ZShkKT09PWF4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoYyl9LGVuYWJsZWQ6ITF9LHtpZDoiaW1wbGljaXQtbGFiZWwiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9YXhlLmNvbW1vbnMuZG9tLmZpbmRVcChhLCJsYWJlbCIpO3JldHVybiEhYyYmISFheGUuY29tbW9ucy50ZXh0LmFjY2Vzc2libGVUZXh0KGMpfX0se2lkOiJtdWx0aXBsZS1sYWJlbCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtmb3IodmFyIGM9W10uc2xpY2UuY2FsbChkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCdsYWJlbFtmb3I9IicrYXhlLmNvbW1vbnMudXRpbHMuZXNjYXBlU2VsZWN0b3IoYS5pZCkrJyJdJykpLGQ9YS5wYXJlbnROb2RlO2Q7KSJMQUJFTCI9PT1kLnRhZ05hbWUmJmMuaW5kZXhPZihkKT09PS0xJiZjLnB1c2goZCksZD1kLnBhcmVudE5vZGU7cmV0dXJuIHRoaXMucmVsYXRlZE5vZGVzKGMpLGMubGVuZ3RoPjF9fSx7aWQ6InRpdGxlLW9ubHkiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9YXhlLmNvbW1vbnMudGV4dC5sYWJlbChhKTtyZXR1cm4hKGN8fCFhLmdldEF0dHJpYnV0ZSgidGl0bGUiKSYmIWEuZ2V0QXR0cmlidXRlKCJhcmlhLWRlc2NyaWJlZGJ5IikpfX0se2lkOiJoYXMtbGFuZyIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4hIShhLmdldEF0dHJpYnV0ZSgibGFuZyIpfHxhLmdldEF0dHJpYnV0ZSgieG1sOmxhbmciKXx8IiIpLnRyaW0oKX19LHtpZDoidmFsaWQtbGFuZyIsb3B0aW9uczpbImFhIiwiYWIiLCJhZSIsImFmIiwiYWsiLCJhbSIsImFuIiwiYXIiLCJhcyIsImF2IiwiYXkiLCJheiIsImJhIiwiYmUiLCJiZyIsImJoIiwiYmkiLCJibSIsImJuIiwiYm8iLCJiciIsImJzIiwiY2EiLCJjZSIsImNoIiwiY28iLCJjciIsImNzIiwiY3UiLCJjdiIsImN5IiwiZGEiLCJkZSIsImR2IiwiZHoiLCJlZSIsImVsIiwiZW4iLCJlbyIsImVzIiwiZXQiLCJldSIsImZhIiwiZmYiLCJmaSIsImZqIiwiZm8iLCJmciIsImZ5IiwiZ2EiLCJnZCIsImdsIiwiZ24iLCJndSIsImd2IiwiaGEiLCJoZSIsImhpIiwiaG8iLCJociIsImh0IiwiaHUiLCJoeSIsImh6IiwiaWEiLCJpZCIsImllIiwiaWciLCJpaSIsImlrIiwiaW4iLCJpbyIsImlzIiwiaXQiLCJpdSIsIml3IiwiamEiLCJqaSIsImp2IiwianciLCJrYSIsImtnIiwia2kiLCJraiIsImtrIiwia2wiLCJrbSIsImtuIiwia28iLCJrciIsImtzIiwia3UiLCJrdiIsImt3Iiwia3kiLCJsYSIsImxiIiwibGciLCJsaSIsImxuIiwibG8iLCJsdCIsImx1IiwibHYiLCJtZyIsIm1oIiwibWkiLCJtayIsIm1sIiwibW4iLCJtbyIsIm1yIiwibXMiLCJtdCIsIm15IiwibmEiLCJuYiIsIm5kIiwibmUiLCJuZyIsIm5sIiwibm4iLCJubyIsIm5yIiwibnYiLCJueSIsIm9jIiwib2oiLCJvbSIsIm9yIiwib3MiLCJwYSIsInBpIiwicGwiLCJwcyIsInB0IiwicXUiLCJybSIsInJuIiwicm8iLCJydSIsInJ3Iiwic2EiLCJzYyIsInNkIiwic2UiLCJzZyIsInNoIiwic2kiLCJzayIsInNsIiwic20iLCJzbiIsInNvIiwic3EiLCJzciIsInNzIiwic3QiLCJzdSIsInN2Iiwic3ciLCJ0YSIsInRlIiwidGciLCJ0aCIsInRpIiwidGsiLCJ0bCIsInRuIiwidG8iLCJ0ciIsInRzIiwidHQiLCJ0dyIsInR5IiwidWciLCJ1ayIsInVyIiwidXoiLCJ2ZSIsInZpIiwidm8iLCJ3YSIsIndvIiwieGgiLCJ5aSIsInlvIiwiemEiLCJ6aCIsInp1Il0sZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtmdW5jdGlvbiBjKGEpe3JldHVybiBhLnRyaW0oKS5zcGxpdCgiLSIpWzBdLnRvTG93ZXJDYXNlKCl9dmFyIGQsZTtyZXR1cm4gZD0oYnx8W10pLm1hcChjKSxlPVsibGFuZyIsInhtbDpsYW5nIl0ucmVkdWNlKGZ1bmN0aW9uKGIsZSl7dmFyIGY9YS5nZXRBdHRyaWJ1dGUoZSk7aWYoInN0cmluZyIhPXR5cGVvZiBmKXJldHVybiBiO3ZhciBnPWMoZik7cmV0dXJuIiIhPT1nJiZkLmluZGV4T2YoZyk9PT0tMSYmYi5wdXNoKGUrJz0iJythLmdldEF0dHJpYnV0ZShlKSsnIicpLGJ9LFtdKSwhIWUubGVuZ3RoJiYodGhpcy5kYXRhKGUpLCEwKX19LHtpZDoiZGxpdGVtIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiJETCI9PT1hLnBhcmVudE5vZGUudGFnTmFtZX19LHtpZDoiaGFzLWxpc3RpdGVtIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuY2hpbGRyZW47aWYoMD09PWMubGVuZ3RoKXJldHVybiEwO2Zvcih2YXIgZD0wO2Q8Yy5sZW5ndGg7ZCsrKWlmKCJMSSI9PT1jW2RdLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCkpcmV0dXJuITE7cmV0dXJuITB9fSx7aWQ6Imxpc3RpdGVtIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVyblsiVUwiLCJPTCJdLmluZGV4T2YoYS5wYXJlbnROb2RlLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCkpIT09LTF8fCJsaXN0Ij09PWEucGFyZW50Tm9kZS5nZXRBdHRyaWJ1dGUoInJvbGUiKX19LHtpZDoib25seS1kbGl0ZW1zIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2Zvcih2YXIgYyxkLGU9W10sZj1hLmNoaWxkTm9kZXMsZz1bIlNUWUxFIiwiTUVUQSIsIkxJTksiLCJNQVAiLCJBUkVBIiwiU0NSSVBUIiwiREFUQUxJU1QiLCJURU1QTEFURSJdLGg9ITEsaT0wO2k8Zi5sZW5ndGg7aSsrKXtjPWZbaV07dmFyIGQ9Yy5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpOzE9PT1jLm5vZGVUeXBlJiYiRFQiIT09ZCYmIkREIiE9PWQmJmcuaW5kZXhPZihkKT09PS0xP2UucHVzaChjKTozPT09Yy5ub2RlVHlwZSYmIiIhPT1jLm5vZGVWYWx1ZS50cmltKCkmJihoPSEwKX1lLmxlbmd0aCYmdGhpcy5yZWxhdGVkTm9kZXMoZSk7dmFyIGo9ISFlLmxlbmd0aHx8aDtyZXR1cm4gan19LHtpZDoib25seS1saXN0aXRlbXMiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7Zm9yKHZhciBjLGQsZT1bXSxmPWEuY2hpbGROb2RlcyxnPVsiU1RZTEUiLCJNRVRBIiwiTElOSyIsIk1BUCIsIkFSRUEiLCJTQ1JJUFQiLCJEQVRBTElTVCIsIlRFTVBMQVRFIl0saD0hMSxpPTA7aTxmLmxlbmd0aDtpKyspYz1mW2ldLGQ9Yy5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpLDE9PT1jLm5vZGVUeXBlJiYiTEkiIT09ZCYmZy5pbmRleE9mKGQpPT09LTE/ZS5wdXNoKGMpOjM9PT1jLm5vZGVUeXBlJiYiIiE9PWMubm9kZVZhbHVlLnRyaW0oKSYmKGg9ITApO3JldHVybiBlLmxlbmd0aCYmdGhpcy5yZWxhdGVkTm9kZXMoZSksISFlLmxlbmd0aHx8aH19LHtpZDoic3RydWN0dXJlZC1kbGl0ZW1zIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuY2hpbGRyZW47aWYoIWN8fCFjLmxlbmd0aClyZXR1cm4hMTtmb3IodmFyIGQsZT0hMSxmPSExLGc9MDtnPGMubGVuZ3RoO2crKyl7aWYoZD1jW2ddLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCksIkRUIj09PWQmJihlPSEwKSxlJiYiREQiPT09ZClyZXR1cm4hMTsiREQiPT09ZCYmKGY9ITApfXJldHVybiBlfHxmfX0se2lkOiJjYXB0aW9uIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiFhLnF1ZXJ5U2VsZWN0b3IoInRyYWNrW2tpbmQ9Y2FwdGlvbnNdIil9fSx7aWQ6ImRlc2NyaXB0aW9uIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiFhLnF1ZXJ5U2VsZWN0b3IoInRyYWNrW2tpbmQ9ZGVzY3JpcHRpb25zXSIpfX0se2lkOiJtZXRhLXZpZXdwb3J0LWxhcmdlIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2I9Ynx8e307Zm9yKHZhciBjLGQ9YS5nZXRBdHRyaWJ1dGUoImNvbnRlbnQiKXx8IiIsZT1kLnNwbGl0KC9bOyxdLyksZj17fSxnPWIuc2NhbGVNaW5pbXVtfHwyLGg9Yi5sb3dlckJvdW5kfHwhMSxpPTAsaj1lLmxlbmd0aDtpPGo7aSsrKXtjPWVbaV0uc3BsaXQoIj0iKTt2YXIgaz1jLnNoaWZ0KCkudG9Mb3dlckNhc2UoKTtrJiZjLmxlbmd0aCYmKGZbay50cmltKCldPWMuc2hpZnQoKS50cmltKCkudG9Mb3dlckNhc2UoKSl9cmV0dXJuISEoaCYmZlsibWF4aW11bS1zY2FsZSJdJiZwYXJzZUZsb2F0KGZbIm1heGltdW0tc2NhbGUiXSk8aCl8fCEoIWgmJiJubyI9PT1mWyJ1c2VyLXNjYWxhYmxlIl0pJiYhKGZbIm1heGltdW0tc2NhbGUiXSYmcGFyc2VGbG9hdChmWyJtYXhpbXVtLXNjYWxlIl0pPGcpfSxvcHRpb25zOntzY2FsZU1pbmltdW06NSxsb3dlckJvdW5kOjJ9fSx7aWQ6Im1ldGEtdmlld3BvcnQiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7Yj1ifHx7fTtmb3IodmFyIGMsZD1hLmdldEF0dHJpYnV0ZSgiY29udGVudCIpfHwiIixlPWQuc3BsaXQoL1s7LF0vKSxmPXt9LGc9Yi5zY2FsZU1pbmltdW18fDIsaD1iLmxvd2VyQm91bmR8fCExLGk9MCxqPWUubGVuZ3RoO2k8ajtpKyspe2M9ZVtpXS5zcGxpdCgiPSIpO3ZhciBrPWMuc2hpZnQoKS50b0xvd2VyQ2FzZSgpO2smJmMubGVuZ3RoJiYoZltrLnRyaW0oKV09Yy5zaGlmdCgpLnRyaW0oKS50b0xvd2VyQ2FzZSgpKX1yZXR1cm4hIShoJiZmWyJtYXhpbXVtLXNjYWxlIl0mJnBhcnNlRmxvYXQoZlsibWF4aW11bS1zY2FsZSJdKTxoKXx8ISghaCYmIm5vIj09PWZbInVzZXItc2NhbGFibGUiXSkmJiEoZlsibWF4aW11bS1zY2FsZSJdJiZwYXJzZUZsb2F0KGZbIm1heGltdW0tc2NhbGUiXSk8Zyl9LG9wdGlvbnM6e3NjYWxlTWluaW11bToyfX0se2lkOiJoZWFkZXItcHJlc2VudCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4hIWEucXVlcnlTZWxlY3RvcignaDEsIGgyLCBoMywgaDQsIGg1LCBoNiwgW3JvbGU9ImhlYWRpbmciXScpfX0se2lkOiJoZWFkaW5nLW9yZGVyIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJhcmlhLWxldmVsIik7aWYobnVsbCE9PWMpcmV0dXJuIHRoaXMuZGF0YShwYXJzZUludChjLDEwKSksITA7dmFyIGQ9YS50YWdOYW1lLm1hdGNoKC9IKFxkKS8pO3JldHVybiFkfHwodGhpcy5kYXRhKHBhcnNlSW50KGRbMV0sMTApKSwhMCl9LGFmdGVyOmZ1bmN0aW9uKGEsYil7aWYoYS5sZW5ndGg8MilyZXR1cm4gYTtmb3IodmFyIGM9YVswXS5kYXRhLGQ9MTtkPGEubGVuZ3RoO2QrKylhW2RdLnJlc3VsdCYmYVtkXS5kYXRhPmMrMSYmKGFbZF0ucmVzdWx0PSExKSxjPWFbZF0uZGF0YTtyZXR1cm4gYX19LHtpZDoiaHJlZi1uby1oYXNoIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJocmVmIik7cmV0dXJuIiMiIT09Y319LHtpZDoiaW50ZXJuYWwtbGluay1wcmVzZW50IixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiEhYS5xdWVyeVNlbGVjdG9yKCdhW2hyZWZePSIjIl0nKX19LHtpZDoibGFuZG1hcmsiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuIGEuZ2V0RWxlbWVudHNCeVRhZ05hbWUoIm1haW4iKS5sZW5ndGg+MHx8ISFhLnF1ZXJ5U2VsZWN0b3IoJ1tyb2xlPSJtYWluIl0nKX19LHtpZDoibWV0YS1yZWZyZXNoIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJjb250ZW50Iil8fCIiLGQ9Yy5zcGxpdCgvWzssXS8pO3JldHVybiIiPT09Y3x8IjAiPT09ZFswXX19LHtpZDoicmVnaW9uIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2Z1bmN0aW9uIGMoYSl7cmV0dXJuIGgmJmF4ZS5jb21tb25zLmRvbS5pc0ZvY3VzYWJsZShheGUuY29tbW9ucy5kb20uZ2V0RWxlbWVudEJ5UmVmZXJlbmNlKGgsImhyZWYiKSkmJmg9PT1hfWZ1bmN0aW9uIGQoYSl7dmFyIGI9YS5nZXRBdHRyaWJ1dGUoInJvbGUiKTtyZXR1cm4gYiYmZy5pbmRleE9mKGIpIT09LTF9ZnVuY3Rpb24gZShhKXtyZXR1cm4gZChhKT9udWxsOmMoYSk/ZihhKTpheGUuY29tbW9ucy5kb20uaXNWaXNpYmxlKGEsITApJiYoYXhlLmNvbW1vbnMudGV4dC52aXNpYmxlKGEsITAsITApfHxheGUuY29tbW9ucy5kb20uaXNWaXN1YWxDb250ZW50KGEpKT9hOmYoYSl9ZnVuY3Rpb24gZihhKXt2YXIgYj1heGUuY29tbW9ucy51dGlscy50b0FycmF5KGEuY2hpbGRyZW4pO3JldHVybiAwPT09Yi5sZW5ndGg/W106Yi5tYXAoZSkuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBudWxsIT09YX0pLnJlZHVjZShmdW5jdGlvbihhLGIpe3JldHVybiBhLmNvbmNhdChiKX0sW10pfXZhciBnPWF4ZS5jb21tb25zLmFyaWEuZ2V0Um9sZXNCeVR5cGUoImxhbmRtYXJrIiksaD1hLnF1ZXJ5U2VsZWN0b3IoImFbaHJlZl0iKSxpPWYoYSk7cmV0dXJuIHRoaXMucmVsYXRlZE5vZGVzKGkpLCFpLmxlbmd0aH0sYWZ0ZXI6ZnVuY3Rpb24oYSxiKXtyZXR1cm5bYVswXV19fSx7aWQ6InNraXAtbGluayIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4gYXhlLmNvbW1vbnMuZG9tLmlzRm9jdXNhYmxlKGF4ZS5jb21tb25zLmRvbS5nZXRFbGVtZW50QnlSZWZlcmVuY2UoYSwiaHJlZiIpKX0sYWZ0ZXI6ZnVuY3Rpb24oYSxiKXtyZXR1cm5bYVswXV19fSx7aWQ6InVuaXF1ZS1mcmFtZS10aXRsZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1heGUuY29tbW9ucy50ZXh0LnNhbml0aXplKGEudGl0bGUpLnRyaW0oKS50b0xvd2VyQ2FzZSgpO3JldHVybiB0aGlzLmRhdGEoYyksITB9LGFmdGVyOmZ1bmN0aW9uKGEsYil7dmFyIGM9e307cmV0dXJuIGEuZm9yRWFjaChmdW5jdGlvbihhKXtjW2EuZGF0YV09dm9pZCAwIT09Y1thLmRhdGFdPysrY1thLmRhdGFdOjB9KSxhLmZvckVhY2goZnVuY3Rpb24oYSl7YS5yZXN1bHQ9ISFjW2EuZGF0YV19KSxhfX0se2lkOiJhcmlhLWxhYmVsIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJhcmlhLWxhYmVsIik7cmV0dXJuISEoYz9heGUuY29tbW9ucy50ZXh0LnNhbml0aXplKGMpLnRyaW0oKToiIil9fSx7aWQ6ImFyaWEtbGFiZWxsZWRieSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1heGUuY29tbW9ucy5kb20uaWRyZWZzO3JldHVybiBjKGEsImFyaWEtbGFiZWxsZWRieSIpLnNvbWUoZnVuY3Rpb24oYSl7cmV0dXJuIGEmJmF4ZS5jb21tb25zLnRleHQuYWNjZXNzaWJsZVRleHQoYSwhMCl9KX19LHtpZDoiYnV0dG9uLWhhcy12aXNpYmxlLXRleHQiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9YS5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpLGQ9YS5nZXRBdHRyaWJ1dGUoInJvbGUiKSxlPXZvaWQgMDtyZXR1cm4oIkJVVFRPTiI9PT1jfHwiYnV0dG9uIj09PWQmJiJJTlBVVCIhPT1jKSYmKGU9YXhlLmNvbW1vbnMudGV4dC5hY2Nlc3NpYmxlVGV4dChhKSx0aGlzLmRhdGEoZSksISFlKX19LHtpZDoiZG9jLWhhcy10aXRsZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1kb2N1bWVudC50aXRsZTtyZXR1cm4hIShjP2F4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoYykudHJpbSgpOiIiKX19LHtpZDoiZHVwbGljYXRlLWlkIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2lmKCFhLmlkLnRyaW0oKSlyZXR1cm4hMDtmb3IodmFyIGM9ZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnW2lkPSInK2F4ZS5jb21tb25zLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEuaWQpKyciXScpLGQ9W10sZT0wO2U8Yy5sZW5ndGg7ZSsrKWNbZV0hPT1hJiZkLnB1c2goY1tlXSk7cmV0dXJuIGQubGVuZ3RoJiZ0aGlzLnJlbGF0ZWROb2RlcyhkKSx0aGlzLmRhdGEoYS5nZXRBdHRyaWJ1dGUoImlkIikpLGMubGVuZ3RoPD0xfSxhZnRlcjpmdW5jdGlvbihhLGIpe3ZhciBjPVtdO3JldHVybiBhLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4gYy5pbmRleE9mKGEuZGF0YSk9PT0tMSYmKGMucHVzaChhLmRhdGEpLCEwKX0pfX0se2lkOiJleGlzdHMiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuITB9fSx7aWQ6Imhhcy1hbHQiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuIGEuaGFzQXR0cmlidXRlKCJhbHQiKX19LHtpZDoiaGFzLXZpc2libGUtdGV4dCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4gYXhlLmNvbW1vbnMudGV4dC5hY2Nlc3NpYmxlVGV4dChhKS5sZW5ndGg+MH19LHtpZDoiaXMtb24tc2NyZWVuIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiBheGUuY29tbW9ucy5kb20uaXNWaXNpYmxlKGEsITEpJiYhYXhlLmNvbW1vbnMuZG9tLmlzT2Zmc2NyZWVuKGEpfX0se2lkOiJub24tZW1wdHktYWx0IixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJhbHQiKTtyZXR1cm4hIShjP2F4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoYykudHJpbSgpOiIiKX19LHtpZDoibm9uLWVtcHR5LWlmLXByZXNlbnQiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9YS5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpLGQ9KGEuZ2V0QXR0cmlidXRlKCJ0eXBlIil8fCIiKS50b0xvd2VyQ2FzZSgpLGU9YS5nZXRBdHRyaWJ1dGUoInZhbHVlIik7cmV0dXJuIHRoaXMuZGF0YShlKSwiSU5QVVQiPT09YyYmWyJzdWJtaXQiLCJyZXNldCJdLmluZGV4T2YoZCkhPT0tMSYmbnVsbD09PWV9fSx7aWQ6Im5vbi1lbXB0eS10aXRsZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1hLmdldEF0dHJpYnV0ZSgidGl0bGUiKTtyZXR1cm4hIShjP2F4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoYykudHJpbSgpOiIiKX19LHtpZDoibm9uLWVtcHR5LXZhbHVlIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJ2YWx1ZSIpO3JldHVybiEhKGM/YXhlLmNvbW1vbnMudGV4dC5zYW5pdGl6ZShjKS50cmltKCk6IiIpfX0se2lkOiJyb2xlLW5vbmUiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuIm5vbmUiPT09YS5nZXRBdHRyaWJ1dGUoInJvbGUiKX19LHtpZDoicm9sZS1wcmVzZW50YXRpb24iLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuInByZXNlbnRhdGlvbiI9PT1hLmdldEF0dHJpYnV0ZSgicm9sZSIpfX0se2lkOiJjYXB0aW9uLWZha2VkIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWF4ZS5jb21tb25zLnRhYmxlLnRvR3JpZChhKSxkPWNbMF07cmV0dXJuIGMubGVuZ3RoPD0xfHxkLmxlbmd0aDw9MXx8YS5yb3dzLmxlbmd0aDw9MXx8ZC5yZWR1Y2UoZnVuY3Rpb24oYSxiLGMpe3JldHVybiBhfHxiIT09ZFtjKzFdJiZ2b2lkIDAhPT1kW2MrMV19LCExKX19LHtpZDoiaGFzLWNhcHRpb24iLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuISFhLmNhcHRpb259fSx7aWQ6Imhhcy1zdW1tYXJ5IixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiEhYS5zdW1tYXJ5fX0se2lkOiJoYXMtdGgiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7Zm9yKHZhciBjLGQsZT1bXSxmPTAsZz1hLnJvd3MubGVuZ3RoO2Y8ZztmKyspe2M9YS5yb3dzW2ZdO2Zvcih2YXIgaD0wLGk9Yy5jZWxscy5sZW5ndGg7aDxpO2grKylkPWMuY2VsbHNbaF0sIlRIIiE9PWQubm9kZU5hbWUudG9VcHBlckNhc2UoKSYmWyJyb3doZWFkZXIiLCJjb2x1bW5oZWFkZXIiXS5pbmRleE9mKGQuZ2V0QXR0cmlidXRlKCJyb2xlIikpPT09LTF8fGUucHVzaChkKX1yZXR1cm4hIWUubGVuZ3RoJiYodGhpcy5yZWxhdGVkTm9kZXMoZSksITApfX0se2lkOiJodG1sNS1zY29wZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4hIWF4ZS5jb21tb25zLmRvbS5pc0hUTUw1KGRvY3VtZW50KSYmIlRIIj09PWEubm9kZU5hbWUudG9VcHBlckNhc2UoKX19LHtpZDoic2FtZS1jYXB0aW9uLXN1bW1hcnkiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuISghYS5zdW1tYXJ5fHwhYS5jYXB0aW9uKSYmYS5zdW1tYXJ5PT09YXhlLmNvbW1vbnMudGV4dC5hY2Nlc3NpYmxlVGV4dChhLmNhcHRpb24pfX0se2lkOiJzY29wZS12YWx1ZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtiPWJ8fHt9O3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJzY29wZSIpLnRvTG93ZXJDYXNlKCksZD1bInJvdyIsImNvbCIsInJvd2dyb3VwIiwiY29sZ3JvdXAiXXx8Yi52YWx1ZXM7cmV0dXJuIGQuaW5kZXhPZihjKSE9PS0xfX0se2lkOiJ0ZC1oYXMtaGVhZGVyIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWF4ZS5jb21tb25zLnRhYmxlLGQ9W10sZT1jLmdldEFsbENlbGxzKGEpO3JldHVybiBlLmZvckVhY2goZnVuY3Rpb24oYSl7aWYoIiIhPT1hLnRleHRDb250ZW50LnRyaW0oKSYmYy5pc0RhdGFDZWxsKGEpJiYhYXhlLmNvbW1vbnMuYXJpYS5sYWJlbChhKSl7dmFyIGI9Yy5nZXRIZWFkZXJzKGEpO2I9Yi5yZWR1Y2UoZnVuY3Rpb24oYSxiKXtyZXR1cm4gYXx8bnVsbCE9PWImJiEhYi50ZXh0Q29udGVudC50cmltKCl9LCExKSxifHxkLnB1c2goYSl9fSksIWQubGVuZ3RofHwodGhpcy5yZWxhdGVkTm9kZXMoZCksITEpfX0se2lkOiJ0ZC1oZWFkZXJzLWF0dHIiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7Zm9yKHZhciBjPVtdLGQ9MCxlPWEucm93cy5sZW5ndGg7ZDxlO2QrKylmb3IodmFyIGY9YS5yb3dzW2RdLGc9MCxoPWYuY2VsbHMubGVuZ3RoO2c8aDtnKyspYy5wdXNoKGYuY2VsbHNbZ10pO3ZhciBpPWMucmVkdWNlKGZ1bmN0aW9uKGEsYil7cmV0dXJuIGIuaWQmJmEucHVzaChiLmlkKSxhfSxbXSksaj1jLnJlZHVjZShmdW5jdGlvbihhLGIpe3ZhciBjLGQsZT0oYi5nZXRBdHRyaWJ1dGUoImhlYWRlcnMiKXx8IiIpLnNwbGl0KC9ccy8pLnJlZHVjZShmdW5jdGlvbihhLGIpe3JldHVybiBiPWIudHJpbSgpLGImJmEucHVzaChiKSxhfSxbXSk7cmV0dXJuIDAhPT1lLmxlbmd0aCYmKGIuaWQmJihjPWUuaW5kZXhPZihiLmlkLnRyaW0oKSkhPT0tMSksZD1lLnJlZHVjZShmdW5jdGlvbihhLGIpe3JldHVybiBhfHxpLmluZGV4T2YoYik9PT0tMX0sITEpLChjfHxkKSYmYS5wdXNoKGIpKSxhfSxbXSk7cmV0dXJuIShqLmxlbmd0aD4wKXx8KHRoaXMucmVsYXRlZE5vZGVzKGopLCExKX19LHtpZDoidGgtaGFzLWRhdGEtY2VsbHMiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9YXhlLmNvbW1vbnMudGFibGUsZD1jLmdldEFsbENlbGxzKGEpLGU9dGhpcyxmPVtdO2QuZm9yRWFjaChmdW5jdGlvbihhKXt2YXIgYj1hLmdldEF0dHJpYnV0ZSgiaGVhZGVycyIpO2ImJihmPWYuY29uY2F0KGIuc3BsaXQoL1xzKy8pKSk7dmFyIGM9YS5nZXRBdHRyaWJ1dGUoImFyaWEtbGFiZWxsZWRieSIpO2MmJihmPWYuY29uY2F0KGMuc3BsaXQoL1xzKy8pKSl9KTt2YXIgZz1kLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4iIiE9PWF4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoYS50ZXh0Q29udGVudCkmJigiVEgiPT09YS5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpfHxbInJvd2hlYWRlciIsImNvbHVtbmhlYWRlciJdLmluZGV4T2YoYS5nZXRBdHRyaWJ1dGUoInJvbGUiKSkhPT0tMSl9KSxoPWMudG9HcmlkKGEpO3JldHVybiBnLnJlZHVjZShmdW5jdGlvbihhLGIpe2lmKGIuaWQmJmYuaW5kZXhPZihiLmlkKSE9PS0xKXJldHVybiEhYXx8YTt2YXIgZD0hMSxnPWMuZ2V0Q2VsbFBvc2l0aW9uKGIsaCk7cmV0dXJuIGMuaXNDb2x1bW5IZWFkZXIoYikmJihkPWMudHJhdmVyc2UoImRvd24iLGcsaCkucmVkdWNlKGZ1bmN0aW9uKGEsYil7cmV0dXJuIGF8fCIiIT09Yi50ZXh0Q29udGVudC50cmltKCkmJiFjLmlzQ29sdW1uSGVhZGVyKGIpfSwhMSkpLCFkJiZjLmlzUm93SGVhZGVyKGIpJiYoZD1jLnRyYXZlcnNlKCJyaWdodCIsZyxoKS5yZWR1Y2UoZnVuY3Rpb24oYSxiKXsKcmV0dXJuIGF8fCIiIT09Yi50ZXh0Q29udGVudC50cmltKCkmJiFjLmlzUm93SGVhZGVyKGIpfSwhMSkpLGR8fGUucmVsYXRlZE5vZGVzKGIpLGEmJmR9LCEwKX19XSxjb21tb25zOmZ1bmN0aW9uKCl7ZnVuY3Rpb24gYShhKXtyZXR1cm4gYS5nZXRQcm9wZXJ0eVZhbHVlKCJmb250LWZhbWlseSIpLnNwbGl0KC9bLDtdL2cpLm1hcChmdW5jdGlvbihhKXtyZXR1cm4gYS50cmltKCkudG9Mb3dlckNhc2UoKX0pfWZ1bmN0aW9uIGIoYixjKXt2YXIgZD13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShiKTtpZigibm9uZSIhPT1kLmdldFByb3BlcnR5VmFsdWUoImJhY2tncm91bmQtaW1hZ2UiKSlyZXR1cm4hMDt2YXIgZT1bImJvcmRlci1ib3R0b20iLCJib3JkZXItdG9wIiwib3V0bGluZSJdLnJlZHVjZShmdW5jdGlvbihhLGIpe3ZhciBjPW5ldyB1LkNvbG9yO3JldHVybiBjLnBhcnNlUmdiU3RyaW5nKGQuZ2V0UHJvcGVydHlWYWx1ZShiKyItY29sb3IiKSksYXx8Im5vbmUiIT09ZC5nZXRQcm9wZXJ0eVZhbHVlKGIrIi1zdHlsZSIpJiZwYXJzZUZsb2F0KGQuZ2V0UHJvcGVydHlWYWx1ZShiKyItd2lkdGgiKSk+MCYmMCE9PWMuYWxwaGF9LCExKTtpZihlKXJldHVybiEwO3ZhciBmPXdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGMpO2lmKGEoZClbMF0hPT1hKGYpWzBdKXJldHVybiEwO3ZhciBnPVsidGV4dC1kZWNvcmF0aW9uLWxpbmUiLCJ0ZXh0LWRlY29yYXRpb24tc3R5bGUiLCJmb250LXdlaWdodCIsImZvbnQtc3R5bGUiLCJmb250LXNpemUiXS5yZWR1Y2UoZnVuY3Rpb24oYSxiKXtyZXR1cm4gYXx8ZC5nZXRQcm9wZXJ0eVZhbHVlKGIpIT09Zi5nZXRQcm9wZXJ0eVZhbHVlKGIpfSwhMSksaD1kLmdldFByb3BlcnR5VmFsdWUoInRleHQtZGVjb3JhdGlvbiIpO3JldHVybiBoLnNwbGl0KCIgIikubGVuZ3RoPDMmJihnPWd8fGghPT1mLmdldFByb3BlcnR5VmFsdWUoInRleHQtZGVjb3JhdGlvbiIpKSxnfWZ1bmN0aW9uIGMoYSxiKXt2YXIgYz1hLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCk7cmV0dXJuISF5LmluY2x1ZGVzKGMpfHwoYj1ifHx3aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShhKSwibm9uZSIhPT1iLmdldFByb3BlcnR5VmFsdWUoImJhY2tncm91bmQtaW1hZ2UiKSl9ZnVuY3Rpb24gZChhLGIpe2I9Ynx8d2luZG93LmdldENvbXB1dGVkU3R5bGUoYSk7dmFyIGM9bmV3IHUuQ29sb3I7aWYoYy5wYXJzZVJnYlN0cmluZyhiLmdldFByb3BlcnR5VmFsdWUoImJhY2tncm91bmQtY29sb3IiKSksMCE9PWMuYWxwaGEpe3ZhciBkPWIuZ2V0UHJvcGVydHlWYWx1ZSgib3BhY2l0eSIpO2MuYWxwaGE9Yy5hbHBoYSpkfXJldHVybiBjfWZ1bmN0aW9uIGUoYSxiKXt2YXIgYz0wO2lmKGE+MClmb3IodmFyIGU9YS0xO2U+PTA7ZS0tKXt2YXIgZj1iW2VdLGc9d2luZG93LmdldENvbXB1dGVkU3R5bGUoZiksaD1kKGYsZyk7aC5hbHBoYT9jKz1oLmFscGhhOmIuc3BsaWNlKGUsMSl9cmV0dXJuIGN9ZnVuY3Rpb24gZihhLGIpeyJ1c2Ugc3RyaWN0Ijt2YXIgYz1iKGEpO2ZvcihhPWEuZmlyc3RDaGlsZDthOyljIT09ITEmJmYoYSxiKSxhPWEubmV4dFNpYmxpbmd9ZnVuY3Rpb24gZyhhKXsidXNlIHN0cmljdCI7dmFyIGI9d2luZG93LmdldENvbXB1dGVkU3R5bGUoYSkuZ2V0UHJvcGVydHlWYWx1ZSgiZGlzcGxheSIpO3JldHVybiB6LmluZGV4T2YoYikhPT0tMXx8InRhYmxlLSI9PT1iLnN1YnN0cigwLDYpfWZ1bmN0aW9uIGgoYSl7InVzZSBzdHJpY3QiO3ZhciBiPWEubWF0Y2goL3JlY3RccypcKChbMC05XSspcHgsP1xzKihbMC05XSspcHgsP1xzKihbMC05XSspcHgsP1xzKihbMC05XSspcHhccypcKS8pO3JldHVybiEoIWJ8fDUhPT1iLmxlbmd0aCkmJihiWzNdLWJbMV08PTAmJmJbMl0tYls0XTw9MCl9ZnVuY3Rpb24gaShhKXt2YXIgYj1udWxsO3JldHVybiBhLmlkJiYoYj1kb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdsYWJlbFtmb3I9IicrYXhlLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEuaWQpKyciXScpKT9iOmI9di5maW5kVXAoYSwibGFiZWwiKX1mdW5jdGlvbiBqKGEpe3JldHVyblsiYnV0dG9uIiwicmVzZXQiLCJzdWJtaXQiXS5pbmRleE9mKGEudHlwZSkhPT0tMX1mdW5jdGlvbiBrKGEpe3ZhciBiPWEubm9kZU5hbWUudG9VcHBlckNhc2UoKTtyZXR1cm4iVEVYVEFSRUEiPT09Ynx8IlNFTEVDVCI9PT1ifHwiSU5QVVQiPT09YiYmImhpZGRlbiIhPT1hLnR5cGUudG9Mb3dlckNhc2UoKX1mdW5jdGlvbiBsKGEpe3JldHVyblsiQlVUVE9OIiwiU1VNTUFSWSIsIkEiXS5pbmRleE9mKGEubm9kZU5hbWUudG9VcHBlckNhc2UoKSkhPT0tMX1mdW5jdGlvbiBtKGEpe3JldHVyblsiVEFCTEUiLCJGSUdVUkUiXS5pbmRleE9mKGEubm9kZU5hbWUudG9VcHBlckNhc2UoKSkhPT0tMX1mdW5jdGlvbiBuKGEpe3ZhciBiPWEubm9kZU5hbWUudG9VcHBlckNhc2UoKTtpZigiSU5QVVQiPT09YilyZXR1cm4hYS5oYXNBdHRyaWJ1dGUoInR5cGUiKXx8Qi5pbmRleE9mKGEuZ2V0QXR0cmlidXRlKCJ0eXBlIikudG9Mb3dlckNhc2UoKSkhPT0tMSYmYS52YWx1ZT9hLnZhbHVlOiIiO2lmKCJTRUxFQ1QiPT09Yil7dmFyIGM9YS5vcHRpb25zO2lmKGMmJmMubGVuZ3RoKXtmb3IodmFyIGQ9IiIsZT0wO2U8Yy5sZW5ndGg7ZSsrKWNbZV0uc2VsZWN0ZWQmJihkKz0iICIrY1tlXS50ZXh0KTtyZXR1cm4geC5zYW5pdGl6ZShkKX1yZXR1cm4iIn1yZXR1cm4iVEVYVEFSRUEiPT09YiYmYS52YWx1ZT9hLnZhbHVlOiIifWZ1bmN0aW9uIG8oYSxiKXt2YXIgYz1hLnF1ZXJ5U2VsZWN0b3IoYi50b0xvd2VyQ2FzZSgpKTtyZXR1cm4gYz94LmFjY2Vzc2libGVUZXh0KGMpOiIifWZ1bmN0aW9uIHAoYSl7aWYoIWEpcmV0dXJuITE7c3dpdGNoKGEubm9kZU5hbWUudG9VcHBlckNhc2UoKSl7Y2FzZSJTRUxFQ1QiOmNhc2UiVEVYVEFSRUEiOnJldHVybiEwO2Nhc2UiSU5QVVQiOnJldHVybiFhLmhhc0F0dHJpYnV0ZSgidHlwZSIpfHxCLmluZGV4T2YoYS5nZXRBdHRyaWJ1dGUoInR5cGUiKS50b0xvd2VyQ2FzZSgpKSE9PS0xO2RlZmF1bHQ6cmV0dXJuITF9fWZ1bmN0aW9uIHEoYSl7dmFyIGI9YS5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpO3JldHVybiJJTlBVVCI9PT1iJiYiaW1hZ2UiPT09YS50eXBlLnRvTG93ZXJDYXNlKCl8fFsiSU1HIiwiQVBQTEVUIiwiQVJFQSJdLmluZGV4T2YoYikhPT0tMX1mdW5jdGlvbiByKGEpe3JldHVybiEheC5zYW5pdGl6ZShhKX12YXIgY29tbW9ucz17fSxzPWNvbW1vbnMuYXJpYT17fSx0PXMuX2x1dD17fTt0LmF0dHJpYnV0ZXM9eyJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiOnt0eXBlOiJpZHJlZiJ9LCJhcmlhLWF0b21pYyI6e3R5cGU6ImJvb2xlYW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSJdfSwiYXJpYS1hdXRvY29tcGxldGUiOnt0eXBlOiJubXRva2VuIix2YWx1ZXM6WyJpbmxpbmUiLCJsaXN0IiwiYm90aCIsIm5vbmUiXX0sImFyaWEtYnVzeSI6e3R5cGU6ImJvb2xlYW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSJdfSwiYXJpYS1jaGVja2VkIjp7dHlwZToibm10b2tlbiIsdmFsdWVzOlsidHJ1ZSIsImZhbHNlIiwibWl4ZWQiLCJ1bmRlZmluZWQiXX0sImFyaWEtY29sY291bnQiOnt0eXBlOiJpbnQifSwiYXJpYS1jb2xpbmRleCI6e3R5cGU6ImludCJ9LCJhcmlhLWNvbHNwYW4iOnt0eXBlOiJpbnQifSwiYXJpYS1jb250cm9scyI6e3R5cGU6ImlkcmVmcyJ9LCJhcmlhLWRlc2NyaWJlZGJ5Ijp7dHlwZToiaWRyZWZzIn0sImFyaWEtZGlzYWJsZWQiOnt0eXBlOiJib29sZWFuIix2YWx1ZXM6WyJ0cnVlIiwiZmFsc2UiXX0sImFyaWEtZHJvcGVmZmVjdCI6e3R5cGU6Im5tdG9rZW5zIix2YWx1ZXM6WyJjb3B5IiwibW92ZSIsInJlZmVyZW5jZSIsImV4ZWN1dGUiLCJwb3B1cCIsIm5vbmUiXX0sImFyaWEtZXhwYW5kZWQiOnt0eXBlOiJubXRva2VuIix2YWx1ZXM6WyJ0cnVlIiwiZmFsc2UiLCJ1bmRlZmluZWQiXX0sImFyaWEtZmxvd3RvIjp7dHlwZToiaWRyZWZzIn0sImFyaWEtZ3JhYmJlZCI6e3R5cGU6Im5tdG9rZW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSIsInVuZGVmaW5lZCJdfSwiYXJpYS1oYXNwb3B1cCI6e3R5cGU6ImJvb2xlYW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSJdfSwiYXJpYS1oaWRkZW4iOnt0eXBlOiJib29sZWFuIix2YWx1ZXM6WyJ0cnVlIiwiZmFsc2UiXX0sImFyaWEtaW52YWxpZCI6e3R5cGU6Im5tdG9rZW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSIsInNwZWxsaW5nIiwiZ3JhbW1hciJdfSwiYXJpYS1sYWJlbCI6e3R5cGU6InN0cmluZyJ9LCJhcmlhLWxhYmVsbGVkYnkiOnt0eXBlOiJpZHJlZnMifSwiYXJpYS1sZXZlbCI6e3R5cGU6ImludCJ9LCJhcmlhLWxpdmUiOnt0eXBlOiJubXRva2VuIix2YWx1ZXM6WyJvZmYiLCJwb2xpdGUiLCJhc3NlcnRpdmUiXX0sImFyaWEtbXVsdGlsaW5lIjp7dHlwZToiYm9vbGVhbiIsdmFsdWVzOlsidHJ1ZSIsImZhbHNlIl19LCJhcmlhLW11bHRpc2VsZWN0YWJsZSI6e3R5cGU6ImJvb2xlYW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSJdfSwiYXJpYS1vcmllbnRhdGlvbiI6e3R5cGU6Im5tdG9rZW4iLHZhbHVlczpbImhvcml6b250YWwiLCJ2ZXJ0aWNhbCJdfSwiYXJpYS1vd25zIjp7dHlwZToiaWRyZWZzIn0sImFyaWEtcG9zaW5zZXQiOnt0eXBlOiJpbnQifSwiYXJpYS1wcmVzc2VkIjp7dHlwZToibm10b2tlbiIsdmFsdWVzOlsidHJ1ZSIsImZhbHNlIiwibWl4ZWQiLCJ1bmRlZmluZWQiXX0sImFyaWEtcmVhZG9ubHkiOnt0eXBlOiJib29sZWFuIix2YWx1ZXM6WyJ0cnVlIiwiZmFsc2UiXX0sImFyaWEtcmVsZXZhbnQiOnt0eXBlOiJubXRva2VucyIsdmFsdWVzOlsiYWRkaXRpb25zIiwicmVtb3ZhbHMiLCJ0ZXh0IiwiYWxsIl19LCJhcmlhLXJlcXVpcmVkIjp7dHlwZToiYm9vbGVhbiIsdmFsdWVzOlsidHJ1ZSIsImZhbHNlIl19LCJhcmlhLXJvd2NvdW50Ijp7dHlwZToiaW50In0sImFyaWEtcm93aW5kZXgiOnt0eXBlOiJpbnQifSwiYXJpYS1yb3dzcGFuIjp7dHlwZToiaW50In0sImFyaWEtc2VsZWN0ZWQiOnt0eXBlOiJubXRva2VuIix2YWx1ZXM6WyJ0cnVlIiwiZmFsc2UiLCJ1bmRlZmluZWQiXX0sImFyaWEtc2V0c2l6ZSI6e3R5cGU6ImludCJ9LCJhcmlhLXNvcnQiOnt0eXBlOiJubXRva2VuIix2YWx1ZXM6WyJhc2NlbmRpbmciLCJkZXNjZW5kaW5nIiwib3RoZXIiLCJub25lIl19LCJhcmlhLXZhbHVlbWF4Ijp7dHlwZToiZGVjaW1hbCJ9LCJhcmlhLXZhbHVlbWluIjp7dHlwZToiZGVjaW1hbCJ9LCJhcmlhLXZhbHVlbm93Ijp7dHlwZToiZGVjaW1hbCJ9LCJhcmlhLXZhbHVldGV4dCI6e3R5cGU6InN0cmluZyJ9fSx0Lmdsb2JhbEF0dHJpYnV0ZXM9WyJhcmlhLWF0b21pYyIsImFyaWEtYnVzeSIsImFyaWEtY29udHJvbHMiLCJhcmlhLWRlc2NyaWJlZGJ5IiwiYXJpYS1kaXNhYmxlZCIsImFyaWEtZHJvcGVmZmVjdCIsImFyaWEtZmxvd3RvIiwiYXJpYS1ncmFiYmVkIiwiYXJpYS1oYXNwb3B1cCIsImFyaWEtaGlkZGVuIiwiYXJpYS1pbnZhbGlkIiwiYXJpYS1sYWJlbCIsImFyaWEtbGFiZWxsZWRieSIsImFyaWEtbGl2ZSIsImFyaWEtb3ducyIsImFyaWEtcmVsZXZhbnQiXSx0LnJvbGU9e2FsZXJ0Ont0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LGFsZXJ0ZGlhbG9nOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LGFwcGxpY2F0aW9uOnt0eXBlOiJsYW5kbWFyayIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbH0sYXJ0aWNsZTp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiYXJ0aWNsZSJdfSxiYW5uZXI6e3R5cGU6ImxhbmRtYXJrIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiaGVhZGVyIl19LGJ1dHRvbjp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCIsImFyaWEtcHJlc3NlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJidXR0b24iLCdpbnB1dFt0eXBlPSJidXR0b24iXScsJ2lucHV0W3R5cGU9ImltYWdlIl0nLCdpbnB1dFt0eXBlPSJyZXNldCJdJywnaW5wdXRbdHlwZT0ic3VibWl0Il0nLCJzdW1tYXJ5Il19LGNlbGw6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtY29saW5kZXgiLCJhcmlhLWNvbHNwYW4iLCJhcmlhLXJvd2luZGV4IiwiYXJpYS1yb3dzcGFuIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJyb3ciXSxpbXBsaWNpdDpbInRkIiwidGgiXX0sY2hlY2tib3g6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7cmVxdWlyZWQ6WyJhcmlhLWNoZWNrZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsnaW5wdXRbdHlwZT0iY2hlY2tib3giXSddfSxjb2x1bW5oZWFkZXI6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiLCJhcmlhLXNvcnQiLCJhcmlhLXJlYWRvbmx5IiwiYXJpYS1zZWxlY3RlZCIsImFyaWEtcmVxdWlyZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpbInJvdyJdLGltcGxpY2l0OlsidGgiXX0sY29tYm9ib3g6e3R5cGU6ImNvbXBvc2l0ZSIsYXR0cmlidXRlczp7cmVxdWlyZWQ6WyJhcmlhLWV4cGFuZGVkIl0sYWxsb3dlZDpbImFyaWEtYXV0b2NvbXBsZXRlIiwiYXJpYS1yZXF1aXJlZCIsImFyaWEtYWN0aXZlZGVzY2VuZGFudCJdfSxvd25lZDp7YWxsOlsibGlzdGJveCIsInRleHRib3giXX0sbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LGNvbW1hbmQ6e25hbWVGcm9tOlsiYXV0aG9yIl0sdHlwZToiYWJzdHJhY3QifSxjb21wbGVtZW50YXJ5Ont0eXBlOiJsYW5kbWFyayIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbImFzaWRlIl19LGNvbXBvc2l0ZTp7bmFtZUZyb206WyJhdXRob3IiXSx0eXBlOiJhYnN0cmFjdCJ9LGNvbnRlbnRpbmZvOnt0eXBlOiJsYW5kbWFyayIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbImZvb3RlciJdfSxkZWZpbml0aW9uOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJkZCJdfSxkaWFsb2c6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbImRpYWxvZyJdfSxkaXJlY3Rvcnk6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpudWxsfSxkb2N1bWVudDp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiYm9keSJdfSxmb3JtOnt0eXBlOiJsYW5kbWFyayIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbImZvcm0iXX0sZ3JpZDp7dHlwZToiY29tcG9zaXRlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1sZXZlbCIsImFyaWEtbXVsdGlzZWxlY3RhYmxlIiwiYXJpYS1yZWFkb25seSIsImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6e29uZTpbInJvd2dyb3VwIiwicm93Il19LG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsidGFibGUiXX0sZ3JpZGNlbGw6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtc2VsZWN0ZWQiLCJhcmlhLXJlYWRvbmx5IiwiYXJpYS1leHBhbmRlZCIsImFyaWEtcmVxdWlyZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpbInJvdyJdLGltcGxpY2l0OlsidGQiLCJ0aCJdfSxncm91cDp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1hY3RpdmVkZXNjZW5kYW50IiwiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiZGV0YWlscyIsIm9wdGdyb3VwIl19LGhlYWRpbmc6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtbGV2ZWwiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbImgxIiwiaDIiLCJoMyIsImg0IiwiaDUiLCJoNiJdfSxpbWc6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbImltZyJdfSxpbnB1dDp7bmFtZUZyb206WyJhdXRob3IiXSx0eXBlOiJhYnN0cmFjdCJ9LGxhbmRtYXJrOntuYW1lRnJvbTpbImF1dGhvciJdLHR5cGU6ImFic3RyYWN0In0sbGluazp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJhW2hyZWZdIl19LGxpc3Q6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6e2FsbDpbImxpc3RpdGVtIl19LG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0Olsib2wiLCJ1bCIsImRsIl19LGxpc3Rib3g6e3R5cGU6ImNvbXBvc2l0ZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtbXVsdGlzZWxlY3RhYmxlIiwiYXJpYS1yZXF1aXJlZCIsImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6e2FsbDpbIm9wdGlvbiJdfSxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbInNlbGVjdCJdfSxsaXN0aXRlbTp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1sZXZlbCIsImFyaWEtcG9zaW5zZXQiLCJhcmlhLXNldHNpemUiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJsaXN0Il0saW1wbGljaXQ6WyJsaSIsImR0Il19LGxvZzp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSxtYWluOnt0eXBlOiJsYW5kbWFyayIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbIm1haW4iXX0sbWFycXVlZTp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSxtYXRoOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJtYXRoIl19LG1lbnU6e3R5cGU6ImNvbXBvc2l0ZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6e29uZTpbIm1lbnVpdGVtIiwibWVudWl0ZW1yYWRpbyIsIm1lbnVpdGVtY2hlY2tib3giXX0sbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WydtZW51W3R5cGU9ImNvbnRleHQiXSddfSxtZW51YmFyOnt0eXBlOiJjb21wb3NpdGUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LG1lbnVpdGVtOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6bnVsbCxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0OlsibWVudSIsIm1lbnViYXIiXSxpbXBsaWNpdDpbJ21lbnVpdGVtW3R5cGU9ImNvbW1hbmQiXSddfSxtZW51aXRlbWNoZWNrYm94Ont0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e3JlcXVpcmVkOlsiYXJpYS1jaGVja2VkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJtZW51IiwibWVudWJhciJdLGltcGxpY2l0OlsnbWVudWl0ZW1bdHlwZT0iY2hlY2tib3giXSddfSxtZW51aXRlbXJhZGlvOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXNlbGVjdGVkIiwiYXJpYS1wb3NpbnNldCIsImFyaWEtc2V0c2l6ZSJdLHJlcXVpcmVkOlsiYXJpYS1jaGVja2VkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJtZW51IiwibWVudWJhciJdLGltcGxpY2l0OlsnbWVudWl0ZW1bdHlwZT0icmFkaW8iXSddfSxuYXZpZ2F0aW9uOnt0eXBlOiJsYW5kbWFyayIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbIm5hdiJdfSxub25lOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6bnVsbCxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSxub3RlOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LG9wdGlvbjp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1zZWxlY3RlZCIsImFyaWEtcG9zaW5zZXQiLCJhcmlhLXNldHNpemUiLCJhcmlhLWNoZWNrZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpbImxpc3Rib3giXSxpbXBsaWNpdDpbIm9wdGlvbiJdfSxwcmVzZW50YXRpb246e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczpudWxsLG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHByb2dyZXNzYmFyOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXZhbHVldGV4dCIsImFyaWEtdmFsdWVub3ciLCJhcmlhLXZhbHVlbWF4IiwiYXJpYS12YWx1ZW1pbiJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsicHJvZ3Jlc3MiXX0scmFkaW86e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtc2VsZWN0ZWQiLCJhcmlhLXBvc2luc2V0IiwiYXJpYS1zZXRzaXplIl0scmVxdWlyZWQ6WyJhcmlhLWNoZWNrZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsnaW5wdXRbdHlwZT0icmFkaW8iXSddfSxyYWRpb2dyb3VwOnt0eXBlOiJjb21wb3NpdGUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiLCJhcmlhLXJlcXVpcmVkIiwiYXJpYS1leHBhbmRlZCJdfSxvd25lZDp7YWxsOlsicmFkaW8iXX0sbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHJhbmdlOntuYW1lRnJvbTpbImF1dGhvciJdLHR5cGU6ImFic3RyYWN0In0scmVnaW9uOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJzZWN0aW9uIl19LHJvbGV0eXBlOnt0eXBlOiJhYnN0cmFjdCJ9LHJvdzp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1sZXZlbCIsImFyaWEtc2VsZWN0ZWQiLCJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOntvbmU6WyJjZWxsIiwiY29sdW1uaGVhZGVyIiwicm93aGVhZGVyIiwiZ3JpZGNlbGwiXX0sbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJyb3dncm91cCIsImdyaWQiLCJ0cmVlZ3JpZCIsInRhYmxlIl0saW1wbGljaXQ6WyJ0ciJdfSxyb3dncm91cDp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1hY3RpdmVkZXNjZW5kYW50IiwiYXJpYS1leHBhbmRlZCJdfSxvd25lZDp7YWxsOlsicm93Il19LG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0OlsiZ3JpZCIsInRhYmxlIl0saW1wbGljaXQ6WyJ0Ym9keSIsInRoZWFkIiwidGZvb3QiXX0scm93aGVhZGVyOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXNvcnQiLCJhcmlhLXJlcXVpcmVkIiwiYXJpYS1yZWFkb25seSIsImFyaWEtZXhwYW5kZWQiLCJhcmlhLXNlbGVjdGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJyb3ciXSxpbXBsaWNpdDpbInRoIl19LHNjcm9sbGJhcjp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOntyZXF1aXJlZDpbImFyaWEtY29udHJvbHMiLCJhcmlhLW9yaWVudGF0aW9uIiwiYXJpYS12YWx1ZW5vdyIsImFyaWEtdmFsdWVtYXgiLCJhcmlhLXZhbHVlbWluIl0sYWxsb3dlZDpbImFyaWEtdmFsdWV0ZXh0Il19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHNlYXJjaDp7dHlwZToibGFuZG1hcmsiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHNlYXJjaGJveDp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1hY3RpdmVkZXNjZW5kYW50IiwiYXJpYS1hdXRvY29tcGxldGUiLCJhcmlhLW11bHRpbGluZSIsImFyaWEtcmVhZG9ubHkiLCJhcmlhLXJlcXVpcmVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WydpbnB1dFt0eXBlPSJzZWFyY2giXSddfSxzZWN0aW9uOntuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sdHlwZToiYWJzdHJhY3QifSxzZWN0aW9uaGVhZDp7bmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLHR5cGU6ImFic3RyYWN0In0sc2VsZWN0OntuYW1lRnJvbTpbImF1dGhvciJdLHR5cGU6ImFic3RyYWN0In0sc2VwYXJhdG9yOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIiwiYXJpYS1vcmllbnRhdGlvbiJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiaHIiXX0sc2xpZGVyOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXZhbHVldGV4dCIsImFyaWEtb3JpZW50YXRpb24iXSxyZXF1aXJlZDpbImFyaWEtdmFsdWVub3ciLCJhcmlhLXZhbHVlbWF4IiwiYXJpYS12YWx1ZW1pbiJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsnaW5wdXRbdHlwZT0icmFuZ2UiXSddfSxzcGluYnV0dG9uOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXZhbHVldGV4dCIsImFyaWEtcmVxdWlyZWQiXSxyZXF1aXJlZDpbImFyaWEtdmFsdWVub3ciLCJhcmlhLXZhbHVlbWF4IiwiYXJpYS12YWx1ZW1pbiJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsnaW5wdXRbdHlwZT0ibnVtYmVyIl0nXX0sc3RhdHVzOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJvdXRwdXQiXX0sc3RydWN0dXJlOnt0eXBlOiJhYnN0cmFjdCJ9LCJzd2l0Y2giOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e3JlcXVpcmVkOlsiYXJpYS1jaGVja2VkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6bnVsbH0sdGFiOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXNlbGVjdGVkIiwiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0OlsidGFibGlzdCJdfSx0YWJsZTp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1jb2xjb3VudCIsImFyaWEtcm93Y291bnQiXX0sb3duZWQ6e29uZTpbInJvd2dyb3VwIiwicm93Il19LG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsidGFibGUiXX0sdGFibGlzdDp7dHlwZToiY29tcG9zaXRlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1hY3RpdmVkZXNjZW5kYW50IiwiYXJpYS1leHBhbmRlZCIsImFyaWEtbGV2ZWwiLCJhcmlhLW11bHRpc2VsZWN0YWJsZSJdfSxvd25lZDp7YWxsOlsidGFiIl19LG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSx0YWJwYW5lbDp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSx0ZXh0Ont0eXBlOiJzdHJ1Y3R1cmUiLG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6bnVsbH0sdGV4dGJveDp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1hY3RpdmVkZXNjZW5kYW50IiwiYXJpYS1hdXRvY29tcGxldGUiLCJhcmlhLW11bHRpbGluZSIsImFyaWEtcmVhZG9ubHkiLCJhcmlhLXJlcXVpcmVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WydpbnB1dFt0eXBlPSJ0ZXh0Il0nLCdpbnB1dFt0eXBlPSJlbWFpbCJdJywnaW5wdXRbdHlwZT0icGFzc3dvcmQiXScsJ2lucHV0W3R5cGU9InRlbCJdJywnaW5wdXRbdHlwZT0idXJsIl0nLCJpbnB1dDpub3QoW3R5cGVdKSIsInRleHRhcmVhIl19LHRpbWVyOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHRvb2xiYXI6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbJ21lbnVbdHlwZT0idG9vbGJhciJdJ119LHRvb2x0aXA6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpudWxsfSx0cmVlOnt0eXBlOiJjb21wb3NpdGUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiLCJhcmlhLW11bHRpc2VsZWN0YWJsZSIsImFyaWEtcmVxdWlyZWQiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOnthbGw6WyJ0cmVlaXRlbSJdfSxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbH0sdHJlZWdyaWQ6e3R5cGU6ImNvbXBvc2l0ZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtZXhwYW5kZWQiLCJhcmlhLWxldmVsIiwiYXJpYS1tdWx0aXNlbGVjdGFibGUiLCJhcmlhLXJlYWRvbmx5IiwiYXJpYS1yZXF1aXJlZCJdfSxvd25lZDp7YWxsOlsidHJlZWl0ZW0iXX0sbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHRyZWVpdGVtOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWNoZWNrZWQiLCJhcmlhLXNlbGVjdGVkIiwiYXJpYS1leHBhbmRlZCIsImFyaWEtbGV2ZWwiLCJhcmlhLXBvc2luc2V0IiwiYXJpYS1zZXRzaXplIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJ0cmVlZ3JpZCIsInRyZWUiXX0sd2lkZ2V0Ont0eXBlOiJhYnN0cmFjdCJ9LHdpbmRvdzp7bmFtZUZyb206WyJhdXRob3IiXSx0eXBlOiJhYnN0cmFjdCJ9fTt2YXIgdT17fTtjb21tb25zLmNvbG9yPXU7dmFyIHY9Y29tbW9ucy5kb209e30sdz1jb21tb25zLnRhYmxlPXt9LHg9Y29tbW9ucy50ZXh0PXt9O2NvbW1vbnMudXRpbHM9YXhlLnV0aWxzO3MucmVxdWlyZWRBdHRyPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj10LnJvbGVbYV0sYz1iJiZiLmF0dHJpYnV0ZXMmJmIuYXR0cmlidXRlcy5yZXF1aXJlZDtyZXR1cm4gY3x8W119LHMuYWxsb3dlZEF0dHI9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPXQucm9sZVthXSxjPWImJmIuYXR0cmlidXRlcyYmYi5hdHRyaWJ1dGVzLmFsbG93ZWR8fFtdLGQ9YiYmYi5hdHRyaWJ1dGVzJiZiLmF0dHJpYnV0ZXMucmVxdWlyZWR8fFtdO3JldHVybiBjLmNvbmNhdCh0Lmdsb2JhbEF0dHJpYnV0ZXMpLmNvbmNhdChkKX0scy52YWxpZGF0ZUF0dHI9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3JldHVybiEhdC5hdHRyaWJ1dGVzW2FdfSxzLnZhbGlkYXRlQXR0clZhbHVlPWZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO3ZhciBjLGQsZT1kb2N1bWVudCxmPWEuZ2V0QXR0cmlidXRlKGIpLGc9dC5hdHRyaWJ1dGVzW2JdO2lmKCFnKXJldHVybiEwO3N3aXRjaChnLnR5cGUpe2Nhc2UiYm9vbGVhbiI6Y2FzZSJubXRva2VuIjpyZXR1cm4ic3RyaW5nIj09dHlwZW9mIGYmJmcudmFsdWVzLmluZGV4T2YoZi50b0xvd2VyQ2FzZSgpKSE9PS0xO2Nhc2Uibm10b2tlbnMiOnJldHVybiBkPWF4ZS51dGlscy50b2tlbkxpc3QoZiksZC5yZWR1Y2UoZnVuY3Rpb24oYSxiKXtyZXR1cm4gYSYmZy52YWx1ZXMuaW5kZXhPZihiKSE9PS0xfSwwIT09ZC5sZW5ndGgpO2Nhc2UiaWRyZWYiOnJldHVybiEoIWZ8fCFlLmdldEVsZW1lbnRCeUlkKGYpKTtjYXNlImlkcmVmcyI6cmV0dXJuIGQ9YXhlLnV0aWxzLnRva2VuTGlzdChmKSxkLnJlZHVjZShmdW5jdGlvbihhLGIpe3JldHVybiEoIWF8fCFlLmdldEVsZW1lbnRCeUlkKGIpKX0sMCE9PWQubGVuZ3RoKTtjYXNlInN0cmluZyI6cmV0dXJuITA7Y2FzZSJkZWNpbWFsIjpyZXR1cm4gYz1mLm1hdGNoKC9eWy0rXT8oWzAtOV0qKVwuPyhbMC05XSopJC8pLCEoIWN8fCFjWzFdJiYhY1syXSk7Y2FzZSJpbnQiOnJldHVybi9eWy0rXT9bMC05XSskLy50ZXN0KGYpfX0scy5sYWJlbD1mdW5jdGlvbihhKXt2YXIgYixjO3JldHVybiBhLmdldEF0dHJpYnV0ZSgiYXJpYS1sYWJlbGxlZGJ5IikmJihiPXYuaWRyZWZzKGEsImFyaWEtbGFiZWxsZWRieSIpLGM9Yi5tYXAoZnVuY3Rpb24oYSl7cmV0dXJuIGE/eC52aXNpYmxlKGEsITApOiIifSkuam9pbigiICIpLnRyaW0oKSk/YzooYz1hLmdldEF0dHJpYnV0ZSgiYXJpYS1sYWJlbCIpLGMmJihjPXguc2FuaXRpemUoYykudHJpbSgpKT9jOm51bGwpfSxzLmlzVmFsaWRSb2xlPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtyZXR1cm4hIXQucm9sZVthXX0scy5nZXRSb2xlc1dpdGhOYW1lRnJvbUNvbnRlbnRzPWZ1bmN0aW9uKCl7cmV0dXJuIE9iamVjdC5rZXlzKHQucm9sZSkuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiB0LnJvbGVbYV0ubmFtZUZyb20mJnQucm9sZVthXS5uYW1lRnJvbS5pbmRleE9mKCJjb250ZW50cyIpIT09LTF9KX0scy5nZXRSb2xlc0J5VHlwZT1mdW5jdGlvbihhKXtyZXR1cm4gT2JqZWN0LmtleXModC5yb2xlKS5maWx0ZXIoZnVuY3Rpb24oYil7cmV0dXJuIHQucm9sZVtiXS50eXBlPT09YX0pfSxzLmdldFJvbGVUeXBlPWZ1bmN0aW9uKGEpe3ZhciBiPXQucm9sZVthXTtyZXR1cm4gYiYmYi50eXBlfHxudWxsfSxzLnJlcXVpcmVkT3duZWQ9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPW51bGwsYz10LnJvbGVbYV07cmV0dXJuIGMmJihiPWF4ZS51dGlscy5jbG9uZShjLm93bmVkKSksYn0scy5yZXF1aXJlZENvbnRleHQ9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPW51bGwsYz10LnJvbGVbYV07cmV0dXJuIGMmJihiPWF4ZS51dGlscy5jbG9uZShjLmNvbnRleHQpKSxifSxzLmltcGxpY2l0Tm9kZXM9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPW51bGwsYz10LnJvbGVbYV07cmV0dXJuIGMmJmMuaW1wbGljaXQmJihiPWF4ZS51dGlscy5jbG9uZShjLmltcGxpY2l0KSksYn0scy5pbXBsaWNpdFJvbGU9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiLGMsZCxlPXQucm9sZTtmb3IoYiBpbiBlKWlmKGUuaGFzT3duUHJvcGVydHkoYikmJihjPWVbYl0sYy5pbXBsaWNpdCkpZm9yKHZhciBmPTAsZz1jLmltcGxpY2l0Lmxlbmd0aDtmPGc7ZisrKWlmKGQ9Yy5pbXBsaWNpdFtmXSxheGUudXRpbHMubWF0Y2hlc1NlbGVjdG9yKGEsZCkpcmV0dXJuIGI7cmV0dXJuIG51bGx9LHUuQ29sb3I9ZnVuY3Rpb24oYSxiLGMsZCl7dGhpcy5yZWQ9YSx0aGlzLmdyZWVuPWIsdGhpcy5ibHVlPWMsdGhpcy5hbHBoYT1kLHRoaXMudG9IZXhTdHJpbmc9ZnVuY3Rpb24oKXt2YXIgYT1NYXRoLnJvdW5kKHRoaXMucmVkKS50b1N0cmluZygxNiksYj1NYXRoLnJvdW5kKHRoaXMuZ3JlZW4pLnRvU3RyaW5nKDE2KSxjPU1hdGgucm91bmQodGhpcy5ibHVlKS50b1N0cmluZygxNik7cmV0dXJuIiMiKyh0aGlzLnJlZD4xNS41P2E6IjAiK2EpKyh0aGlzLmdyZWVuPjE1LjU/YjoiMCIrYikrKHRoaXMuYmx1ZT4xNS41P2M6IjAiK2MpfTt2YXIgZT0vXnJnYlwoKFxkKyksIChcZCspLCAoXGQrKVwpJC8sZj0vXnJnYmFcKChcZCspLCAoXGQrKSwgKFxkKyksIChcZCooXC5cZCspPylcKS87dGhpcy5wYXJzZVJnYlN0cmluZz1mdW5jdGlvbihhKXtpZigidHJhbnNwYXJlbnQiPT09YSlyZXR1cm4gdGhpcy5yZWQ9MCx0aGlzLmdyZWVuPTAsdGhpcy5ibHVlPTAsdm9pZCh0aGlzLmFscGhhPTApO3ZhciBiPWEubWF0Y2goZSk7cmV0dXJuIGI/KHRoaXMucmVkPXBhcnNlSW50KGJbMV0sMTApLHRoaXMuZ3JlZW49cGFyc2VJbnQoYlsyXSwxMCksdGhpcy5ibHVlPXBhcnNlSW50KGJbM10sMTApLHZvaWQodGhpcy5hbHBoYT0xKSk6KGI9YS5tYXRjaChmKSxiPyh0aGlzLnJlZD1wYXJzZUludChiWzFdLDEwKSx0aGlzLmdyZWVuPXBhcnNlSW50KGJbMl0sMTApLHRoaXMuYmx1ZT1wYXJzZUludChiWzNdLDEwKSx2b2lkKHRoaXMuYWxwaGE9cGFyc2VGbG9hdChiWzRdKSkpOnZvaWQgMCl9LHRoaXMuZ2V0UmVsYXRpdmVMdW1pbmFuY2U9ZnVuY3Rpb24oKXt2YXIgYT10aGlzLnJlZC8yNTUsYj10aGlzLmdyZWVuLzI1NSxjPXRoaXMuYmx1ZS8yNTUsZD1hPD0uMDM5Mjg/YS8xMi45MjpNYXRoLnBvdygoYSsuMDU1KS8xLjA1NSwyLjQpLGU9Yjw9LjAzOTI4P2IvMTIuOTI6TWF0aC5wb3coKGIrLjA1NSkvMS4wNTUsMi40KSxmPWM8PS4wMzkyOD9jLzEyLjkyOk1hdGgucG93KChjKy4wNTUpLzEuMDU1LDIuNCk7cmV0dXJuLjIxMjYqZCsuNzE1MiplKy4wNzIyKmZ9fSx1LmZsYXR0ZW5Db2xvcnM9ZnVuY3Rpb24oYSxiKXt2YXIgYz1hLmFscGhhLGQ9KDEtYykqYi5yZWQrYyphLnJlZCxlPSgxLWMpKmIuZ3JlZW4rYyphLmdyZWVuLGY9KDEtYykqYi5ibHVlK2MqYS5ibHVlLGc9YS5hbHBoYStiLmFscGhhKigxLWEuYWxwaGEpO3JldHVybiBuZXcgdS5Db2xvcihkLGUsZixnKX0sdS5nZXRDb250cmFzdD1mdW5jdGlvbihhLGIpe2lmKCFifHwhYSlyZXR1cm4gbnVsbDtiLmFscGhhPDEmJihiPXUuZmxhdHRlbkNvbG9ycyhiLGEpKTt2YXIgYz1hLmdldFJlbGF0aXZlTHVtaW5hbmNlKCksZD1iLmdldFJlbGF0aXZlTHVtaW5hbmNlKCk7cmV0dXJuKE1hdGgubWF4KGQsYykrLjA1KS8oTWF0aC5taW4oZCxjKSsuMDUpfSx1Lmhhc1ZhbGlkQ29udHJhc3RSYXRpbz1mdW5jdGlvbihhLGIsYyxkKXt2YXIgZT11LmdldENvbnRyYXN0KGEsYiksZj1kJiZNYXRoLmNlaWwoNzIqYykvOTY8MTR8fCFkJiZNYXRoLmNlaWwoNzIqYykvOTY8MTg7cmV0dXJue2lzVmFsaWQ6ZiYmZT49NC41fHwhZiYmZT49Myxjb250cmFzdFJhdGlvOmV9fSx1LmVsZW1lbnRJc0Rpc3RpbmN0PWI7dmFyIHk9WyJJTUciLCJDQU5WQVMiLCJPQkpFQ1QiLCJJRlJBTUUiLCJWSURFTyIsIlNWRyJdO3UuZ2V0QmFja2dyb3VuZFN0YWNrPWZ1bmN0aW9uKGEpe3ZhciBiPWEuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCksZj12b2lkIDAsZz12b2lkIDA7aWYoIShiLmxlZnQ+d2luZG93LmlubmVyV2lkdGh8fGIudG9wPndpbmRvdy5pbm5lcldpZHRoKSl7Zj1NYXRoLm1pbihNYXRoLmNlaWwoYi5sZWZ0K2Iud2lkdGgvMiksd2luZG93LmlubmVyV2lkdGgtMSksZz1NYXRoLm1pbihNYXRoLmNlaWwoYi50b3ArYi5oZWlnaHQvMiksd2luZG93LmlubmVySGVpZ2h0LTEpO3ZhciBoPWRvY3VtZW50LmVsZW1lbnRzRnJvbVBvaW50KGYsZyk7aD12LnJlZHVjZVRvRWxlbWVudHNCZWxvd0Zsb2F0aW5nKGgsYSk7dmFyIGk9aC5pbmRleE9mKGRvY3VtZW50LmJvZHkpO2k+MSYmIWMoZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50KSYmMD09PWQoZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50KS5hbHBoYSYmKGguc3BsaWNlKGksMSksaC5zcGxpY2UoaC5pbmRleE9mKGRvY3VtZW50LmRvY3VtZW50RWxlbWVudCksMSksaC5wdXNoKGRvY3VtZW50LmJvZHkpKTt2YXIgaj1oLmluZGV4T2YoYSk7cmV0dXJuIGUoaixoKT49Ljk5P251bGw6aiE9PS0xP2g6bnVsbH19LHUuZ2V0QmFja2dyb3VuZENvbG9yPWZ1bmN0aW9uKGEpe3ZhciBiPWFyZ3VtZW50cy5sZW5ndGg+MSYmdm9pZCAwIT09YXJndW1lbnRzWzFdP2FyZ3VtZW50c1sxXTpbXSxlPWFyZ3VtZW50cy5sZW5ndGg+MiYmdm9pZCAwIT09YXJndW1lbnRzWzJdJiZhcmd1bWVudHNbMl07ZSE9PSEwJiZhLnNjcm9sbEludG9WaWV3KCk7dmFyIGY9W10sZz11LmdldEJhY2tncm91bmRTdGFjayhhKTtyZXR1cm4oZ3x8W10pLnNvbWUoZnVuY3Rpb24oZSl7dmFyIGc9d2luZG93LmdldENvbXB1dGVkU3R5bGUoZSksaD1kKGUsZyk7cmV0dXJuIGEhPT1lJiYhdi52aXN1YWxseUNvbnRhaW5zKGEsZSkmJjAhPT1oLmFscGhhfHxjKGUsZyk/KGY9bnVsbCxiLnB1c2goZSksITApOjAhPT1oLmFscGhhJiYoYi5wdXNoKGUpLGYucHVzaChoKSwxPT09aC5hbHBoYSl9KSxudWxsIT09ZiYmbnVsbCE9PWc/KGYucHVzaChuZXcgdS5Db2xvcigyNTUsMjU1LDI1NSwxKSksZi5yZWR1Y2UodS5mbGF0dGVuQ29sb3JzKSk6bnVsbH0sdi5pc09wYXF1ZT1mdW5jdGlvbihhKXt2YXIgYj13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShhKTtyZXR1cm4gYyhhLGIpfHwxPT09ZChhLGIpLmFscGhhfSx1LmdldEZvcmVncm91bmRDb2xvcj1mdW5jdGlvbihhLGIpe3ZhciBjPXdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGEpLGQ9bmV3IHUuQ29sb3I7ZC5wYXJzZVJnYlN0cmluZyhjLmdldFByb3BlcnR5VmFsdWUoImNvbG9yIikpO3ZhciBlPWMuZ2V0UHJvcGVydHlWYWx1ZSgib3BhY2l0eSIpO2lmKGQuYWxwaGE9ZC5hbHBoYSplLDE9PT1kLmFscGhhKXJldHVybiBkO3ZhciBmPXUuZ2V0QmFja2dyb3VuZENvbG9yKGEsW10sYik7cmV0dXJuIG51bGw9PT1mP251bGw6dS5mbGF0dGVuQ29sb3JzKGQsZil9LHYucmVkdWNlVG9FbGVtZW50c0JlbG93RmxvYXRpbmc9ZnVuY3Rpb24oYSxiKXt2YXIgYyxkLGUsZj1bImZpeGVkIiwic3RpY2t5Il0sZz1bXSxoPSExO2ZvcihjPTA7YzxhLmxlbmd0aDsrK2MpZD1hW2NdLGQ9PT1iJiYoaD0hMCksZT13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShkKSxofHxmLmluZGV4T2YoZS5wb3NpdGlvbik9PT0tMT9nLnB1c2goZCk6Zz1bXTtyZXR1cm4gZ30sdi5maW5kVXA9ZnVuY3Rpb24oYSxiKXsidXNlIHN0cmljdCI7dmFyIGMsZD1kb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKGIpLGU9ZC5sZW5ndGg7aWYoIWUpcmV0dXJuIG51bGw7Zm9yKGQ9YXhlLnV0aWxzLnRvQXJyYXkoZCksYz1hLnBhcmVudE5vZGU7YyYmZC5pbmRleE9mKGMpPT09LTE7KWM9Yy5wYXJlbnROb2RlO3JldHVybiBjfSx2LmdldEVsZW1lbnRCeVJlZmVyZW5jZT1mdW5jdGlvbihhLGIpeyJ1c2Ugc3RyaWN0Ijt2YXIgYyxkPWEuZ2V0QXR0cmlidXRlKGIpLGU9ZG9jdW1lbnQ7aWYoZCYmIiMiPT09ZC5jaGFyQXQoMCkpe2lmKGQ9ZC5zdWJzdHJpbmcoMSksYz1lLmdldEVsZW1lbnRCeUlkKGQpKXJldHVybiBjO2lmKGM9ZS5nZXRFbGVtZW50c0J5TmFtZShkKSxjLmxlbmd0aClyZXR1cm4gY1swXX1yZXR1cm4gbnVsbH0sdi5nZXRFbGVtZW50Q29vcmRpbmF0ZXM9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPXYuZ2V0U2Nyb2xsT2Zmc2V0KGRvY3VtZW50KSxjPWIubGVmdCxkPWIudG9wLGU9YS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtyZXR1cm57dG9wOmUudG9wK2QscmlnaHQ6ZS5yaWdodCtjLGJvdHRvbTplLmJvdHRvbStkLGxlZnQ6ZS5sZWZ0K2Msd2lkdGg6ZS5yaWdodC1lLmxlZnQsaGVpZ2h0OmUuYm90dG9tLWUudG9wfX0sdi5nZXRTY3JvbGxPZmZzZXQ9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO2lmKCFhLm5vZGVUeXBlJiZhLmRvY3VtZW50JiYoYT1hLmRvY3VtZW50KSw5PT09YS5ub2RlVHlwZSl7dmFyIGI9YS5kb2N1bWVudEVsZW1lbnQsYz1hLmJvZHk7cmV0dXJue2xlZnQ6YiYmYi5zY3JvbGxMZWZ0fHxjJiZjLnNjcm9sbExlZnR8fDAsdG9wOmImJmIuc2Nyb2xsVG9wfHxjJiZjLnNjcm9sbFRvcHx8MH19cmV0dXJue2xlZnQ6YS5zY3JvbGxMZWZ0LHRvcDphLnNjcm9sbFRvcH19LHYuZ2V0Vmlld3BvcnRTaXplPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYixjPWEuZG9jdW1lbnQsZD1jLmRvY3VtZW50RWxlbWVudDtyZXR1cm4gYS5pbm5lcldpZHRoP3t3aWR0aDphLmlubmVyV2lkdGgsaGVpZ2h0OmEuaW5uZXJIZWlnaHR9OmQ/e3dpZHRoOmQuY2xpZW50V2lkdGgsaGVpZ2h0OmQuY2xpZW50SGVpZ2h0fTooYj1jLmJvZHkse3dpZHRoOmIuY2xpZW50V2lkdGgsaGVpZ2h0OmIuY2xpZW50SGVpZ2h0fSl9LHYuaWRyZWZzPWZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO3ZhciBjLGQsZT1kb2N1bWVudCxmPVtdLGc9YS5nZXRBdHRyaWJ1dGUoYik7aWYoZylmb3IoZz1heGUudXRpbHMudG9rZW5MaXN0KGcpLGM9MCxkPWcubGVuZ3RoO2M8ZDtjKyspZi5wdXNoKGUuZ2V0RWxlbWVudEJ5SWQoZ1tjXSkpO3JldHVybiBmfSx2LmlzRm9jdXNhYmxlPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtpZighYXx8YS5kaXNhYmxlZHx8IXYuaXNWaXNpYmxlKGEpJiYiQVJFQSIhPT1hLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCkpcmV0dXJuITE7c3dpdGNoKGEubm9kZU5hbWUudG9VcHBlckNhc2UoKSl7Y2FzZSJBIjpjYXNlIkFSRUEiOmlmKGEuaHJlZilyZXR1cm4hMDticmVhaztjYXNlIklOUFVUIjpyZXR1cm4iaGlkZGVuIiE9PWEudHlwZTtjYXNlIlRFWFRBUkVBIjpjYXNlIlNFTEVDVCI6Y2FzZSJERVRBSUxTIjpjYXNlIkJVVFRPTiI6cmV0dXJuITB9dmFyIGI9YS5nZXRBdHRyaWJ1dGUoInRhYmluZGV4Iik7cmV0dXJuISghYnx8aXNOYU4ocGFyc2VJbnQoYiwxMCkpKX0sdi5pc0hUTUw1PWZ1bmN0aW9uKGEpe3ZhciBiPWEuZG9jdHlwZTtyZXR1cm4gbnVsbCE9PWImJigiaHRtbCI9PT1iLm5hbWUmJiFiLnB1YmxpY0lkJiYhYi5zeXN0ZW1JZCl9O3ZhciB6PVsiYmxvY2siLCJsaXN0LWl0ZW0iLCJ0YWJsZSIsImZsZXgiLCJncmlkIiwiaW5saW5lLWJsb2NrIl07di5pc0luVGV4dEJsb2NrPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtpZihnKGEpKXJldHVybiExO2Zvcih2YXIgYj1hLnBhcmVudE5vZGU7MT09PWIubm9kZVR5cGUmJiFnKGIpOyliPWIucGFyZW50Tm9kZTt2YXIgYz0iIixkPSIiLGU9MDtyZXR1cm4gZihiLGZ1bmN0aW9uKGIpe2lmKDI9PT1lKXJldHVybiExO2lmKDM9PT1iLm5vZGVUeXBlJiYoYys9Yi5ub2RlVmFsdWUpLDE9PT1iLm5vZGVUeXBlKXt2YXIgZj0oYi5ub2RlTmFtZXx8IiIpLnRvVXBwZXJDYXNlKCk7aWYoWyJCUiIsIkhSIl0uaW5kZXhPZihmKSE9PS0xKTA9PT1lPyhjPSIiLGQ9IiIpOmU9MjtlbHNle2lmKCJub25lIj09PWIuc3R5bGUuZGlzcGxheXx8ImhpZGRlbiI9PT1iLnN0eWxlLm92ZXJmbG93fHxbIiIsbnVsbCwibm9uZSJdLmluZGV4T2YoYi5zdHlsZVsiZmxvYXQiXSk9PT0tMXx8WyIiLG51bGwsInJlbGF0aXZlIl0uaW5kZXhPZihiLnN0eWxlLnBvc2l0aW9uKT09PS0xKXJldHVybiExO2lmKCJBIj09PWYmJmIuaHJlZnx8ImxpbmsiPT09KGIuZ2V0QXR0cmlidXRlKCJyb2xlIil8fCIiKS50b0xvd2VyQ2FzZSgpKXJldHVybiBiPT09YSYmKGU9MSksZCs9Yi50ZXh0Q29udGVudCwhMX19fSksYz1heGUuY29tbW9ucy50ZXh0LnNhbml0aXplKGMpLGQ9YXhlLmNvbW1vbnMudGV4dC5zYW5pdGl6ZShkKSxjLmxlbmd0aD5kLmxlbmd0aH0sdi5pc05vZGU9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3JldHVybiBhIGluc3RhbmNlb2YgTm9kZX0sdi5pc09mZnNjcmVlbj1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7dmFyIGIsYz1kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQsZD13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShkb2N1bWVudC5ib2R5fHxjKS5nZXRQcm9wZXJ0eVZhbHVlKCJkaXJlY3Rpb24iKSxlPXYuZ2V0RWxlbWVudENvb3JkaW5hdGVzKGEpO2lmKGUuYm90dG9tPDApcmV0dXJuITA7aWYoImx0ciI9PT1kKXtpZihlLnJpZ2h0PDApcmV0dXJuITB9ZWxzZSBpZihiPU1hdGgubWF4KGMuc2Nyb2xsV2lkdGgsdi5nZXRWaWV3cG9ydFNpemUod2luZG93KS53aWR0aCksZS5sZWZ0PmIpcmV0dXJuITA7cmV0dXJuITF9LHYuaXNWaXNpYmxlPWZ1bmN0aW9uKGEsYixjKXsidXNlIHN0cmljdCI7dmFyIGQsZT1hLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCksZj1hLnBhcmVudE5vZGU7cmV0dXJuIDk9PT1hLm5vZGVUeXBlfHwoZD13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShhLG51bGwpLG51bGwhPT1kJiYoISgibm9uZSI9PT1kLmdldFByb3BlcnR5VmFsdWUoImRpc3BsYXkiKXx8IlNUWUxFIj09PWUudG9VcHBlckNhc2UoKXx8IlNDUklQVCI9PT1lLnRvVXBwZXJDYXNlKCl8fCFiJiZoKGQuZ2V0UHJvcGVydHlWYWx1ZSgiY2xpcCIpKXx8IWMmJigiaGlkZGVuIj09PWQuZ2V0UHJvcGVydHlWYWx1ZSgidmlzaWJpbGl0eSIpfHwhYiYmdi5pc09mZnNjcmVlbihhKSl8fGImJiJ0cnVlIj09PWEuZ2V0QXR0cmlidXRlKCJhcmlhLWhpZGRlbiIpKSYmKCEhZiYmdi5pc1Zpc2libGUoZixiLCEwKSkpKX0sdi5pc1Zpc3VhbENvbnRlbnQ9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3N3aXRjaChhLnRhZ05hbWUudG9VcHBlckNhc2UoKSl7Y2FzZSJJTUciOmNhc2UiSUZSQU1FIjpjYXNlIk9CSkVDVCI6Y2FzZSJWSURFTyI6Y2FzZSJBVURJTyI6Y2FzZSJDQU5WQVMiOmNhc2UiU1ZHIjpjYXNlIk1BVEgiOmNhc2UiQlVUVE9OIjpjYXNlIlNFTEVDVCI6Y2FzZSJURVhUQVJFQSI6Y2FzZSJLRVlHRU4iOmNhc2UiUFJPR1JFU1MiOmNhc2UiTUVURVIiOnJldHVybiEwO2Nhc2UiSU5QVVQiOnJldHVybiJoaWRkZW4iIT09YS50eXBlO2RlZmF1bHQ6cmV0dXJuITF9fSx2LnZpc3VhbGx5Q29udGFpbnM9ZnVuY3Rpb24oYSxiKXt2YXIgYz1hLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLGQ9LjAxLGU9e3RvcDpjLnRvcCtkLGJvdHRvbTpjLmJvdHRvbS1kLGxlZnQ6Yy5sZWZ0K2QscmlnaHQ6Yy5yaWdodC1kfSxmPWIuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCksZz1mLnRvcCxoPWYubGVmdCxpPXt0b3A6Zy1iLnNjcm9sbFRvcCxib3R0b206Zy1iLnNjcm9sbFRvcCtiLnNjcm9sbEhlaWdodCxsZWZ0OmgtYi5zY3JvbGxMZWZ0LHJpZ2h0OmgtYi5zY3JvbGxMZWZ0K2Iuc2Nyb2xsV2lkdGh9O2lmKGUubGVmdDxpLmxlZnQmJmUubGVmdDxmLmxlZnR8fGUudG9wPGkudG9wJiZlLnRvcDxmLnRvcHx8ZS5yaWdodD5pLnJpZ2h0JiZlLnJpZ2h0PmYucmlnaHR8fGUuYm90dG9tPmkuYm90dG9tJiZlLmJvdHRvbT5mLmJvdHRvbSlyZXR1cm4hMTt2YXIgaj13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShiKTtyZXR1cm4hKGUucmlnaHQ+Zi5yaWdodHx8ZS5ib3R0b20+Zi5ib3R0b20pfHwoInNjcm9sbCI9PT1qLm92ZXJmbG93fHwiYXV0byI9PT1qLm92ZXJmbG93fHwiaGlkZGVuIj09PWoub3ZlcmZsb3d8fGIgaW5zdGFuY2VvZiBIVE1MQm9keUVsZW1lbnR8fGIgaW5zdGFuY2VvZiBIVE1MSHRtbEVsZW1lbnQpfSx2LnZpc3VhbGx5T3ZlcmxhcHM9ZnVuY3Rpb24oYSxiKXt2YXIgYz1iLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLGQ9Yy50b3AsZT1jLmxlZnQsZj17dG9wOmQtYi5zY3JvbGxUb3AsYm90dG9tOmQtYi5zY3JvbGxUb3ArYi5zY3JvbGxIZWlnaHQsbGVmdDplLWIuc2Nyb2xsTGVmdCxyaWdodDplLWIuc2Nyb2xsTGVmdCtiLnNjcm9sbFdpZHRofTtpZihhLmxlZnQ+Zi5yaWdodCYmYS5sZWZ0PmMucmlnaHR8fGEudG9wPmYuYm90dG9tJiZhLnRvcD5jLmJvdHRvbXx8YS5yaWdodDxmLmxlZnQmJmEucmlnaHQ8Yy5sZWZ0fHxhLmJvdHRvbTxmLnRvcCYmYS5ib3R0b208Yy50b3ApcmV0dXJuITE7dmFyIGc9d2luZG93LmdldENvbXB1dGVkU3R5bGUoYik7cmV0dXJuIShhLmxlZnQ+Yy5yaWdodHx8YS50b3A+Yy5ib3R0b20pfHwoInNjcm9sbCI9PT1nLm92ZXJmbG93fHwiYXV0byI9PT1nLm92ZXJmbG93fHxiIGluc3RhbmNlb2YgSFRNTEJvZHlFbGVtZW50fHxiIGluc3RhbmNlb2YgSFRNTEh0bWxFbGVtZW50KX0sdy5nZXRBbGxDZWxscz1mdW5jdGlvbihhKXt2YXIgYixjLGQsZSxmPVtdO2ZvcihiPTAsZD1hLnJvd3MubGVuZ3RoO2I8ZDtiKyspZm9yKGM9MCxlPWEucm93c1tiXS5jZWxscy5sZW5ndGg7YzxlO2MrKylmLnB1c2goYS5yb3dzW2JdLmNlbGxzW2NdKTtyZXR1cm4gZn0sdy5nZXRDZWxsUG9zaXRpb249ZnVuY3Rpb24oYSxiKXt2YXIgYyxkO2ZvcihifHwoYj13LnRvR3JpZCh2LmZpbmRVcChhLCJ0YWJsZSIpKSksYz0wO2M8Yi5sZW5ndGg7YysrKWlmKGJbY10mJihkPWJbY10uaW5kZXhPZihhKSxkIT09LTEpKXJldHVybnt4OmQseTpjfX0sdy5nZXRIZWFkZXJzPWZ1bmN0aW9uKGEpe2lmKGEuaGFzQXR0cmlidXRlKCJoZWFkZXJzIikpcmV0dXJuIGNvbW1vbnMuZG9tLmlkcmVmcyhhLCJoZWFkZXJzIik7dmFyIGI9Y29tbW9ucy50YWJsZS50b0dyaWQoY29tbW9ucy5kb20uZmluZFVwKGEsInRhYmxlIikpLGM9Y29tbW9ucy50YWJsZS5nZXRDZWxsUG9zaXRpb24oYSxiKSxkPXcudHJhdmVyc2UoImxlZnQiLGMsYikuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiB3LmlzUm93SGVhZGVyKGEpfSksZT13LnRyYXZlcnNlKCJ1cCIsYyxiKS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuIHcuaXNDb2x1bW5IZWFkZXIoYSl9KTtyZXR1cm5bXS5jb25jYXQoZCxlKS5yZXZlcnNlKCl9LHcuZ2V0U2NvcGU9ZnVuY3Rpb24oYSl7dmFyIGI9YS5nZXRBdHRyaWJ1dGUoInNjb3BlIiksYz1hLmdldEF0dHJpYnV0ZSgicm9sZSIpO2lmKGEgaW5zdGFuY2VvZiBFbGVtZW50PT0hMXx8WyJURCIsIlRIIl0uaW5kZXhPZihhLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCkpPT09LTEpdGhyb3cgbmV3IFR5cGVFcnJvcigiRXhwZWN0ZWQgVEQgb3IgVEggZWxlbWVudCIpO2lmKCJjb2x1bW5oZWFkZXIiPT09YylyZXR1cm4iY29sIjtpZigicm93aGVhZGVyIj09PWMpcmV0dXJuInJvdyI7aWYoImNvbCI9PT1ifHwicm93Ij09PWIpcmV0dXJuIGI7aWYoIlRIIiE9PWEubm9kZU5hbWUudG9VcHBlckNhc2UoKSlyZXR1cm4hMTt2YXIgZD13LnRvR3JpZCh2LmZpbmRVcChhLCJ0YWJsZSIpKSxlPXcuZ2V0Q2VsbFBvc2l0aW9uKGEpLGY9ZFtlLnldLnJlZHVjZShmdW5jdGlvbihhLGIpe3JldHVybiBhJiYiVEgiPT09Yi5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpfSwhMCk7aWYoZilyZXR1cm4iY29sIjt2YXIgZz1kLm1hcChmdW5jdGlvbihhKXtyZXR1cm4gYVtlLnhdfSkucmVkdWNlKGZ1bmN0aW9uKGEsYil7cmV0dXJuIGEmJiJUSCI9PT1iLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCl9LCEwKTtyZXR1cm4gZz8icm93IjoiYXV0byJ9LHcuaXNDb2x1bW5IZWFkZXI9ZnVuY3Rpb24oYSl7cmV0dXJuWyJjb2wiLCJhdXRvIl0uaW5kZXhPZih3LmdldFNjb3BlKGEpKSE9PS0xfSx3LmlzRGF0YUNlbGw9ZnVuY3Rpb24oYSl7cmV0dXJuISghYS5jaGlsZHJlbi5sZW5ndGgmJiFhLnRleHRDb250ZW50LnRyaW0oKSkmJiJURCI9PT1hLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCl9LHcuaXNEYXRhVGFibGU9ZnVuY3Rpb24oYSl7dmFyIGI9YS5nZXRBdHRyaWJ1dGUoInJvbGUiKTtpZigoInByZXNlbnRhdGlvbiI9PT1ifHwibm9uZSI9PT1iKSYmIXYuaXNGb2N1c2FibGUoYSkpcmV0dXJuITE7aWYoInRydWUiPT09YS5nZXRBdHRyaWJ1dGUoImNvbnRlbnRlZGl0YWJsZSIpfHx2LmZpbmRVcChhLCdbY29udGVudGVkaXRhYmxlPSJ0cnVlIl0nKSlyZXR1cm4hMDtpZigiZ3JpZCI9PT1ifHwidHJlZWdyaWQiPT09Ynx8InRhYmxlIj09PWIpcmV0dXJuITA7aWYoImxhbmRtYXJrIj09PWNvbW1vbnMuYXJpYS5nZXRSb2xlVHlwZShiKSlyZXR1cm4hMDtpZigiMCI9PT1hLmdldEF0dHJpYnV0ZSgiZGF0YXRhYmxlIikpcmV0dXJuITE7aWYoYS5nZXRBdHRyaWJ1dGUoInN1bW1hcnkiKSlyZXR1cm4hMDtpZihhLnRIZWFkfHxhLnRGb290fHxhLmNhcHRpb24pcmV0dXJuITA7Zm9yKHZhciBjPTAsZD1hLmNoaWxkcmVuLmxlbmd0aDtjPGQ7YysrKWlmKCJDT0xHUk9VUCI9PT1hLmNoaWxkcmVuW2NdLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCkpcmV0dXJuITA7Zm9yKHZhciBlLGYsZz0wLGg9YS5yb3dzLmxlbmd0aCxpPSExLGo9MDtqPGg7aisrKXtlPWEucm93c1tqXTtmb3IodmFyIGs9MCxsPWUuY2VsbHMubGVuZ3RoO2s8bDtrKyspe2lmKGY9ZS5jZWxsc1trXSwiVEgiPT09Zi5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpKXJldHVybiEwO2lmKGl8fGYub2Zmc2V0V2lkdGg9PT1mLmNsaWVudFdpZHRoJiZmLm9mZnNldEhlaWdodD09PWYuY2xpZW50SGVpZ2h0fHwoaT0hMCksZi5nZXRBdHRyaWJ1dGUoInNjb3BlIil8fGYuZ2V0QXR0cmlidXRlKCJoZWFkZXJzIil8fGYuZ2V0QXR0cmlidXRlKCJhYmJyIikpcmV0dXJuITA7aWYoWyJjb2x1bW5oZWFkZXIiLCJyb3doZWFkZXIiXS5pbmRleE9mKGYuZ2V0QXR0cmlidXRlKCJyb2xlIikpIT09LTEpcmV0dXJuITA7aWYoMT09PWYuY2hpbGRyZW4ubGVuZ3RoJiYiQUJCUiI9PT1mLmNoaWxkcmVuWzBdLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCkpcmV0dXJuITA7ZysrfX1pZihhLmdldEVsZW1lbnRzQnlUYWdOYW1lKCJ0YWJsZSIpLmxlbmd0aClyZXR1cm4hMTtpZihoPDIpcmV0dXJuITE7dmFyIG09YS5yb3dzW01hdGguY2VpbChoLzIpXTtpZigxPT09bS5jZWxscy5sZW5ndGgmJjE9PT1tLmNlbGxzWzBdLmNvbFNwYW4pcmV0dXJuITE7aWYobS5jZWxscy5sZW5ndGg+PTUpcmV0dXJuITA7aWYoaSlyZXR1cm4hMDt2YXIgbixvO2ZvcihqPTA7ajxoO2orKyl7aWYoZT1hLnJvd3Nbal0sbiYmbiE9PXdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGUpLmdldFByb3BlcnR5VmFsdWUoImJhY2tncm91bmQtY29sb3IiKSlyZXR1cm4hMDtpZihuPXdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGUpLmdldFByb3BlcnR5VmFsdWUoImJhY2tncm91bmQtY29sb3IiKSxvJiZvIT09d2luZG93LmdldENvbXB1dGVkU3R5bGUoZSkuZ2V0UHJvcGVydHlWYWx1ZSgiYmFja2dyb3VuZC1pbWFnZSIpKXJldHVybiEwO289d2luZG93LmdldENvbXB1dGVkU3R5bGUoZSkuZ2V0UHJvcGVydHlWYWx1ZSgiYmFja2dyb3VuZC1pbWFnZSIpfXJldHVybiBoPj0yMHx8ISh2LmdldEVsZW1lbnRDb29yZGluYXRlcyhhKS53aWR0aD4uOTUqdi5nZXRWaWV3cG9ydFNpemUod2luZG93KS53aWR0aCkmJighKGc8MTApJiYhYS5xdWVyeVNlbGVjdG9yKCJvYmplY3QsIGVtYmVkLCBpZnJhbWUsIGFwcGxldCIpKX0sdy5pc0hlYWRlcj1mdW5jdGlvbihhKXtyZXR1cm4hKCF3LmlzQ29sdW1uSGVhZGVyKGEpJiYhdy5pc1Jvd0hlYWRlcihhKSl8fCEhYS5pZCYmISFkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdbaGVhZGVyc349IicrYXhlLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEuaWQpKyciXScpfSx3LmlzUm93SGVhZGVyPWZ1bmN0aW9uKGEpe3JldHVyblsicm93IiwiYXV0byJdLmluZGV4T2Yody5nZXRTY29wZShhKSkhPT0tMX0sdy50b0dyaWQ9ZnVuY3Rpb24oYSl7Zm9yKHZhciBiPVtdLGM9YS5yb3dzLGQ9MCxlPWMubGVuZ3RoO2Q8ZTtkKyspe3ZhciBmPWNbZF0uY2VsbHM7YltkXT1iW2RdfHxbXTtmb3IodmFyIGc9MCxoPTAsaT1mLmxlbmd0aDtoPGk7aCsrKWZvcih2YXIgaj0wO2o8ZltoXS5jb2xTcGFuO2orKyl7Zm9yKHZhciBrPTA7azxmW2hdLnJvd1NwYW47aysrKXtmb3IoYltkK2tdPWJbZCtrXXx8W107YltkK2tdW2ddOylnKys7YltkK2tdW2ddPWZbaF19ZysrfX1yZXR1cm4gYn0sdy50b0FycmF5PXcudG9HcmlkLGZ1bmN0aW9uKGEpe3ZhciBiPWZ1bmN0aW9uIGMoYSxiLGQsZSl7dmFyIGYsZz1kW2IueV0/ZFtiLnldW2IueF06dm9pZCAwO3JldHVybiBnPyJmdW5jdGlvbiI9PXR5cGVvZiBlJiYoZj1lKGcsYixkKSxmPT09ITApP1tnXTooZj1jKGEse3g6Yi54K2EueCx5OmIueSthLnl9LGQsZSksZi51bnNoaWZ0KGcpLGYpOltdfTthLnRyYXZlcnNlPWZ1bmN0aW9uKGEsYyxkLGUpe2lmKEFycmF5LmlzQXJyYXkoYykmJihlPWQsZD1jLGM9e3g6MCx5OjB9KSwic3RyaW5nIj09dHlwZW9mIGEpc3dpdGNoKGEpe2Nhc2UibGVmdCI6YT17eDotMSx5OjB9O2JyZWFrO2Nhc2UidXAiOmE9e3g6MCx5Oi0xfTticmVhaztjYXNlInJpZ2h0IjphPXt4OjEseTowfTticmVhaztjYXNlImRvd24iOmE9e3g6MCx5OjF9fXJldHVybiBiKGEse3g6Yy54K2EueCx5OmMueSthLnl9LGQsZSl9fSh3KTt2YXIgQT17c3VibWl0OiJTdWJtaXQiLHJlc2V0OiJSZXNldCJ9LEI9WyJ0ZXh0Iiwic2VhcmNoIiwidGVsIiwidXJsIiwiZW1haWwiLCJkYXRlIiwidGltZSIsIm51bWJlciIsInJhbmdlIiwiY29sb3IiXSxDPVsiQSIsIkVNIiwiU1RST05HIiwiU01BTEwiLCJNQVJLIiwiQUJCUiIsIkRGTiIsIkkiLCJCIiwiUyIsIlUiLCJDT0RFIiwiVkFSIiwiU0FNUCIsIktCRCIsIlNVUCIsIlNVQiIsIlEiLCJDSVRFIiwiU1BBTiIsIkJETyIsIkJESSIsIkJSIiwiV0JSIiwiSU5TIiwiREVMIiwiSU1HIiwiRU1CRUQiLCJPQkpFQ1QiLCJJRlJBTUUiLCJNQVAiLCJBUkVBIiwiU0NSSVBUIiwiTk9TQ1JJUFQiLCJSVUJZIiwiVklERU8iLCJBVURJTyIsIklOUFVUIiwiVEVYVEFSRUEiLCJTRUxFQ1QiLCJCVVRUT04iLCJMQUJFTCIsIk9VVFBVVCIsIkRBVEFMSVNUIiwiS0VZR0VOIiwiUFJPR1JFU1MiLCJDT01NQU5EIiwiQ0FOVkFTIiwiVElNRSIsIk1FVEVSIl07cmV0dXJuIHguYWNjZXNzaWJsZVRleHQ9ZnVuY3Rpb24oYSxiKXtmdW5jdGlvbiBjKGEsYixjKXsKZm9yKHZhciBkLGU9YS5jaGlsZE5vZGVzLGc9IiIsaD0wO2g8ZS5sZW5ndGg7aCsrKWQ9ZVtoXSwzPT09ZC5ub2RlVHlwZT9nKz1kLnRleHRDb250ZW50OjE9PT1kLm5vZGVUeXBlJiYoQy5pbmRleE9mKGQubm9kZU5hbWUudG9VcHBlckNhc2UoKSk9PT0tMSYmKGcrPSIgIiksZys9ZihlW2hdLGIsYykpO3JldHVybiBnfWZ1bmN0aW9uIGQoYSxiLGQpe3ZhciBlPSIiLGc9YS5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpO2lmKGwoYSkmJihlPWMoYSwhMSwhMSl8fCIiLHIoZSkpKXJldHVybiBlO2lmKCJGSUdVUkUiPT09ZyYmKGU9byhhLCJmaWdjYXB0aW9uIikscihlKSkpcmV0dXJuIGU7aWYoIlRBQkxFIj09PWcpe2lmKGU9byhhLCJjYXB0aW9uIikscihlKSlyZXR1cm4gZTtpZihlPWEuZ2V0QXR0cmlidXRlKCJ0aXRsZSIpfHxhLmdldEF0dHJpYnV0ZSgic3VtbWFyeSIpfHwiIixyKGUpKXJldHVybiBlfWlmKHEoYSkpcmV0dXJuIGEuZ2V0QXR0cmlidXRlKCJhbHQiKXx8IiI7aWYoayhhKSYmIWQpe2lmKGooYSkpcmV0dXJuIGEudmFsdWV8fGEudGl0bGV8fEFbYS50eXBlXXx8IiI7dmFyIGg9aShhKTtpZihoKXJldHVybiBmKGgsYiwhMCl9cmV0dXJuIiJ9ZnVuY3Rpb24gZShhLGIsYyl7cmV0dXJuIWImJmEuaGFzQXR0cmlidXRlKCJhcmlhLWxhYmVsbGVkYnkiKT94LnNhbml0aXplKHYuaWRyZWZzKGEsImFyaWEtbGFiZWxsZWRieSIpLm1hcChmdW5jdGlvbihiKXtyZXR1cm4gYT09PWImJmcucG9wKCksZihiLCEwLGEhPT1iKX0pLmpvaW4oIiAiKSk6YyYmcChhKXx8IWEuaGFzQXR0cmlidXRlKCJhcmlhLWxhYmVsIik/IiI6eC5zYW5pdGl6ZShhLmdldEF0dHJpYnV0ZSgiYXJpYS1sYWJlbCIpKX12YXIgZixnPVtdO3JldHVybiBmPWZ1bmN0aW9uKGEsYixmKXsidXNlIHN0cmljdCI7dmFyIGg7aWYobnVsbD09PWF8fGcuaW5kZXhPZihhKSE9PS0xKXJldHVybiIiO2lmKCFiJiYhdi5pc1Zpc2libGUoYSwhMCkpcmV0dXJuIiI7Zy5wdXNoKGEpO3ZhciBpPWEuZ2V0QXR0cmlidXRlKCJyb2xlIik7cmV0dXJuIGg9ZShhLGIsZikscihoKT9oOihoPWQoYSxiLGYpLHIoaCk/aDpmJiYoaD1uKGEpLHIoaCkpP2g6bShhKXx8aSYmcy5nZXRSb2xlc1dpdGhOYW1lRnJvbUNvbnRlbnRzKCkuaW5kZXhPZihpKT09PS0xfHwoaD1jKGEsYixmKSwhcihoKSk/YS5oYXNBdHRyaWJ1dGUoInRpdGxlIik/YS5nZXRBdHRyaWJ1dGUoInRpdGxlIik6IiI6aCl9LHguc2FuaXRpemUoZihhLGIpKX0seC5sYWJlbD1mdW5jdGlvbihhKXt2YXIgYixjO3JldHVybihjPXMubGFiZWwoYSkpP2M6YS5pZCYmKGI9ZG9jdW1lbnQucXVlcnlTZWxlY3RvcignbGFiZWxbZm9yPSInK2F4ZS51dGlscy5lc2NhcGVTZWxlY3RvcihhLmlkKSsnIl0nKSxjPWImJngudmlzaWJsZShiLCEwKSk/YzooYj12LmZpbmRVcChhLCJsYWJlbCIpLGM9YiYmeC52aXNpYmxlKGIsITApLGM/YzpudWxsKX0seC5zYW5pdGl6ZT1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7cmV0dXJuIGEucmVwbGFjZSgvXHJcbi9nLCJcbiIpLnJlcGxhY2UoL1x1MDBBMC9nLCIgIikucmVwbGFjZSgvW1xzXXsyLH0vZywiICIpLnRyaW0oKX0seC52aXNpYmxlPWZ1bmN0aW9uKGEsYixjKXsidXNlIHN0cmljdCI7dmFyIGQsZSxmLGc9YS5jaGlsZE5vZGVzLGg9Zy5sZW5ndGgsaT0iIjtmb3IoZD0wO2Q8aDtkKyspZT1nW2RdLDM9PT1lLm5vZGVUeXBlPyhmPWUubm9kZVZhbHVlLGYmJnYuaXNWaXNpYmxlKGEsYikmJihpKz1lLm5vZGVWYWx1ZSkpOmN8fChpKz14LnZpc2libGUoZSxiKSk7cmV0dXJuIHguc2FuaXRpemUoaSl9LGF4ZS51dGlscy50b0FycmF5PWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtyZXR1cm4gQXJyYXkucHJvdG90eXBlLnNsaWNlLmNhbGwoYSl9LGF4ZS51dGlscy50b2tlbkxpc3Q9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3JldHVybiBhLnRyaW0oKS5yZXBsYWNlKC9cc3syLH0vZywiICIpLnNwbGl0KCIgIil9LGNvbW1vbnN9KCl9KX0oIm9iamVjdCI9PXR5cGVvZiB3aW5kb3c/d2luZG93OnRoaXMpOw==","base64");
-
-
-
-
-
-function runA11yChecks(){
-return axe.run(document,{
-runOnly:{
-type:'rule',
-values:[
-'aria-allowed-attr',
-'aria-required-attr',
-'aria-valid-attr',
-'aria-valid-attr-value',
-'color-contrast',
-'image-alt',
-'label',
-'tabindex']}});
-
-
-
+// This is run in the page, not Lighthouse itself.
+// axe.run returns a promise which fulfills with a results object
+// containing any violations.
+/* istanbul ignore next */
+function runA11yChecks() {
+  return axe.run(document, {
+    runOnly: {
+      type: 'tag',
+      values: [
+        'wcag2a',
+        'wcag2aa'
+      ]
+    },
+    rules: {
+      'tabindex': {enabled: true},
+      'table-fake-caption': {enabled: true},
+      'td-has-header': {enabled: true},
+      'area-alt': {enabled: false},
+      'blink': {enabled: false},
+      'server-side-image-map': {enabled: false}
+    }
+  });
 }
 
-class Accessibility extends Gatherer{
-
-
-
-
-afterPass(options){
-const driver=options.driver;
-const expression=`(function () {
+class Accessibility extends Gatherer {
+  /**
+   * @param {!Object} options
+   * @return {!Promise<{violations: !Array}>}
+   */
+  afterPass(options) {
+    const driver = options.driver;
+    const expression = `(function () {
       ${axe};
       return (${runA11yChecks.toString()}());
     })()`;
 
-return driver.
-evaluateAsync(expression).
-then(returnedValue=>{
-if(!returnedValue||!Array.isArray(returnedValue.violations)){
-throw new Error('Unable to parse axe results'+returnedValue);
+    return driver
+      .evaluateAsync(expression)
+      .then(returnedValue => {
+        if (!returnedValue || !Array.isArray(returnedValue.violations)) {
+          throw new Error('Unable to parse axe results' + returnedValue);
+        }
+
+        return returnedValue;
+      });
+  }
 }
 
-return returnedValue;
-});
-}}
+module.exports = Accessibility;
 
-
-module.exports=Accessibility;
-
-}).call(this,require("buffer").Buffer);
-},{"./gatherer":13,"buffer":199}],"./gatherers/cache-contents":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+}).call(this,require("buffer").Buffer)
+},{"./gatherer":13,"buffer":205}],"./gatherers/cache-contents":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
+/* global caches */
 
+const Gatherer = require('./gatherer');
 
-const Gatherer=require('./gatherer');
+// This is run in the page, not Lighthouse itself.
+/* istanbul ignore next */
+function getCacheContents() {
+  // Get every cache by name.
+  return caches.keys()
 
+      // Open each one.
+      .then(cacheNames => Promise.all(cacheNames.map(cacheName => caches.open(cacheName))))
 
+      .then(caches => {
+        const requests = [];
 
-function getCacheContents(){
-
-return caches.keys().
-
-
-then(cacheNames=>Promise.all(cacheNames.map(cacheName=>caches.open(cacheName)))).
-
-then(caches=>{
-const requests=[];
-
-
-return Promise.all(caches.map(cache=>{
-return cache.keys().
-then(reqs=>{
-requests.push(...reqs.map(r=>r.url));
-});
-})).then(_=>{
-return requests;
-});
-});
+        // Take each cache and get any requests is contains, and bounce each one down to its URL.
+        return Promise.all(caches.map(cache => {
+          return cache.keys()
+              .then(reqs => {
+                requests.push(...reqs.map(r => r.url));
+              });
+        })).then(_ => {
+          return requests;
+        });
+      });
 }
 
-class CacheContents extends Gatherer{
+class CacheContents extends Gatherer {
+  /**
+   * Creates an array of cached URLs.
+   * @param {!Object} options
+   * @return {!Promise<!Array<string>>}
+   */
+  afterPass(options) {
+    const driver = options.driver;
 
-
-
-
-
-afterPass(options){
-const driver=options.driver;
-
-return driver.
-evaluateAsync(`(${getCacheContents.toString()}())`).
-then(returnedValue=>{
-if(!returnedValue||!Array.isArray(returnedValue)){
-throw new Error('Unable to retrieve cache contents');
+    return driver
+        .evaluateAsync(`(${getCacheContents.toString()}())`)
+        .then(returnedValue => {
+          if (!returnedValue || !Array.isArray(returnedValue)) {
+            throw new Error('Unable to retrieve cache contents');
+          }
+          return returnedValue;
+        });
+  }
 }
-return returnedValue;
-});
-}}
 
-
-module.exports=CacheContents;
+module.exports = CacheContents;
 
 },{"./gatherer":13}],"./gatherers/chrome-console-messages":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Gathers console deprecation and intervention warnings logged by Chrome.
+ */
 
 'use strict';
 
-const Gatherer=require('./gatherer');
+const Gatherer = require('./gatherer');
 
-class ChromeConsoleMessages extends Gatherer{
+class ChromeConsoleMessages extends Gatherer {
 
-constructor(){
-super();
-this._logEntries=[];
-this._onConsoleEntryAdded=this.onConsoleEntry.bind(this);
+  constructor() {
+    super();
+    this._logEntries = [];
+    this._onConsoleEntryAdded = this.onConsoleEntry.bind(this);
+  }
+
+  onConsoleEntry(entry) {
+    this._logEntries.push(entry);
+  }
+
+  beforePass(options) {
+    options.driver.on('Log.entryAdded', this._onConsoleEntryAdded);
+    return options.driver.sendCommand('Log.enable');
+  }
+
+  afterPass(options) {
+    options.driver.off('Log.entryAdded', this._onConsoleEntryAdded);
+    return options.driver.sendCommand('Log.disable')
+        .then(_ => this._logEntries);
+  }
 }
 
-onConsoleEntry(entry){
-this._logEntries.push(entry);
-}
-
-beforePass(options){
-options.driver.on('Log.entryAdded',this._onConsoleEntryAdded);
-return options.driver.sendCommand('Log.enable');
-}
-
-afterPass(options){
-options.driver.off('Log.entryAdded',this._onConsoleEntryAdded);
-return options.driver.sendCommand('Log.disable').
-then(_=>this._logEntries);
-}}
-
-
-module.exports=ChromeConsoleMessages;
-
-},{"./gatherer":13}],"./gatherers/content-width":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-'use strict';
-
-const Gatherer=require('./gatherer');
-
-
-
-
-function getContentWidth(){
-
-
-
-return Promise.resolve({
-scrollWidth:window.innerWidth,
-viewportWidth:window.outerWidth,
-devicePixelRatio:window.devicePixelRatio});
-
-}
-
-class ContentWidth extends Gatherer{
-
-
-
-
-
-afterPass(options){
-const driver=options.driver;
-
-return driver.evaluateAsync(`(${getContentWidth.toString()}())`).
-
-then(returnedValue=>{
-if(!Number.isFinite(returnedValue.scrollWidth)||
-!Number.isFinite(returnedValue.viewportWidth)||
-!Number.isFinite(returnedValue.devicePixelRatio)){
-throw new Error(`ContentWidth results were not numeric: ${JSON.stringify(returnedValue)}`);
-}
-
-return returnedValue;
-});
-}}
-
-
-module.exports=ContentWidth;
+module.exports = ChromeConsoleMessages;
 
 },{"./gatherer":13}],"./gatherers/css-usage":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-const Gatherer=require('./gatherer');
+const Gatherer = require('./gatherer');
 
+/**
+ * @fileoverview Tracks unused CSS rules.
+ */
+class CSSUsage extends Gatherer {
+  beforePass(options) {
+    return options.driver.sendCommand('DOM.enable')
+      .then(_ => options.driver.sendCommand('CSS.enable'))
+      .then(_ => options.driver.sendCommand('CSS.startRuleUsageTracking'))
+      .catch(err => {
+        // TODO(phulce): Remove this once CSS usage hits stable
+        if (/startRuleUsageTracking/.test(err.message)) {
+          throw new Error('CSS Usage tracking requires Chrome \u2265 56');
+        }
 
+        throw err;
+      });
+  }
 
+  afterPass(options) {
+    const driver = options.driver;
 
-class CSSUsage extends Gatherer{
-beforePass(options){
-return options.driver.sendCommand('DOM.enable').
-then(_=>options.driver.sendCommand('CSS.enable')).
-then(_=>options.driver.sendCommand('CSS.startRuleUsageTracking')).
-catch(err=>{
-
-if(/startRuleUsageTracking/.test(err.message)){
-throw new Error('CSS Usage tracking requires Chrome \u2265 56');
+    return driver.sendCommand('CSS.stopRuleUsageTracking').then(results => {
+      return driver.sendCommand('CSS.disable')
+        .then(_ => driver.sendCommand('DOM.disable'))
+        .then(_ => results.ruleUsage);
+    });
+  }
 }
 
-throw err;
-});
-}
-
-afterPass(options){
-const driver=options.driver;
-
-return driver.sendCommand('CSS.stopRuleUsageTracking').then(results=>{
-return driver.sendCommand('CSS.disable').
-then(_=>driver.sendCommand('DOM.disable')).
-then(_=>results.ruleUsage);
-});
-}}
-
-
-module.exports=CSSUsage;
+module.exports = CSSUsage;
 
 },{"./gatherer":13}],"./gatherers/dobetterweb/all-event-listeners":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Tests whether the page is using passive event listeners.
+ */
 
 'use strict';
 
-const Gatherer=require('../gatherer');
+const Gatherer = require('../gatherer');
 
-class EventListeners extends Gatherer{
+class EventListeners extends Gatherer {
 
-listenForScriptParsedEvents(){
-return this.driver.sendCommand('Debugger.enable').then(_=>{
-this.driver.on('Debugger.scriptParsed',script=>{
-this._parsedScripts.set(script.scriptId,script);
-});
-});
+  listenForScriptParsedEvents() {
+    return this.driver.sendCommand('Debugger.enable').then(_ => {
+      this.driver.on('Debugger.scriptParsed', script => {
+        this._parsedScripts.set(script.scriptId, script);
+      });
+    });
+  }
+
+  unlistenForScriptParsedEvents() {
+    this.driver.off('Debugger.scriptParsed', this.listenForScriptParsedEvents);
+    return this.driver.sendCommand('Debugger.disable');
+  }
+
+  /**
+   * @param {number|string} nodeIdOrObject The node id of the element or the
+   *     string of and object ('document', 'window').
+   * @return {!Promise<!Array<{listeners: !Array, tagName: string}>>}
+   * @private
+   */
+  _listEventListeners(nodeIdOrObject) {
+    let promise;
+
+    if (typeof nodeIdOrObject === 'string') {
+      promise = this.driver.sendCommand('Runtime.evaluate', {
+        expression: nodeIdOrObject,
+        objectGroup: 'event-listeners-gatherer' // populates event handler info.
+      });
+    } else {
+      promise = this.driver.sendCommand('DOM.resolveNode', {
+        nodeId: nodeIdOrObject,
+        objectGroup: 'event-listeners-gatherer' // populates event handler info.
+      });
+    }
+
+    return promise.then(result => {
+      const obj = result.object || result.result;
+      return this.driver.sendCommand('DOMDebugger.getEventListeners', {
+        objectId: obj.objectId
+      }).then(results => {
+        return {listeners: results.listeners, tagName: obj.description};
+      });
+    });
+  }
+
+  /**
+   * Collects the event listeners attached to an object and formats the results.
+   * listenForScriptParsedEvents should be called before this method to ensure
+   * the page's parsed scripts are collected at page load.
+   * @param {string} nodeId The node to look for attached event listeners.
+   * @return {!Promise<!Array<!Object>>} List of event listeners attached to
+   *     the node.
+   */
+  getEventListeners(nodeId) {
+    const matchedListeners = [];
+
+    return this._listEventListeners(nodeId).then(results => {
+      results.listeners.forEach(listener => {
+        // Slim down the list of parsed scripts to match the found event
+        // listeners that have the same script id.
+        const script = this._parsedScripts.get(listener.scriptId);
+        if (script) {
+          // Combine the EventListener object and the result of the
+          // Debugger.scriptParsed event so we get .url and other
+          // needed properties.
+          const combo = Object.assign(listener, script);
+          combo.objectName = results.tagName;
+
+          // Note: line/col numbers are zero-index. Add one to each so we have
+          // actual file line/col numbers.
+          combo.line = combo.lineNumber + 1;
+          combo.col = combo.columnNumber + 1;
+
+          matchedListeners.push(combo);
+        }
+      });
+
+      return matchedListeners;
+    });
+  }
+
+  /**
+   * Aggregates the event listeners used on each element into a single list.
+   * @param {!Array<!Element>} nodes List of elements to fetch event listeners for.
+   * @return {!Promise<!Array<!Object>>} Resolves to a list of all the event
+   *     listeners found across the elements.
+   */
+  collectListeners(nodes) {
+    // Gather event listeners from each node in parallel.
+    return Promise.all(nodes.map(node => {
+      return this.getEventListeners(node.element ? node.element.nodeId : node);
+    })).then(nestedListeners => [].concat(...nestedListeners));
+  }
+
+  beforePass(options) {
+    this.driver = options.driver;
+    this._parsedScripts = new Map();
+    return this.listenForScriptParsedEvents();
+  }
+
+  /**
+   * @param {!Object} options
+   * @return {!Promise<!Array<!Object>>}
+   */
+  afterPass(options) {
+    return this.unlistenForScriptParsedEvents()
+      .then(_ => options.driver.querySelectorAll('body, body /deep/ *')) // drill into shadow trees
+      .then(nodes => {
+        nodes.push('document', 'window');
+        return this.collectListeners(nodes);
+      });
+  }
 }
 
-unlistenForScriptParsedEvents(){
-this.driver.off('Debugger.scriptParsed',this.listenForScriptParsedEvents);
-return this.driver.sendCommand('Debugger.disable');
-}
-
-
-
-
-
-
-
-_listEventListeners(nodeIdOrObject){
-let promise;
-
-if(typeof nodeIdOrObject==='string'){
-promise=this.driver.sendCommand('Runtime.evaluate',{
-expression:nodeIdOrObject,
-objectGroup:'event-listeners-gatherer'});
-
-}else{
-promise=this.driver.sendCommand('DOM.resolveNode',{
-nodeId:nodeIdOrObject,
-objectGroup:'event-listeners-gatherer'});
-
-}
-
-return promise.then(result=>{
-const obj=result.object||result.result;
-return this.driver.sendCommand('DOMDebugger.getEventListeners',{
-objectId:obj.objectId}).
-then(results=>{
-return{listeners:results.listeners,tagName:obj.description};
-});
-});
-}
-
-
-
-
-
-
-
-
-
-getEventListeners(nodeId){
-const matchedListeners=[];
-
-return this._listEventListeners(nodeId).then(results=>{
-results.listeners.forEach(listener=>{
-
-
-const script=this._parsedScripts.get(listener.scriptId);
-if(script){
-
-
-
-const combo=Object.assign(listener,script);
-combo.objectName=results.tagName;
-
-
-
-combo.line=combo.lineNumber+1;
-combo.col=combo.columnNumber+1;
-
-matchedListeners.push(combo);
-}
-});
-
-return matchedListeners;
-});
-}
-
-
-
-
-
-
-
-collectListeners(nodes){
-
-return Promise.all(nodes.map(node=>{
-return this.getEventListeners(node.element?node.element.nodeId:node);
-})).then(nestedListeners=>[].concat(...nestedListeners));
-}
-
-beforePass(options){
-this.driver=options.driver;
-this._parsedScripts=new Map();
-return this.listenForScriptParsedEvents();
-}
-
-
-
-
-
-afterPass(options){
-return this.unlistenForScriptParsedEvents().
-then(_=>options.driver.querySelectorAll('body, body /deep/ *')).
-then(nodes=>{
-nodes.push('document','window');
-return this.collectListeners(nodes);
-});
-}}
-
-
-module.exports=EventListeners;
+module.exports = EventListeners;
 
 },{"../gatherer":13}],"./gatherers/dobetterweb/anchors-with-no-rel-noopener":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-const Gatherer=require('../gatherer');
+const Gatherer = require('../gatherer');
 
-class AnchorsWithNoRelNoopener extends Gatherer{
+class AnchorsWithNoRelNoopener extends Gatherer {
+  /**
+   * @param {!Object} options
+   * @return {!Promise<!Array<{href: string, rel: string, target: string}>>}
+   */
+  afterPass(options) {
+    const driver = options.driver;
+    return driver.querySelectorAll('a[target="_blank"]:not([rel~="noopener"])')
+      .then(failingNodeList => {
+        const failingNodes = failingNodeList.map(node => {
+          return Promise.all([
+            node.getAttribute('href'),
+            node.getAttribute('rel'),
+            node.getAttribute('target')
+          ]);
+        });
+        return Promise.all(failingNodes);
+      })
+      .then(failingNodes => {
+        return failingNodes.map(node => {
+          return {
+            href: node[0],
+            rel: node[1],
+            target: node[2]
+          };
+        });
+      });
+  }
+}
 
-
-
-
-afterPass(options){
-const driver=options.driver;
-return driver.querySelectorAll('a[target="_blank"]:not([rel~="noopener"])').
-then(failingNodeList=>{
-const failingNodes=failingNodeList.map(node=>{
-return Promise.all([
-node.getAttribute('href'),
-node.getAttribute('rel'),
-node.getAttribute('target')]);
-
-});
-return Promise.all(failingNodes);
-}).
-then(failingNodes=>{
-return failingNodes.map(node=>{
-return{
-href:node[0],
-rel:node[1],
-target:node[2]};
-
-});
-});
-}}
-
-
-module.exports=AnchorsWithNoRelNoopener;
+module.exports = AnchorsWithNoRelNoopener;
 
 },{"../gatherer":13}],"./gatherers/dobetterweb/appcache":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Gatherer=require('../gatherer');
+const Gatherer = require('../gatherer');
 
-class AppCacheManifest extends Gatherer{
+class AppCacheManifest extends Gatherer {
+  /**
+   * Retrurns the value of the html element's manifest attribute or null if it
+   * is not defined.
+   * @param {!Object} options
+   * @return {!Promise<?string>}
+   */
+  afterPass(options) {
+    const driver = options.driver;
 
+    return driver.querySelector('html')
+      .then(node => node && node.getAttribute('manifest'));
+  }
+}
 
-
-
-
-
-afterPass(options){
-const driver=options.driver;
-
-return driver.querySelector('html').
-then(node=>node&&node.getAttribute('manifest'));
-}}
-
-
-module.exports=AppCacheManifest;
+module.exports = AppCacheManifest;
 
 },{"../gatherer":13}],"./gatherers/dobetterweb/console-time-usage":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Tests whether the page is using console.time().
+ */
 
 'use strict';
 
-const Gatherer=require('../gatherer');
+const Gatherer = require('../gatherer');
 
-class ConsoleTimeUsage extends Gatherer{
+class ConsoleTimeUsage extends Gatherer {
 
-beforePass(options){
-this.collectUsage=options.driver.captureFunctionCallSites('console.time');
+  beforePass(options) {
+    this.collectUsage = options.driver.captureFunctionCallSites('console.time');
+  }
+
+  /**
+   * @return {!Promise<!Array<{url: string, line: number, col: number}>>}
+   */
+  afterPass() {
+    return this.collectUsage();
+  }
 }
 
-
-
-
-afterPass(){
-return this.collectUsage();
-}}
-
-
-module.exports=ConsoleTimeUsage;
+module.exports = ConsoleTimeUsage;
 
 },{"../gatherer":13}],"./gatherers/dobetterweb/datenow":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Tests whether the page is using Date.now().
+ */
 
 'use strict';
 
-const Gatherer=require('../gatherer');
+const Gatherer = require('../gatherer');
 
-class DateNowUse extends Gatherer{
+class DateNowUse extends Gatherer {
 
-beforePass(options){
-this.collectUsage=options.driver.captureFunctionCallSites('Date.now');
+  beforePass(options) {
+    this.collectUsage = options.driver.captureFunctionCallSites('Date.now');
+  }
+
+  /**
+   * @return {!Promise<!Array<{url: string, line: number, col: number}>>}
+   */
+  afterPass() {
+    return this.collectUsage();
+  }
 }
 
-
-
-
-afterPass(){
-return this.collectUsage();
-}}
-
-
-module.exports=DateNowUse;
+module.exports = DateNowUse;
 
 },{"../gatherer":13}],"./gatherers/dobetterweb/document-write":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Tests whether the page is using document.write().
+ */
 
 'use strict';
 
-const Gatherer=require('../gatherer');
+const Gatherer = require('../gatherer');
 
-class DocWriteUse extends Gatherer{
+class DocWriteUse extends Gatherer {
 
-beforePass(options){
-this.collectUsage=options.driver.captureFunctionCallSites('document.write');
+  beforePass(options) {
+    this.collectUsage = options.driver.captureFunctionCallSites('document.write');
+  }
+
+  /**
+   * @return {!Promise<!Array<{url: string, line: number, col: number}>>}
+   */
+  afterPass() {
+    return this.collectUsage();
+  }
 }
 
-
-
-
-afterPass(){
-return this.collectUsage();
-}}
-
-
-module.exports=DocWriteUse;
+module.exports = DocWriteUse;
 
 },{"../gatherer":13}],"./gatherers/dobetterweb/domstats":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
+/**
+ * @fileoverview Gathers stats about the max height and width of the DOM tree
+ * and total number of nodes used on the page.
+ */
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/* global document */
 
 'use strict';
 
-const Gatherer=require('../gatherer');
+const Gatherer = require('../gatherer');
 
-
-
-
-
-
-
-
-function createSelectorsLabel(element){
-let name=element.localName;
-const idAttr=element.getAttribute&&element.getAttribute('id');
-if(idAttr){
-name+=`#${idAttr}`;
+/**
+ * Constructs a pretty label from element's selectors. For example, given
+ * <div id="myid" class="myclass">, returns 'div#myid.myclass'.
+ * @param {!HTMLElement} element
+ * @return {!string}
+ */
+/* istanbul ignore next */
+function createSelectorsLabel(element) {
+  let name = element.localName;
+  const idAttr = element.getAttribute && element.getAttribute('id');
+  if (idAttr) {
+    name += `#${idAttr}`;
+  }
+  // svg elements return SVGAnimatedString for .className, which is an object.
+  // Stringify classList instead.
+  const className = element.classList.toString();
+  if (className) {
+    name += `.${className.trim().replace(/\s+/g, '.')}`;
+  }
+  return name;
 }
 
-
-const className=element.classList.toString();
-if(className){
-name+=`.${className.trim().replace(/\s+/g,'.')}`;
-}
-return name;
+/**
+ * @param {!HTMLElement} element
+ * @return {!Array<string>}
+ */
+/* istanbul ignore next */
+function elementPathInDOM(element) {
+  const path = [createSelectorsLabel(element)];
+  let node = element;
+  while (node) {
+    // Anchor elements have a .host property. Be sure we've found a shadow root
+    // host and not an anchor.
+    const shadowHost = node.parentNode && node.parentNode.host &&
+                       node.parentNode.localName !== 'a';
+    node = shadowHost ? node.parentNode.host : node.parentElement;
+    if (node) {
+      path.unshift(createSelectorsLabel(node));
+    }
+  }
+  return path;
 }
 
+/**
+ * Calculates the maximum tree depth of the DOM.
+ * @param {!HTMLElement} element Root of the tree to look in.
+ * @param {boolean=} deep True to include shadow roots. Defaults to true.
+ * @return {!number}
+ */
+/* istanbul ignore next */
+function getDOMStats(element, deep=true) {
+  let deepestNode = null;
+  let maxDepth = 0;
+  let maxWidth = 0;
+  let parentWithMostChildren = null;
 
+  const _calcDOMWidthAndHeight = function(element, depth=1) {
+    if (depth > maxDepth) {
+      deepestNode = element;
+      maxDepth = depth;
+    }
+    if (element.children.length > maxWidth) {
+      parentWithMostChildren = element;
+      maxWidth = element.children.length;
+    }
 
+    let child = element.firstElementChild;
+    while (child) {
+      _calcDOMWidthAndHeight(child, depth + 1);
+      // If node has shadow dom, traverse into that tree.
+      if (deep && child.shadowRoot) {
+        _calcDOMWidthAndHeight(child.shadowRoot, depth + 1);
+      }
+      child = child.nextElementSibling;
+    }
 
+    return {maxDepth, maxWidth};
+  };
 
+  const result = _calcDOMWidthAndHeight(element);
 
-function elementPathInDOM(element){
-const path=[createSelectorsLabel(element)];
-let node=element;
-while(node){
-
-
-const shadowHost=node.parentNode&&node.parentNode.host&&
-node.parentNode.localName!=='a';
-node=shadowHost?node.parentNode.host:node.parentElement;
-if(node){
-path.unshift(createSelectorsLabel(node));
-}
-}
-return path;
+  return {
+    totalDOMNodes: document.querySelectorAll('html, html /deep/ *').length,
+    depth: {
+      max: result.maxDepth,
+      pathToElement: elementPathInDOM(deepestNode),
+    },
+    width: {
+      max: result.maxWidth,
+      pathToElement: elementPathInDOM(parentWithMostChildren)
+    }
+  };
 }
 
-
-
-
-
-
-
-
-function getDOMStats(element,deep=true){
-let deepestNode=null;
-let maxDepth=0;
-let maxWidth=0;
-let parentWithMostChildren=null;
-
-const _calcDOMWidthAndHeight=function(element,depth=1){
-if(depth>maxDepth){
-deepestNode=element;
-maxDepth=depth;
-}
-if(element.children.length>maxWidth){
-parentWithMostChildren=element;
-maxWidth=element.children.length;
-}
-
-let child=element.firstElementChild;
-while(child){
-_calcDOMWidthAndHeight(child,depth+1);
-
-if(deep&&child.shadowRoot){
-_calcDOMWidthAndHeight(child.shadowRoot,depth+1);
-}
-child=child.nextElementSibling;
-}
-
-return{maxDepth,maxWidth};
-};
-
-const result=_calcDOMWidthAndHeight(element);
-
-return{
-totalDOMNodes:document.querySelectorAll('html, html /deep/ *').length,
-depth:{
-max:result.maxDepth,
-pathToElement:elementPathInDOM(deepestNode)},
-
-width:{
-max:result.maxWidth,
-pathToElement:elementPathInDOM(parentWithMostChildren)}};
-
-
-}
-
-class DOMStats extends Gatherer{
-
-
-
-
-afterPass(options){
-const expression=`(function() {
+class DOMStats extends Gatherer {
+  /**
+   * @param {!Object} options
+   * @return {!Promise<!Array<!Object>>}
+   */
+  afterPass(options) {
+    const expression = `(function() {
       ${createSelectorsLabel.toString()};
       ${elementPathInDOM.toString()};
       return (${getDOMStats.toString()}(document.documentElement));
     })()`;
-return options.driver.evaluateAsync(expression);
-}}
+    return options.driver.evaluateAsync(expression);
+  }
+}
 
-
-module.exports=DOMStats;
+module.exports = DOMStats;
 
 },{"../gatherer":13}],"./gatherers/dobetterweb/geolocation-on-start":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Captures calls to the geolocation API on page load.
+ */
 
 'use strict';
 
-const Gatherer=require('../gatherer');
+const Gatherer = require('../gatherer');
 
-class GeolocationOnStart extends Gatherer{
+class GeolocationOnStart extends Gatherer {
 
-beforePass(options){
-this.collectCurrentPosUsage=options.driver.captureFunctionCallSites(
-'navigator.geolocation.getCurrentPosition');
-this.collectWatchPosUsage=options.driver.captureFunctionCallSites(
-'navigator.geolocation.watchPosition');
+  beforePass(options) {
+    this.collectCurrentPosUsage = options.driver.captureFunctionCallSites(
+        'navigator.geolocation.getCurrentPosition');
+    this.collectWatchPosUsage = options.driver.captureFunctionCallSites(
+        'navigator.geolocation.watchPosition');
+  }
+
+  /**
+   * @param {!Object} options
+   * @return {!Promise<!Array<{url: string, line: number, col: number}>>}
+   */
+  afterPass(options) {
+    return options.driver.evaluateAsync('(function(){return window.isSecureContext;})()')
+      .then(isSecureContext => {
+        if (!isSecureContext) {
+          throw new Error('Unable to determine if the Geolocation permission requested on page ' +
+              'load because the page is not hosted on a secure origin. The Geolocation API ' +
+              'requires an https URL.');
+        }
+      })
+      .then(_ => options.driver.queryPermissionState('geolocation'))
+      .then(state => {
+        if (state === 'granted' || state === 'denied') {
+          throw new Error('Unable to determine if the Geolocation permission was ' +
+              `requested on page load because it was already ${state}. ` +
+              'Try resetting the permission and running Lighthouse again.');
+        }
+
+        return this.collectCurrentPosUsage().then(results => {
+          return this.collectWatchPosUsage().then(results2 => results.concat(results2));
+        });
+      });
+  }
 }
 
-
-
-
-
-afterPass(options){
-return options.driver.evaluateAsync('(function(){return window.isSecureContext;})()').
-then(isSecureContext=>{
-if(!isSecureContext){
-throw new Error('Unable to determine if the Geolocation permission requested on page '+
-'load because the page is not hosted on a secure origin. The Geolocation API '+
-'requires an https URL.');
-}
-}).
-then(_=>options.driver.queryPermissionState('geolocation')).
-then(state=>{
-if(state==='granted'||state==='denied'){
-throw new Error('Unable to determine if the Geolocation permission was '+
-`requested on page load because it was already ${state}. `+
-'Try resetting the permission and running Lighthouse again.');
-}
-
-return this.collectCurrentPosUsage().then(results=>{
-return this.collectWatchPosUsage().then(results2=>results.concat(results2));
-});
-});
-}}
-
-
-module.exports=GeolocationOnStart;
+module.exports = GeolocationOnStart;
 
 },{"../gatherer":13}],"./gatherers/dobetterweb/notification-on-start":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Captures calls to the notification API on page load.
+ */
 
 'use strict';
 
-const Gatherer=require('../gatherer');
+const Gatherer = require('../gatherer');
 
-class NotificationOnStart extends Gatherer{
+class NotificationOnStart extends Gatherer {
 
-beforePass(options){
-this.collectNotificationUsage=options.driver.captureFunctionCallSites(
-'Notification.requestPermission');
+  beforePass(options) {
+    this.collectNotificationUsage = options.driver.captureFunctionCallSites(
+        'Notification.requestPermission');
+  }
+
+  afterPass(options) {
+    return options.driver.queryPermissionState('notifications')
+      .then(state => {
+        if (state === 'granted' || state === 'denied') {
+          throw new Error('Unable to determine if the Notification permission was ' +
+              `requested on page load because it was already ${state}. ` +
+              'Try resetting the permission and running Lighthouse again.');
+        }
+
+        return this.collectNotificationUsage();
+      });
+  }
 }
 
-afterPass(options){
-return options.driver.queryPermissionState('notifications').
-then(state=>{
-if(state==='granted'||state==='denied'){
-throw new Error('Unable to determine if the Notification permission was '+
-`requested on page load because it was already ${state}. `+
-'Try resetting the permission and running Lighthouse again.');
-}
-
-return this.collectNotificationUsage();
-});
-}}
-
-
-module.exports=NotificationOnStart;
+module.exports = NotificationOnStart;
 
 },{"../gatherer":13}],"./gatherers/dobetterweb/optimized-images":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
+ /**
+  * @fileoverview Determines optimized jpeg/webp filesizes for all same-origin and dataURI images by
+  *   running the images through canvas in the browser context at quality 0.8.
+  */
+'use strict';
 
+const Gatherer = require('../gatherer');
+const URL = require('../../../lib/url-shim');
 
+/* global document, Image, atob */
 
+/**
+ * Runs in the context of the browser
+ * @param {string} url
+ * @return {!Promise<{jpeg: Object, webp: Object}>}
+ */
+/* istanbul ignore next */
+function getOptimizedNumBytes(url) {
+  return new Promise(function(resolve, reject) {
+    const img = new Image();
+    const canvas = document.createElement('canvas');
+    const context = canvas.getContext('2d');
 
+    function getTypeStats(type, quality) {
+      const dataURI = canvas.toDataURL(type, quality);
+      const base64 = dataURI.slice(dataURI.indexOf(',') + 1);
+      return {base64: base64.length, binary: atob(base64).length};
+    }
 
+    img.addEventListener('error', reject);
+    img.addEventListener('load', () => {
+      try {
+        canvas.height = img.height;
+        canvas.width = img.width;
+        context.drawImage(img, 0, 0);
 
+        const jpeg = getTypeStats('image/jpeg', 0.8);
+        const webp = getTypeStats('image/webp', 0.8);
 
+        resolve({jpeg, webp});
+      } catch (err) {
+        reject(err);
+      }
+    }, false);
 
+    img.src = url;
+  });
+}
 
+class OptimizedImages extends Gatherer {
+  /**
+   * @param {string} pageUrl
+   * @param {!NetworkRecords} networkRecords
+   * @return {!Array<{url: string, isBase64DataUri: boolean, mimeType: string, resourceSize: number}>}
+   */
+  static filterImageRequests(pageUrl, networkRecords) {
+    const seenUrls = new Set();
+    return networkRecords.reduce((prev, record) => {
+      if (seenUrls.has(record._url)) {
+        return prev;
+      }
 
+      seenUrls.add(record._url);
+      const isOptimizableImage = /image\/(png|bmp|jpeg)/.test(record._mimeType);
+      const isSameOrigin = URL.originsMatch(pageUrl, record._url);
+      const isBase64DataUri = /^data:.{2,40}base64\s*,/.test(record._url);
 
+      if (isOptimizableImage) {
+        prev.push({
+          isSameOrigin,
+          isBase64DataUri,
+          requestId: record._requestId,
+          url: record._url,
+          mimeType: record._mimeType,
+          resourceSize: record._resourceSize,
+        });
+      }
 
+      return prev;
+    }, []);
+  }
 
+  /**
+   * @param {!Object} driver
+   * @param {{url: string, isBase64DataUri: boolean, resourceSize: number}} networkRecord
+   * @return {!Promise<{originalSize: number, jpegSize: number, webpSize: number}>}
+   */
+  calculateImageStats(driver, networkRecord) {
+    let uriPromise = Promise.resolve(networkRecord.url);
 
+    // For cross-origin images, circumvent canvas CORS policy by getting image directly from protocol
+    if (!networkRecord.isSameOrigin && !networkRecord.isBase64DataUri) {
+      const requestId = networkRecord.requestId;
+      uriPromise = driver.sendCommand('Network.getResponseBody', {requestId}).then(resp => {
+        if (!resp.base64Encoded) {
+          throw new Error('Unable to fetch cross-origin image body');
+        }
 
+        return `data:${networkRecord.mimeType};base64,${resp.body}`;
+      });
+    }
 
+    return uriPromise.then(uri => {
+      const script = `(${getOptimizedNumBytes.toString()})(${JSON.stringify(uri)})`;
+      return driver.evaluateAsync(script).then(stats => {
+        const isBase64DataUri = networkRecord.isBase64DataUri;
+        const base64Length = networkRecord.url.length - networkRecord.url.indexOf(',') - 1;
+        return {
+          originalSize: isBase64DataUri ? base64Length : networkRecord.resourceSize,
+          jpegSize: isBase64DataUri ? stats.jpeg.base64 : stats.jpeg.binary,
+          webpSize: isBase64DataUri ? stats.webp.base64 : stats.webp.binary,
+        };
+      });
+    });
+  }
 
+  /**
+   * @param {!Object} driver
+   * @param {!Array<!Object>} imageRecords
+   * @return {!Promise<!Array<!Object>>}
+   */
+  computeOptimizedImages(driver, imageRecords) {
+    return imageRecords.reduce((promise, record) => {
+      return promise.then(results => {
+        return this.calculateImageStats(driver, record)
+          .catch(err => ({failed: true, err}))
+          .then(stats => results.concat(Object.assign(stats, record)));
+      });
+    }, Promise.resolve([]));
+  }
 
+  /**
+   * @param {!Object} options
+   * @param {{networkRecords: !Array<!NetworRecord>}} traceData
+   * @return {!Promise<!Array<!Object>}
+   */
+  afterPass(options, traceData) {
+    const networkRecords = traceData.networkRecords;
+    const imageRecords = OptimizedImages.filterImageRequests(options.url, networkRecords);
 
+    return options.driver.sendCommand('Network.enable')
+      .then(_ => this.computeOptimizedImages(options.driver, imageRecords))
+      .then(results => options.driver.sendCommand('Network.disable').then(_ => results))
+      .then(results => {
+        const successfulResults = results.filter(result => !result.failed);
+        if (results.length && !successfulResults.length) {
+          throw new Error('All image optimizations failed');
+        }
+
+        return results;
+      });
+  }
+}
+
+module.exports = OptimizedImages;
+
+},{"../../../lib/url-shim":25,"../gatherer":13}],"./gatherers/dobetterweb/response-compression":[function(require,module,exports){
+(function (Buffer){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ /**
+  * @fileoverview Determines optimized gzip/br/deflate filesizes for all responses by
+  *   checking the content-encoding header.
+  */
+'use strict';
+
+const Gatherer = require('../gatherer');
+const gzip = require('zlib').gzip;
+
+const compressionTypes = ['gzip', 'br', 'deflate'];
+
+class ResponseCompression extends Gatherer {
+  /**
+   * @param {!NetworkRecords} networkRecords
+   * @return {!Array<{url: string, isBase64DataUri: boolean, mimeType: string, resourceSize: number}>}
+   */
+  static filterUnoptimizedResponses(networkRecords) {
+    const unoptimizedResponses = [];
+
+    networkRecords.forEach(record => {
+      const isTextBasedResource = record.resourceType() && record.resourceType().isTextType();
+      if (!isTextBasedResource || !record.resourceSize) {
+        return;
+      }
+
+      const isContentEncoded = record.responseHeaders.find(header =>
+        header.name.toLowerCase() === 'content-encoding' &&
+        compressionTypes.includes(header.value)
+      );
+
+      if (!isContentEncoded) {
+        unoptimizedResponses.push({
+          record: record,
+          url: record.url,
+          mimeType: record.mimeType,
+          resourceSize: record.resourceSize,
+        });
+      }
+    });
+
+    return unoptimizedResponses;
+  }
+
+  afterPass(options, traceData) {
+    const networkRecords = traceData.networkRecords;
+    const textRecords = ResponseCompression.filterUnoptimizedResponses(networkRecords);
+
+    return Promise.all(textRecords.map(record => {
+      return record.record.requestContent().then(content => {
+        // if we don't have any content gzipSize is set to 0
+        if (!content) {
+          record.gzipSize = 0;
+
+          return record;
+        }
+
+        return new Promise((resolve, reject) => {
+          return gzip(content, (err, res) => {
+            if (err) {
+              return reject(err);
+            }
+
+            // get gzip size
+            record.gzipSize = Buffer.byteLength(res, 'utf8');
+
+            resolve(record);
+          });
+        });
+      });
+    }));
+  }
+}
+
+module.exports = ResponseCompression;
+
+}).call(this,require("buffer").Buffer)
+},{"../gatherer":13,"buffer":205,"zlib":202}],"./gatherers/dobetterweb/tags-blocking-first-paint":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ /**
+  * @fileoverview
+  *   Identifies stylesheets, HTML Imports, and scripts that potentially block
+  *   the first paint of the page by running several scripts in the page context.
+  *   Candidate blocking tags are collected by querying for all script tags in
+  *   the head of the page and all link tags that are either matching media
+  *   stylesheets or non-async HTML imports. These are then compared to the
+  *   network requests to ensure they were initiated by the parser and not
+  *   injected with script. To avoid false positives from strategies like
+  *   (http://filamentgroup.github.io/loadCSS/test/preload.html), a separate
+  *   script is run to flag all links that at one point were rel=preload.
+  */
 
 'use strict';
 
-const Gatherer=require('../gatherer');
-const URL=require('../../../lib/url-shim');
+const Gatherer = require('../gatherer');
 
+/* global document,window */
 
+/* istanbul ignore next */
+function saveAsyncLinks() {
+  function checkForLinks() {
+    document.querySelectorAll('link').forEach(link => {
+      if (link.rel === 'preload' || link.disabled) {
+        window.__asyncLinks[link.href] = true;
+      }
+    });
 
+    setTimeout(checkForLinks, 50);
+  }
 
-
-
-
-
-
-function getOptimizedNumBytes(url){
-return new Promise(function(resolve,reject){
-const img=new Image();
-const canvas=document.createElement('canvas');
-const context=canvas.getContext('2d');
-
-function getTypeStats(type,quality){
-const dataURI=canvas.toDataURL(type,quality);
-const base64=dataURI.slice(dataURI.indexOf(',')+1);
-return{base64:base64.length,binary:atob(base64).length};
+  window.__asyncLinks = window.__asyncLinks || {};
+  setTimeout(checkForLinks, 50);
+  checkForLinks();
 }
 
-img.addEventListener('error',reject);
-img.addEventListener('load',()=>{
-try{
-canvas.height=img.height;
-canvas.width=img.width;
-context.drawImage(img,0,0);
+/* istanbul ignore next */
+function collectTagsThatBlockFirstPaint() {
+  return new Promise((resolve, reject) => {
+    try {
+      const tagList = [...document.querySelectorAll('link, head script[src]')]
+        .filter(tag => {
+          if (tag.tagName === 'SCRIPT') {
+            return !tag.hasAttribute('async') &&
+                !tag.hasAttribute('defer') &&
+                !/^data:/.test(tag.src);
+          }
 
-const jpeg=getTypeStats('image/jpeg',0.8);
-const webp=getTypeStats('image/webp',0.8);
-
-resolve({jpeg,webp});
-}catch(err){
-reject(err);
-}
-},false);
-
-img.src=url;
-});
+          // Filter stylesheet/HTML imports that block rendering.
+          // https://www.igvita.com/2012/06/14/debunking-responsive-css-performance-myths/
+          // https://www.w3.org/TR/html-imports/#dfn-import-async-attribute
+          const blockingStylesheet = (tag.rel === 'stylesheet' &&
+              window.matchMedia(tag.media).matches && !tag.disabled);
+          const blockingImport = tag.rel === 'import' && !tag.hasAttribute('async');
+          return blockingStylesheet || blockingImport;
+        })
+        .map(tag => {
+          return {
+            tagName: tag.tagName,
+            url: tag.tagName === 'LINK' ? tag.href : tag.src,
+            src: tag.src,
+            href: tag.href,
+            rel: tag.rel,
+            media: tag.media,
+            disabled: tag.disabled
+          };
+        })
+        .filter(tag => !window.__asyncLinks[tag.url]);
+      resolve(tagList);
+    } catch (e) {
+      const friendly = 'Unable to gather Scripts/Stylesheets/HTML Imports on the page';
+      reject(new Error(`${friendly}: ${e.message}`));
+    }
+  });
 }
 
-class OptimizedImages extends Gatherer{
+function filteredAndIndexedByUrl(networkRecords) {
+  return networkRecords.reduce((prev, record) => {
+    const isParserGenerated = record._initiator.type === 'parser';
+    // A stylesheet only blocks script if it was initiated by the parser
+    // https://html.spec.whatwg.org/multipage/semantics.html#interactions-of-styling-and-scripting
+    const isParserScriptOrStyle = /(css|script)/.test(record._mimeType) && isParserGenerated;
+    const isFailedRequest = record._failed;
+    const isHtml = record._mimeType && record._mimeType.includes('html');
 
+    // Filter stylesheet, javascript, and html import mimetypes.
+    // Include 404 scripts/links generated by the parser because they are likely blocking.
+    if (isHtml || isParserScriptOrStyle || (isFailedRequest && isParserGenerated)) {
+      prev[record._url] = {
+        transferSize: record._transferSize,
+        startTime: record._startTime,
+        endTime: record._endTime
+      };
+    }
 
-
-
-
-static filterImageRequests(pageUrl,networkRecords){
-const seenUrls=new Set();
-return networkRecords.reduce((prev,record)=>{
-if(seenUrls.has(record._url)){
-return prev;
+    return prev;
+  }, {});
 }
 
-seenUrls.add(record._url);
-const isOptimizableImage=/image\/(png|bmp|jpeg)/.test(record._mimeType);
-const isSameOrigin=URL.hostsMatch(pageUrl,record._url);
-const isBase64DataUri=/^data:.{2,40}base64\s*,/.test(record._url);
+class TagsBlockingFirstPaint extends Gatherer {
+  constructor() {
+    super();
+    this._filteredAndIndexedByUrl = filteredAndIndexedByUrl;
+  }
 
-if(isOptimizableImage&&(isSameOrigin||isBase64DataUri)){
-prev.push({
-isBase64DataUri,
-url:record._url,
-mimeType:record._mimeType,
-resourceSize:record._resourceSize});
+  static findBlockingTags(driver, networkRecords) {
+    const scriptSrc = `(${collectTagsThatBlockFirstPaint.toString()}())`;
+    return driver.evaluateAsync(scriptSrc).then(tags => {
+      const requests = filteredAndIndexedByUrl(networkRecords);
 
+      return tags.reduce((prev, tag) => {
+        const request = requests[tag.url];
+        if (request) {
+          prev.push({
+            tag,
+            transferSize: request.transferSize || 0,
+            startTime: request.startTime,
+            endTime: request.endTime,
+          });
+
+          // Prevent duplicates from showing up again
+          requests[tag.url] = null;
+        }
+
+        return prev;
+      }, []);
+    });
+  }
+
+  beforePass(options) {
+    const scriptSrc = `(${saveAsyncLinks.toString()})()`;
+    return options.driver.evaluateScriptOnLoad(scriptSrc);
+  }
+
+  /**
+   * @param {!Object} options
+   * @param {{networkRecords: !Array<!NetworkRecord>}} tracingData
+   * @return {!Array<{tag: string, transferSize: number, startTime: number, endTime: number}>}
+   */
+  afterPass(options, tracingData) {
+    return TagsBlockingFirstPaint
+      .findBlockingTags(options.driver, tracingData.networkRecords);
+  }
 }
 
-return prev;
-},[]);
-}
-
-
-
-
-
-
-calculateImageStats(driver,networkRecord){
-const param=JSON.stringify(networkRecord.url);
-const script=`(${getOptimizedNumBytes.toString()})(${param})`;
-return driver.evaluateAsync(script).then(stats=>{
-const isBase64DataUri=networkRecord.isBase64DataUri;
-const base64Length=networkRecord.url.length-networkRecord.url.indexOf(',')-1;
-return{
-originalSize:isBase64DataUri?base64Length:networkRecord.resourceSize,
-jpegSize:isBase64DataUri?stats.jpeg.base64:stats.jpeg.binary,
-webpSize:isBase64DataUri?stats.webp.base64:stats.webp.binary};
-
-});
-}
-
-
-
-
-
-
-afterPass(options,traceData){
-const networkRecords=traceData.networkRecords;
-const imageRecords=OptimizedImages.filterImageRequests(options.url,networkRecords);
-
-return Promise.all(imageRecords.map(record=>{
-return this.calculateImageStats(options.driver,record).catch(err=>{
-return{failed:true,err};
-}).then(stats=>{
-return Object.assign(stats,record);
-});
-})).then(results=>{
-const successfulResults=results.filter(result=>!result.failed);
-if(results.length&&!successfulResults.length){
-throw new Error('All image optimizations failed');
-}
-
-return results;
-});
-}}
-
-
-module.exports=OptimizedImages;
-
-},{"../../../lib/url-shim":25,"../gatherer":13}],"./gatherers/dobetterweb/tags-blocking-first-paint":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-'use strict';
-
-const Gatherer=require('../gatherer');
-
-
-
-
-function saveAsyncLinks(){
-function checkForLinks(){
-document.querySelectorAll('link').forEach(link=>{
-if(link.rel==='preload'||link.disabled){
-window.__asyncLinks[link.href]=true;
-}
-});
-}
-
-window.__asyncLinks=window.__asyncLinks||{};
-setInterval(checkForLinks,100);
-checkForLinks();
-}
-
-
-function collectTagsThatBlockFirstPaint(){
-return new Promise((resolve,reject)=>{
-try{
-const tagList=[...document.querySelectorAll('link, head script[src]')].
-filter(tag=>{
-if(tag.tagName==='SCRIPT'){
-return!tag.hasAttribute('async')&&
-!tag.hasAttribute('defer')&&
-!/^data:/.test(tag.src);
-}
-
-
-
-
-const blockingStylesheet=tag.rel==='stylesheet'&&
-window.matchMedia(tag.media).matches&&!tag.disabled;
-const blockingImport=tag.rel==='import'&&!tag.hasAttribute('async');
-return blockingStylesheet||blockingImport;
-}).
-map(tag=>{
-return{
-tagName:tag.tagName,
-url:tag.tagName==='LINK'?tag.href:tag.src,
-src:tag.src,
-href:tag.href,
-rel:tag.rel,
-media:tag.media,
-disabled:tag.disabled};
-
-}).
-filter(tag=>!window.__asyncLinks[tag.url]);
-resolve(tagList);
-}catch(e){
-const friendly='Unable to gather Scripts/Stylesheets/HTML Imports on the page';
-reject(new Error(`${friendly}: ${e.message}`));
-}
-});
-}
-
-function filteredAndIndexedByUrl(networkRecords){
-return networkRecords.reduce((prev,record)=>{
-const isParserGenerated=record._initiator.type==='parser';
-
-
-const isParserScriptOrStyle=/(css|script)/.test(record._mimeType)&&isParserGenerated;
-const isFailedRequest=record._failed;
-const isHtml=record._mimeType&&record._mimeType.includes('html');
-
-
-
-if(isHtml||isParserScriptOrStyle||isFailedRequest&&isParserGenerated){
-prev[record._url]={
-transferSize:record._transferSize,
-startTime:record._startTime,
-endTime:record._endTime};
-
-}
-
-return prev;
-},{});
-}
-
-class TagsBlockingFirstPaint extends Gatherer{
-constructor(){
-super();
-this._filteredAndIndexedByUrl=filteredAndIndexedByUrl;
-}
-
-static findBlockingTags(driver,networkRecords){
-const scriptSrc=`(${collectTagsThatBlockFirstPaint.toString()}())`;
-return driver.evaluateAsync(scriptSrc).then(tags=>{
-const requests=filteredAndIndexedByUrl(networkRecords);
-
-return tags.reduce((prev,tag)=>{
-const request=requests[tag.url];
-if(request){
-prev.push({
-tag,
-transferSize:request.transferSize||0,
-startTime:request.startTime,
-endTime:request.endTime});
-
-
-
-requests[tag.url]=null;
-}
-
-return prev;
-},[]);
-});
-}
-
-beforePass(options){
-const scriptSrc=`(${saveAsyncLinks.toString()})()`;
-return options.driver.evaluateScriptOnLoad(scriptSrc);
-}
-
-
-
-
-
-
-afterPass(options,tracingData){
-return TagsBlockingFirstPaint.
-findBlockingTags(options.driver,tracingData.networkRecords);
-}}
-
-
-module.exports=TagsBlockingFirstPaint;
+module.exports = TagsBlockingFirstPaint;
 
 },{"../gatherer":13}],"./gatherers/dobetterweb/websql":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const Gatherer=require('../gatherer');
+const Gatherer = require('../gatherer');
 
-const MAX_WAIT_TIMEOUT=500;
+const MAX_WAIT_TIMEOUT = 500;
 
-class WebSQL extends Gatherer{
+class WebSQL extends Gatherer {
 
-listenForDatabaseEvents(driver){
-let timeout;
+  listenForDatabaseEvents(driver) {
+    let timeout;
 
-return new Promise((resolve,reject)=>{
-driver.once('Database.addDatabase',db=>{
-clearTimeout(timeout);
-driver.sendCommand('Database.disable').then(_=>resolve(db),reject);
-});
+    return new Promise((resolve, reject) => {
+      driver.once('Database.addDatabase', db => {
+        clearTimeout(timeout);
+        driver.sendCommand('Database.disable').then(_ => resolve(db), reject);
+      });
 
-driver.sendCommand('Database.enable').catch(reject);
+      driver.sendCommand('Database.enable').catch(reject);
 
+      // Wait for a websql db to be opened. Reject the Promise no dbs were created.
+      // TODO(ericbidelman): this assumes dbs are opened on page load.
+      // load. Figure out a better strategy (code greping, user interaction) later.
+      timeout = setTimeout(function() {
+        resolve(null);
+      }, MAX_WAIT_TIMEOUT);
+    });
+  }
 
-
-
-timeout=setTimeout(function(){
-resolve(null);
-},MAX_WAIT_TIMEOUT);
-});
+  /**
+   * Returns WebSQL database information or null if none was found.
+   * @param {!Object} options
+   * @return {?{id: string, domain: string, name: string, version: string}}
+   */
+  afterPass(options) {
+    return this.listenForDatabaseEvents(options.driver)
+      .then(result => {
+        return result && result.database;
+      });
+  }
 }
 
-
-
-
-
-
-afterPass(options){
-return this.listenForDatabaseEvents(options.driver).
-then(result=>{
-return result&&result.database;
-});
-}}
-
-
-module.exports=WebSQL;
+module.exports = WebSQL;
 
 },{"../gatherer":13}],"./gatherers/html-without-javascript":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-const Gatherer=require('./gatherer');
+const Gatherer = require('./gatherer');
 
+/**
+ * @fileoverview Returns the innerText of the <body> element while JavaScript is
+ * disabled.
+ */
 
+/* global document */
 
-
-
-
-
-
-
-function getBodyText(){
-
-const body=document.querySelector('body');
-return Promise.resolve(body?body.innerText:'');
+/* istanbul ignore next */
+function getBodyText() {
+  // note: we use innerText, not textContent, because textContent includes the content of <script> elements!
+  const body = document.querySelector('body');
+  return Promise.resolve(body ? body.innerText : '');
 }
 
-class HTMLWithoutJavaScript extends Gatherer{
-beforePass(options){
-options.disableJavaScript=true;
+class HTMLWithoutJavaScript extends Gatherer {
+  beforePass(options) {
+    options.disableJavaScript = true;
+  }
+
+  afterPass(options) {
+    // Reset the JS disable.
+    options.disableJavaScript = false;
+
+    return options.driver.evaluateAsync(`(${getBodyText.toString()}())`)
+      .then(result => {
+        if (typeof result !== 'string') {
+          throw new Error('document body innerText returned by protocol was not a string');
+        }
+
+        return {
+          value: result
+        };
+      });
+  }
 }
 
-afterPass(options){
-
-options.disableJavaScript=false;
-
-return options.driver.evaluateAsync(`(${getBodyText.toString()}())`).
-then(result=>{
-if(typeof result!=='string'){
-throw new Error('document body innerText returned by protocol was not a string');
-}
-
-return{
-value:result};
-
-});
-}}
-
-
-module.exports=HTMLWithoutJavaScript;
+module.exports = HTMLWithoutJavaScript;
 
 },{"./gatherer":13}],"./gatherers/http-redirect":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-const Gatherer=require('./gatherer');
+const Gatherer = require('./gatherer');
 
+/**
+ * This gatherer changes the options.url so that its pass loads the http page.
+ * After load it detects if its on a crypographic scheme.
+ * TODO: Instead of abusing a loadPage pass for this test, it could likely just do an XHR instead
+ */
+class HTTPRedirect extends Gatherer {
 
+  constructor() {
+    super();
+    this._preRedirectURL = undefined;
+  }
 
+  beforePass(options) {
+    this._preRedirectURL = options.url;
+    options.url = this._preRedirectURL.replace(/^https/, 'http');
+  }
 
+  afterPass(options) {
+    // Reset the options.
+    options.url = this._preRedirectURL;
 
+    // Allow override for faster testing.
+    const timeout = options._testTimeout || 10000;
 
-class HTTPRedirect extends Gatherer{
+    const securityPromise = options.driver.getSecurityState()
+      .then(state => {
+        return {
+          value: state.schemeIsCryptographic
+        };
+      });
 
-constructor(){
-super();
-this._preRedirectURL=undefined;
+    let noSecurityChangesTimeout;
+    const timeoutPromise = new Promise((resolve, reject) => {
+      // Set up a timeout for ten seconds in case we don't get any
+      // security events at all. If that happens, bail.
+      noSecurityChangesTimeout = setTimeout(_ => {
+        reject(new Error('Timed out waiting for HTTP redirection.'));
+      }, timeout);
+    });
+
+    return Promise.race([
+      securityPromise,
+      timeoutPromise
+    ]).then(result => {
+      // Clear timeout. No effect if it won, no need to wait if it lost.
+      clearTimeout(noSecurityChangesTimeout);
+      return result;
+    }).catch(err => {
+      clearTimeout(noSecurityChangesTimeout);
+      throw err;
+    });
+  }
 }
 
-beforePass(options){
-this._preRedirectURL=options.url;
-options.url=this._preRedirectURL.replace(/^https/,'http');
-}
-
-afterPass(options){
-
-options.url=this._preRedirectURL;
-
-
-const timeout=options._testTimeout||10000;
-
-const securityPromise=options.driver.getSecurityState().
-then(state=>{
-return{
-value:state.schemeIsCryptographic};
-
-});
-
-let noSecurityChangesTimeout;
-const timeoutPromise=new Promise((resolve,reject)=>{
-
-
-noSecurityChangesTimeout=setTimeout(_=>{
-reject(new Error('Timed out waiting for HTTP redirection.'));
-},timeout);
-});
-
-return Promise.race([
-securityPromise,
-timeoutPromise]).
-then(result=>{
-
-clearTimeout(noSecurityChangesTimeout);
-return result;
-}).catch(err=>{
-clearTimeout(noSecurityChangesTimeout);
-throw err;
-});
-}}
-
-
-module.exports=HTTPRedirect;
-
-},{"./gatherer":13}],"./gatherers/https":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-'use strict';
-
-const Gatherer=require('./gatherer');
-
-
-
-
-
-
-class HTTPS extends Gatherer{
-
-constructor(){
-super();
-this._noSecurityChangesTimeout=undefined;
-}
-
-afterPass(options){
-
-const timeout=options._testTimeout||10000;
-
-const securityPromise=options.driver.getSecurityState().
-then(state=>{
-return{
-value:state.schemeIsCryptographic};
-
-});
-
-let noSecurityChangesTimeout;
-const timeoutPromise=new Promise((resolve,reject)=>{
-
-
-noSecurityChangesTimeout=setTimeout(_=>{
-reject(new Error('Timed out waiting for page security state.'));
-},timeout);
-});
-
-return Promise.race([
-securityPromise,
-timeoutPromise]).
-then(result=>{
-
-clearTimeout(noSecurityChangesTimeout);
-return result;
-}).catch(err=>{
-clearTimeout(noSecurityChangesTimeout);
-throw err;
-});
-}}
-
-
-module.exports=HTTPS;
+module.exports = HTTPRedirect;
 
 },{"./gatherer":13}],"./gatherers/image-usage":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ /**
+  * @fileoverview Gathers all images used on the page with their src, size,
+  *   and attribute information. Executes script in the context of the page.
+  */
 'use strict';
 
-const Gatherer=require('./gatherer');
+const Gatherer = require('./gatherer');
 
+/* global window, document, Image */
 
+/* istanbul ignore next */
+function collectImageElementInfo() {
+  function getClientRect(element) {
+    const clientRect = element.getBoundingClientRect();
+    return {
+      // manually copy the properties because ClientRect does not JSONify
+      top: clientRect.top,
+      bottom: clientRect.bottom,
+      left: clientRect.left,
+      right: clientRect.right,
+    };
+  }
 
+  const htmlImages = [...document.querySelectorAll('img')].map(element => {
+    return {
+      // currentSrc used over src to get the url as determined by the browser
+      // after taking into account srcset/media/sizes/etc.
+      src: element.currentSrc,
+      clientWidth: element.clientWidth,
+      clientHeight: element.clientHeight,
+      clientRect: getClientRect(element),
+      naturalWidth: element.naturalWidth,
+      naturalHeight: element.naturalHeight,
+      isCss: false,
+      isPicture: element.parentElement.tagName === 'PICTURE',
+    };
+  });
 
-function collectImageElementInfo(){
-return[...document.querySelectorAll('img')].map(element=>{
-return{
+  // Chrome normalizes background image style from getComputedStyle to be an absolute URL in quotes.
+  // Only match basic background-image: url("http://host/image.jpeg") declarations
+  const CSS_URL_REGEX = /^url\("([^"]+)"\)$/;
+  // Only find images that aren't specifically scaled
+  const CSS_SIZE_REGEX = /(auto|contain|cover)/;
+  const cssImages = [...document.querySelectorAll('html /deep/ *')].reduce((images, element) => {
+    const style = window.getComputedStyle(element);
+    if (!CSS_URL_REGEX.test(style.backgroundImage) ||
+        !CSS_SIZE_REGEX.test(style.backgroundSize)) {
+      return images;
+    }
 
+    const imageMatch = style.backgroundImage.match(CSS_URL_REGEX);
+    const url = imageMatch[1];
 
-src:element.currentSrc,
-clientWidth:element.clientWidth,
-clientHeight:element.clientHeight,
-naturalWidth:element.naturalWidth,
-naturalHeight:element.naturalHeight,
-isPicture:element.parentElement.tagName==='PICTURE'};
+    // Heuristic to filter out sprite sheets
+    const differentImages = images.filter(image => image.src !== url);
+    if (images.length - differentImages.length > 2) {
+      return differentImages;
+    }
 
-});
+    images.push({
+      src: url,
+      clientWidth: element.clientWidth,
+      clientHeight: element.clientHeight,
+      clientRect: getClientRect(element),
+      // CSS Images do not expose natural size, we'll determine the size later
+      naturalWidth: Number.MAX_VALUE,
+      naturalHeight: Number.MAX_VALUE,
+      isCss: true,
+      isPicture: false,
+    });
+
+    return images;
+  }, []);
+
+  return htmlImages.concat(cssImages);
 }
 
+/* istanbul ignore next */
+function determineNaturalSize(url) {
+  return new Promise((resolve, reject) => {
+    const img = new Image();
+    img.addEventListener('error', reject);
+    img.addEventListener('load', () => {
+      resolve({
+        naturalWidth: img.naturalWidth,
+        naturalHeight: img.naturalHeight
+      });
+    });
 
-function determineNaturalSize(url){
-return new Promise((resolve,reject)=>{
-const img=new Image();
-img.addEventListener('error',reject);
-img.addEventListener('load',()=>{
-resolve({
-naturalWidth:img.naturalWidth,
-naturalHeight:img.naturalHeight});
-
-});
-
-img.src=url;
-});
+    img.src = url;
+  });
 }
 
-class ImageUsage extends Gatherer{
+class ImageUsage extends Gatherer {
 
+  /**
+   * @param {{src: string}} element
+   * @return {!Promise<!Object>}
+   */
+  fetchElementWithSizeInformation(element) {
+    const url = JSON.stringify(element.src);
+    return this.driver.evaluateAsync(`(${determineNaturalSize.toString()})(${url})`)
+      .then(size => {
+        return Object.assign(element, size);
+      });
+  }
 
+  afterPass(options, traceData) {
+    const driver = this.driver = options.driver;
+    const indexedNetworkRecords = traceData.networkRecords.reduce((map, record) => {
+      if (/^image/.test(record._mimeType)) {
+        map[record._url] = {
+          url: record.url,
+          resourceSize: record.resourceSize,
+          startTime: record.startTime,
+          endTime: record.endTime,
+          responseReceivedTime: record.responseReceivedTime,
+          mimeType: record._mimeType
+        };
+      }
 
+      return map;
+    }, {});
 
+    return driver.evaluateAsync(`(${collectImageElementInfo.toString()})()`)
+      .then(elements => {
+        return elements.reduce((promise, element) => {
+          return promise.then(collector => {
+            // link up the image with its network record
+            element.networkRecord = indexedNetworkRecords[element.src];
 
-fetchElementWithSizeInformation(element){
-const url=JSON.stringify(element.src);
-return this.driver.evaluateAsync(`(${determineNaturalSize.toString()})(${url})`).
-then(size=>{
-return Object.assign(element,size);
-});
+            // Images within `picture` behave strangely and natural size information isn't accurate,
+            // CSS images have no natural size information at all.
+            // Try to get the actual size if we can.
+            const elementPromise = (element.isPicture || element.isCss) && element.networkRecord ?
+                this.fetchElementWithSizeInformation(element) :
+                Promise.resolve(element);
+
+            return elementPromise.then(element => {
+              collector.push(element);
+              return collector;
+            });
+          });
+        }, Promise.resolve([]));
+      });
+  }
 }
 
-afterPass(options,traceData){
-const driver=this.driver=options.driver;
-const indexedNetworkRecords=traceData.networkRecords.reduce((map,record)=>{
-if(/^image/.test(record._mimeType)){
-map[record._url]={
-url:record.url,
-resourceSize:record.resourceSize,
-startTime:record.startTime,
-endTime:record.endTime,
-responseReceivedTime:record.responseReceivedTime,
-mimeType:record._mimeType};
-
-}
-
-return map;
-},{});
-
-return driver.evaluateAsync(`(${collectImageElementInfo.toString()})()`).
-then(elements=>{
-return elements.reduce((promise,element)=>{
-return promise.then(collector=>{
-
-element.networkRecord=indexedNetworkRecords[element.src];
-
-
-
-const elementPromise=element.isPicture&&element.networkRecord?
-this.fetchElementWithSizeInformation(element):
-Promise.resolve(element);
-
-return elementPromise.then(element=>{
-collector.push(element);
-return collector;
-});
-});
-},Promise.resolve([]));
-});
-}}
-
-
-module.exports=ImageUsage;
+module.exports = ImageUsage;
 
 },{"./gatherer":13}],"./gatherers/manifest":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-const Gatherer=require('./gatherer');
-const manifestParser=require('../../lib/manifest-parser');
+const Gatherer = require('./gatherer');
+const manifestParser = require('../../lib/manifest-parser');
 
+/**
+ * Uses the debugger protocol to fetch the manifest from within the context of
+ * the target page, reusing any credentials, emulation, etc, already established
+ * there. The artifact produced is the fetched string, if any, passed through
+ * the manifest parser.
+ */
+class Manifest extends Gatherer {
+  /**
+   * Returns the parsed manifest or null if the page had no manifest. If the manifest
+   * was unparseable as JSON, manifest.value will be undefined and manifest.debugString
+   * will have the reason. See manifest-parser.js for more information.
+   * @param {!Object} options
+   * @return {!Promise<?Manifest>}
+   */
+  afterPass(options) {
+    return options.driver.sendCommand('Page.getAppManifest')
+      .then(response => {
+        // We're not reading `response.errors` however it may contain critical and noncritical
+        // errors from Blink's manifest parser:
+        //   https://chromedevtools.github.io/debugger-protocol-viewer/tot/Page/#type-AppManifestError
+        if (!response.data) {
+          if (response.url) {
+            throw new Error(`Unable to retrieve manifest at ${response.url}`);
+          }
 
+          // If both the data and the url are empty strings, the page had no manifest.
+          return null;
+        }
 
-
-
-
-
-class Manifest extends Gatherer{
-
-
-
-
-
-
-
-afterPass(options){
-return options.driver.sendCommand('Page.getAppManifest').
-then(response=>{
-
-
-
-if(!response.data){
-if(response.url){
-throw new Error(`Unable to retrieve manifest at ${response.url}`);
+        return manifestParser(response.data, response.url, options.url);
+      });
+  }
 }
 
-
-return null;
-}
-
-return manifestParser(response.data,response.url,options.url);
-});
-}}
-
-
-module.exports=Manifest;
+module.exports = Manifest;
 
 },{"../../lib/manifest-parser":20,"./gatherer":13}],"./gatherers/offline":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-const Gatherer=require('./gatherer');
-const URL=require('../../lib/url-shim');
+const Gatherer = require('./gatherer');
+const URL = require('../../lib/url-shim');
 
-class Offline extends Gatherer{
-beforePass(options){
-return options.driver.goOffline();
+class Offline extends Gatherer {
+  beforePass(options) {
+    return options.driver.goOffline();
+  }
+
+  afterPass(options, tracingData) {
+    const navigationRecord = tracingData.networkRecords.filter(record => {
+      return URL.equalWithExcludedFragments(record._url, options.url) &&
+        record._fetchedViaServiceWorker;
+    }).pop(); // Take the last record that matches.
+
+    return options.driver.goOnline(options).then(_ => {
+      return navigationRecord ? navigationRecord.statusCode : -1;
+    });
+  }
 }
 
-afterPass(options,tracingData){
-const navigationRecord=tracingData.networkRecords.filter(record=>{
-return URL.equalWithExcludedFragments(record._url,options.url)&&
-record._fetchedViaServiceWorker;
-}).pop();
-
-return options.driver.goOnline(options).then(_=>{
-return navigationRecord?navigationRecord.statusCode:-1;
-});
-}}
-
-
-module.exports=Offline;
+module.exports = Offline;
 
 },{"../../lib/url-shim":25,"./gatherer":13}],"./gatherers/service-worker":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-const Gatherer=require('./gatherer');
+const Gatherer = require('./gatherer');
 
-class ServiceWorker extends Gatherer{
-beforePass(options){
-const driver=options.driver;
-return driver.
-getServiceWorkerVersions().
-then(data=>{
-return{
-versions:data.versions};
+class ServiceWorker extends Gatherer {
+  beforePass(options) {
+    const driver = options.driver;
+    return driver
+      .getServiceWorkerVersions()
+      .then(data => {
+        return {
+          versions: data.versions
+        };
+      });
+  }
+}
 
-});
-}}
-
-
-module.exports=ServiceWorker;
+module.exports = ServiceWorker;
 
 },{"./gatherer":13}],"./gatherers/styles":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @fileoverview Gathers the active style and stylesheets used on a page.
+ * "Active" means that if the stylesheet is removed at a later time
+ * (before endStylesCollect is called), this gatherer will not include it.
+ */
 
 'use strict';
 
-const WebInspector=require('../../lib/web-inspector');
-const Gatherer=require('./gatherer');
-const log=require('../../lib/log.js');
+const WebInspector = require('../../lib/web-inspector');
+const Gatherer = require('./gatherer');
+const log = require('../../lib/log.js');
 
+/**
+ * @param {!gonzales.AST} parseTree
+ * @return {!Array}
+ */
+function getCSSPropsInStyleSheet(parseTree) {
+  const results = [];
 
+  parseTree.traverseByType('declaration', function(node, index, parent) {
+    if (parent.type === 'arguments') {
+      // We don't want to return data URI declarations of the form
+      // background-image: -webkit-image-set(url('data:image/png,...') 1x)
+      return;
+    }
 
+    const keyVal = node.toString().split(':').map(item => item.trim());
+    results.push({
+      property: {name: keyVal[0], val: keyVal[1]},
+      declarationRange: node.declarationRange,
+      selector: parent.selectors.toString()
+    });
+  });
 
-
-function getCSSPropsInStyleSheet(parseTree){
-const results=[];
-
-parseTree.traverseByType('declaration',function(node,index,parent){
-if(parent.type==='arguments'){
-
-
-return;
+  return results;
 }
 
-const keyVal=node.toString().split(':').map(item=>item.trim());
-results.push({
-property:{name:keyVal[0],val:keyVal[1]},
-declarationRange:node.declarationRange,
-selector:parent.selectors.toString()});
+class Styles extends Gatherer {
 
-});
+  constructor() {
+    super();
+    this._activeStyleSheetIds = [];
+    this._activeStyleHeaders = {};
+    this._onStyleSheetAdded = this.onStyleSheetAdded.bind(this);
+    this._onStyleSheetRemoved = this.onStyleSheetRemoved.bind(this);
+  }
 
-return results;
+  onStyleSheetAdded(styleHeader) {
+    // Exclude stylesheets "injected" by extensions or ones that were added by
+    // users using the "inspector".
+    if (styleHeader.header.origin !== 'regular') {
+      return;
+    }
+
+    this._activeStyleHeaders[styleHeader.header.styleSheetId] = styleHeader;
+    this._activeStyleSheetIds.push(styleHeader.header.styleSheetId);
+  }
+
+  onStyleSheetRemoved(styleHeader) {
+    delete this._activeStyleHeaders[styleHeader.styleSheetId];
+
+    const idx = this._activeStyleSheetIds.indexOf(styleHeader.styleSheetId);
+    if (idx !== -1) {
+      this._activeStyleSheetIds.splice(idx, 1);
+    }
+  }
+
+  beginStylesCollect(driver) {
+    driver.on('CSS.styleSheetAdded', this._onStyleSheetAdded);
+    driver.on('CSS.styleSheetRemoved', this._onStyleSheetRemoved);
+    return driver.sendCommand('DOM.enable')
+      .then(_ => driver.sendCommand('CSS.enable'));
+  }
+
+  endStylesCollect(driver) {
+    return new Promise((resolve, reject) => {
+      if (!this._activeStyleSheetIds.length) {
+        resolve([]);
+        return;
+      }
+
+      const parser = new WebInspector.SCSSParser();
+
+      // Get text content of each style.
+      const contentPromises = this._activeStyleSheetIds.map(sheetId => {
+        return driver.sendCommand('CSS.getStyleSheetText', {
+          styleSheetId: sheetId
+        }).then(content => {
+          const styleHeader = this._activeStyleHeaders[sheetId];
+          styleHeader.content = content.text;
+
+          const parsedContent = parser.parse(styleHeader.content);
+          if (parsedContent.error) {
+            log.warn('Styles Gatherer', `Could not parse content: ${parsedContent.error}`);
+            styleHeader.parsedContent = [];
+          } else {
+            styleHeader.parsedContent = getCSSPropsInStyleSheet(parsedContent);
+          }
+
+          return styleHeader;
+        });
+      });
+
+      Promise.all(contentPromises).then(styleHeaders => {
+        driver.off('CSS.styleSheetAdded', this._onStyleSheetAdded);
+        driver.off('CSS.styleSheetRemoved', this._onStyleSheetRemoved);
+
+        return driver.sendCommand('CSS.disable')
+          .then(_ => driver.sendCommand('DOM.disable'))
+          .then(_ => resolve(styleHeaders));
+      }).catch(err => reject(err));
+    });
+  }
+
+  beforePass(options) {
+    return this.beginStylesCollect(options.driver);
+  }
+
+  afterPass(options) {
+    return this.endStylesCollect(options.driver)
+      .then(stylesheets => {
+        // Generally want unique stylesheets. Mark those with the same text content.
+        // An example where stylesheets are the same is if the user includes a
+        // stylesheet more than once (these have unique stylesheet ids according to
+        // the DevTools protocol). Another example is many instances of a shadow
+        // root that share the same <style> tag.
+        const map = new Map(stylesheets.map(s => [s.content, s]));
+        return stylesheets.map(stylesheet => {
+          const idInMap = map.get(stylesheet.content).header.styleSheetId;
+          stylesheet.isDuplicate = idInMap !== stylesheet.header.styleSheetId;
+          return stylesheet;
+        });
+      });
+  }
 }
 
-class Styles extends Gatherer{
-
-constructor(){
-super();
-this._activeStyleSheetIds=[];
-this._activeStyleHeaders={};
-this._onStyleSheetAdded=this.onStyleSheetAdded.bind(this);
-this._onStyleSheetRemoved=this.onStyleSheetRemoved.bind(this);
-}
-
-onStyleSheetAdded(styleHeader){
-
-
-if(styleHeader.header.origin!=='regular'){
-return;
-}
-
-this._activeStyleHeaders[styleHeader.header.styleSheetId]=styleHeader;
-this._activeStyleSheetIds.push(styleHeader.header.styleSheetId);
-}
-
-onStyleSheetRemoved(styleHeader){
-delete this._activeStyleHeaders[styleHeader.styleSheetId];
-
-const idx=this._activeStyleSheetIds.indexOf(styleHeader.styleSheetId);
-if(idx!==-1){
-this._activeStyleSheetIds.splice(idx,1);
-}
-}
-
-beginStylesCollect(driver){
-driver.on('CSS.styleSheetAdded',this._onStyleSheetAdded);
-driver.on('CSS.styleSheetRemoved',this._onStyleSheetRemoved);
-return driver.sendCommand('DOM.enable').
-then(_=>driver.sendCommand('CSS.enable'));
-}
-
-endStylesCollect(driver){
-return new Promise((resolve,reject)=>{
-if(!this._activeStyleSheetIds.length){
-resolve([]);
-return;
-}
-
-const parser=new WebInspector.SCSSParser();
-
-
-const contentPromises=this._activeStyleSheetIds.map(sheetId=>{
-return driver.sendCommand('CSS.getStyleSheetText',{
-styleSheetId:sheetId}).
-then(content=>{
-const styleHeader=this._activeStyleHeaders[sheetId];
-styleHeader.content=content.text;
-
-const parsedContent=parser.parse(styleHeader.content);
-if(parsedContent.error){
-log.warn('Styles Gatherer',`Could not parse content: ${parsedContent.error}`);
-styleHeader.parsedContent=[];
-}else{
-styleHeader.parsedContent=getCSSPropsInStyleSheet(parsedContent);
-}
-
-return styleHeader;
-});
-});
-
-Promise.all(contentPromises).then(styleHeaders=>{
-driver.off('CSS.styleSheetAdded',this._onStyleSheetAdded);
-driver.off('CSS.styleSheetRemoved',this._onStyleSheetRemoved);
-
-return driver.sendCommand('CSS.disable').
-then(_=>driver.sendCommand('DOM.disable')).
-then(_=>resolve(styleHeaders));
-}).catch(err=>reject(err));
-});
-}
-
-beforePass(options){
-return this.beginStylesCollect(options.driver);
-}
-
-afterPass(options){
-return this.endStylesCollect(options.driver).
-then(stylesheets=>{
-
-
-
-
-
-const map=new Map(stylesheets.map(s=>[s.content,s]));
-return stylesheets.map(stylesheet=>{
-const idInMap=map.get(stylesheet.content).header.styleSheetId;
-stylesheet.isDuplicate=idInMap!==stylesheet.header.styleSheetId;
-return stylesheet;
-});
-});
-}}
-
-
-module.exports=Styles;
+module.exports = Styles;
 
 },{"../../lib/log.js":19,"../../lib/web-inspector":26,"./gatherer":13}],"./gatherers/theme-color":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-const Gatherer=require('./gatherer');
+const Gatherer = require('./gatherer');
 
-class ThemeColor extends Gatherer{
+class ThemeColor extends Gatherer {
 
+  /**
+   * @param {{driver: !Object}} options
+   * @return {!Promise<?string>} The value of the theme-color meta's content attribute, or null
+   */
+  afterPass(options) {
+    const driver = options.driver;
 
+    return driver.querySelector('head meta[name="theme-color"]')
+      .then(node => node && node.getAttribute('content'));
+  }
+}
 
-
-
-afterPass(options){
-const driver=options.driver;
-
-return driver.querySelector('head meta[name="theme-color"]').
-then(node=>node&&node.getAttribute('content'));
-}}
-
-
-module.exports=ThemeColor;
+module.exports = ThemeColor;
 
 },{"./gatherer":13}],"./gatherers/url":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-const Gatherer=require('./gatherer');
+const Gatherer = require('./gatherer');
 
-class URL extends Gatherer{
+class URL extends Gatherer {
 
-afterPass(options){
+  afterPass(options) {
+    // Used currently by cache-start-url audit, which wants to know if the start_url
+    // in the manifest is stored in the cache.
+    // Instead of the originally inputted URL (options.initialUrl), we want the resolved
+    // post-redirect URL (which is here at options.url)
+    return {
+      initialUrl: options.initialUrl,
+      finalUrl: options.url
+    };
+  }
+}
 
+module.exports = URL;
 
+},{"./gatherer":13}],"./gatherers/viewport-dimensions":[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
 
+const Gatherer = require('./gatherer');
 
-return{
-initialUrl:options.initialUrl,
-finalUrl:options.url};
+/* global window */
 
-}}
+/* istanbul ignore next */
+function getViewportDimensions() {
+  // window.innerWidth to get the scrollable size of the window (irrespective of zoom)
+  // window.outerWidth to get the size of the visible area
+  // window.devicePixelRatio to get ratio of logical pixels to physical pixels
+  return Promise.resolve({
+    innerWidth: window.innerWidth,
+    innerHeight: window.innerHeight,
+    outerWidth: window.outerWidth,
+    outerHeight: window.outerHeight,
+    devicePixelRatio: window.devicePixelRatio,
+  });
+}
 
+class ViewportDimensions extends Gatherer {
 
-module.exports=URL;
+  /**
+   * @param {!Object} options
+   * @return {!Promise<{innerWidth: number, outerWidth: number, devicePixelRatio: number}>}
+   */
+  afterPass(options) {
+    const driver = options.driver;
+
+    return driver.evaluateAsync(`(${getViewportDimensions.toString()}())`)
+
+    .then(dimensions => {
+      const allNumeric = Object.keys(dimensions).every(key => Number.isFinite(dimensions[key]));
+      if (!allNumeric) {
+        const results = JSON.stringify(dimensions);
+        throw new Error(`ViewportDimensions results were not numeric: ${results}`);
+      }
+
+      return dimensions;
+    });
+  }
+}
+
+module.exports = ViewportDimensions;
 
 },{"./gatherer":13}],"./gatherers/viewport":[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-const Gatherer=require('./gatherer');
+const Gatherer = require('./gatherer');
 
-class Viewport extends Gatherer{
+class Viewport extends Gatherer {
 
+  /**
+   * @param {{driver: !Object}} options Run options
+   * @return {!Promise<?string>} The value of the viewport meta's content attribute, or null
+   */
+  afterPass(options) {
+    const driver = options.driver;
 
+    return driver.querySelector('head meta[name="viewport"]')
+      .then(node => node && node.getAttribute('content'));
+  }
+}
 
-
-
-afterPass(options){
-const driver=options.driver;
-
-return driver.querySelector('head meta[name="viewport"]').
-then(node=>node&&node.getAttribute('content'));
-}}
-
-
-module.exports=Viewport;
+module.exports = Viewport;
 
 },{"./gatherer":13}],1:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-class Aggregate{
+/**
+ * @fileoverview Base class for all aXe audits. Provides a consistent way to
+ * generate audit results using aXe rule names.
+ */
 
+const Audit = require('../audit');
+const Formatter = require('../../report/formatter');
 
+class AxeAudit extends Audit {
+  /**
+   * @param {!Artifacts} artifacts Accessibility gatherer artifacts. Note that AxeAudit
+   * expects the meta name for the class to match the rule id from aXe.
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    const violations = artifacts.Accessibility.violations;
+    const rule = violations.find(result => result.id === this.meta.name);
 
-
-
-
-
-static _filterResultsByAuditNames(results,expected){
-const expectedNames=Object.keys(expected);
-return results.filter(r=>expectedNames.includes(r.name));
+    return {
+      rawValue: typeof rule === 'undefined',
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.ACCESSIBILITY,
+        value: rule
+      }
+    };
+  }
 }
 
+module.exports = AxeAudit;
 
+},{"../../report/formatter":27,"../audit":2}],2:[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
 
+const DEFAULT_PASS = 'defaultPass';
 
+class Audit {
+  /**
+   * @return {!string}
+   */
+  static get DEFAULT_PASS() {
+    return DEFAULT_PASS;
+  }
 
+  /**
+   * @return {{NUMERIC: string, BINARY: string}}
+   */
+  static get SCORING_MODES() {
+    return {
+      NUMERIC: 'numeric',
+      BINARY: 'binary',
+    };
+  }
 
-static _getTotalWeight(expected){
-const expectedNames=Object.keys(expected);
-const totalWeight=expectedNames.reduce((last,e)=>last+(expected[e].weight||0),0);
-return totalWeight;
+  /**
+   * @throws {Error}
+   */
+  static get meta() {
+    throw new Error('Audit meta information must be overridden.');
+  }
+
+  /**
+   * @param {!Audit} audit
+   * @param {string} debugString
+   * @return {!AuditFullResult}
+   */
+  static generateErrorAuditResult(audit, debugString) {
+    return Audit.generateAuditResult(audit, {
+      rawValue: null,
+      error: true,
+      debugString
+    });
+  }
+
+  /**
+   * @param {!Audit} audit
+   * @param {!AuditResult} result
+   * @return {!AuditFullResult}
+   */
+  static generateAuditResult(audit, result) {
+    if (typeof result.rawValue === 'undefined') {
+      throw new Error('generateAuditResult requires a rawValue');
+    }
+
+    const score = typeof result.score === 'undefined' ? result.rawValue : result.score;
+    let displayValue = result.displayValue;
+    if (typeof displayValue === 'undefined') {
+      displayValue = result.rawValue ? result.rawValue : '';
+    }
+
+    // The same value or true should be '' it doesn't add value to the report
+    if (displayValue === score) {
+      displayValue = '';
+    }
+
+    return {
+      score,
+      displayValue: `${displayValue}`,
+      rawValue: result.rawValue,
+      error: result.error,
+      debugString: result.debugString,
+      optimalValue: result.optimalValue,
+      extendedInfo: result.extendedInfo,
+      scoringMode: audit.meta.scoringMode || Audit.SCORING_MODES.BINARY,
+      informative: audit.meta.informative,
+      name: audit.meta.name,
+      category: audit.meta.category,
+      description: audit.meta.description,
+      helpText: audit.meta.helpText,
+      details: result.details,
+    };
+  }
 }
 
+module.exports = Audit;
 
+},{}],3:[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
 
+const Audit = require('../audit');
+const Formatter = require('../../report/formatter');
 
+const KB_IN_BYTES = 1024;
+const WASTEFUL_THRESHOLD_IN_BYTES = 20 * KB_IN_BYTES;
 
+/**
+ * @overview Used as the base for all byte efficiency audits. Computes total bytes
+ *    and estimated time saved. Subclass and override `audit_` to return results.
+ */
+class UnusedBytes extends Audit {
+  /**
+   * @param {number} bytes
+   * @return {string}
+   */
+  static bytesToKbString(bytes) {
+    return Math.round(bytes / KB_IN_BYTES).toLocaleString() + ' KB';
+  }
 
-static _remapResultsByName(results){
-const remapped={};
-results.forEach(r=>{
-if(remapped[r.name]){
-throw new Error(`Cannot remap: ${r.name} already exists`);
+  /**
+   * @param {number} bytes
+   * @param {number} percent
+   * @return {string}
+   */
+  static toSavingsString(bytes = 0, percent = 0) {
+    const kbDisplay = this.bytesToKbString(bytes);
+    const percentDisplay = Math.round(percent).toLocaleString() + '%';
+    return `${kbDisplay} _${percentDisplay}_`;
+  }
+
+  /**
+   * @param {number} bytes
+   * @param {number} networkThroughput measured in bytes/second
+   * @return {string}
+   */
+  static bytesToMsString(bytes, networkThroughput) {
+    return (Math.round(bytes / networkThroughput * 100) * 10).toLocaleString() + 'ms';
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!Promise<!AuditResult>}
+   */
+  static audit(artifacts) {
+    const networkRecords = artifacts.networkRecords[Audit.DEFAULT_PASS];
+    return artifacts.requestNetworkThroughput(networkRecords).then(networkThroughput => {
+      return Promise.resolve(this.audit_(artifacts, networkRecords)).then(result => {
+        return this.createAuditResult(result, networkThroughput);
+      });
+    });
+  }
+
+  /**
+   * @param {!{debugString: string=, passes: boolean=, tableHeadings: !Object,
+   *    results: !Array<!Object>}} result
+   * @param {number} networkThroughput
+   * @return {!AuditResult}
+   */
+  static createAuditResult(result, networkThroughput) {
+    const debugString = result.debugString;
+    const results = result.results
+        .map(item => {
+          item.wastedKb = this.bytesToKbString(item.wastedBytes);
+          item.wastedMs = this.bytesToMsString(item.wastedBytes, networkThroughput);
+          item.totalKb = this.bytesToKbString(item.totalBytes);
+          item.totalMs = this.bytesToMsString(item.totalBytes, networkThroughput);
+          item.potentialSavings = this.toSavingsString(item.wastedBytes, item.wastedPercent);
+          return item;
+        })
+        .sort((itemA, itemB) => itemB.wastedBytes - itemA.wastedBytes);
+
+    const wastedBytes = results.reduce((sum, item) => sum + item.wastedBytes, 0);
+
+    let displayValue = result.displayValue || '';
+    if (typeof result.displayValue === 'undefined' && wastedBytes) {
+      const wastedKbDisplay = this.bytesToKbString(wastedBytes);
+      const wastedMsDisplay = this.bytesToMsString(wastedBytes, networkThroughput);
+      displayValue = `Potential savings of ${wastedKbDisplay} (~${wastedMsDisplay})`;
+    }
+
+    return {
+      debugString,
+      displayValue,
+      rawValue: typeof result.passes === 'undefined' ?
+          wastedBytes < WASTEFUL_THRESHOLD_IN_BYTES :
+          !!result.passes,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.TABLE,
+        value: {results, tableHeadings: result.tableHeadings}
+      }
+    };
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {{results: !Array<Object>, tableHeadings: Object,
+   *     passes: boolean=, debugString: string=}}
+   */
+  static audit_() {
+    throw new Error('audit_ unimplemented');
+  }
 }
 
-remapped[r.name]=r;
-});
-return remapped;
-}
+module.exports = UnusedBytes;
 
-
-
-
-
-
-
-
-
-static _convertToWeight(result,expected,name){
-let weight=0;
-
-if(typeof expected==='undefined'||
-typeof expected.expectedValue==='undefined'||
-typeof expected.weight==='undefined'){
-const msg=
-`aggregations: ${name} audit does not contain expectedValue or weight properties`;
-throw new Error(msg);
-}
-
-
-
-if(result.error){
-return 0;
-}
-
-if(typeof result==='undefined'||
-typeof result.score==='undefined'){
-let msg=
-`${name} audit result is undefined or does not contain score property`;
-if(result&&result.debugString){
-msg+=': '+result.debugString;
-}
-throw new Error(msg);
-}
-
-if(typeof result.score!==typeof expected.expectedValue){
-const expectedType=typeof expected.expectedValue;
-const resultType=typeof result.rawValue;
-let msg=`Expected expectedValue of type ${expectedType}, got ${resultType}`;
-if(result.debugString){
-msg+=': '+result.debugString;
-}
-throw new Error(msg);
-}
-
-switch(typeof expected.expectedValue){
-case'boolean':
-weight=this._convertBooleanToWeight(result.score,
-expected.expectedValue,expected.weight);
-break;
-
-case'number':
-weight=this._convertNumberToWeight(result.score,expected.expectedValue,expected.weight);
-break;
-
-default:
-weight=0;
-break;}
-
-
-return weight;
-}
-
-
-
-
-
-
-
-
-static _convertNumberToWeight(resultValue,expectedValue,weight){
-return resultValue/expectedValue*weight;
-}
-
-
-
-
-
-
-
-
-static _convertBooleanToWeight(resultValue,expectedValue,weight){
-return resultValue===expectedValue?weight:0;
-}
-
-
-
-
-
-
-
-static compare(results,items){
-return items.map(item=>{
-const expectedNames=Object.keys(item.audits);
-
-
-
-const filteredAndRemappedResults=
-Aggregate._remapResultsByName(
-Aggregate._filterResultsByAuditNames(results,item.audits));
-
-
-const subItems=[];
-let overallScore=0;
-let maxScore=1;
-
-
-
-expectedNames.forEach(e=>{
-if(!filteredAndRemappedResults[e]){
-throw new Error(`aggregations: expected audit results not found under audit name ${e}`);
-}
-
-subItems.push(filteredAndRemappedResults[e].name);
-
-overallScore+=Aggregate._convertToWeight(
-filteredAndRemappedResults[e],
-item.audits[e],
-e);
-});
-
-maxScore=Aggregate._getTotalWeight(item.audits);
-
-return{
-overall:overallScore/maxScore,
-name:item.name,
-description:item.description,
-subItems:subItems};
-
-});
-}
-
-
-
-
-
-
-static getTotal(scores){
-return scores.reduce((total,s)=>total+s.overall,0)/scores.length;
-}
-
-
-
-
-
-
-
-static aggregate(aggregation,auditResults){
-const score=Aggregate.compare(auditResults,aggregation.items);
-return{
-name:aggregation.name,
-description:aggregation.description,
-scored:aggregation.scored,
-additional:aggregation.additional,
-total:aggregation.scored?Aggregate.getTotal(score):null,
-categorizable:aggregation.categorizable,
-score:score};
-
-}}
-
-
-module.exports=Aggregate;
-
-},{}],2:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"../../report/formatter":27,"../audit":2}],4:[function(require,module,exports){
+/**
+ * @license Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
 
 'use strict';
 
+/**
+ * @fileoverview Base class for boolean audits that can have multiple reasons for failure
+ */
 
+const Audit = require('./audit');
+const Formatter = require('../report/formatter');
 
+class MultiCheckAudit extends Audit {
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    return Promise.resolve(this.audit_(artifacts)).then(result => this.createAuditResult(result));
+  }
 
+  /**
+   * @param {!{failures: !Array<!string>, themeColor: ?string, manifestValues: ?Object, }} result
+   * @return {!AuditResult}
+   */
+  static createAuditResult(result) {
+    const extendedInfo = {
+      value: result,
+      formatter: Formatter.SUPPORTED_FORMATS.NULL
+    };
 
+    // If we fail, share the failures
+    if (result.failures.length > 0) {
+      return {
+        rawValue: false,
+        debugString: `Failures: ${result.failures.join(', ')}.`,
+        extendedInfo
+      };
+    }
 
-const Audit=require('../audit');
-const Formatter=require('../../report/formatter');
+    // Otherwise, we pass
+    return {
+      rawValue: true,
+      extendedInfo
+    };
+  }
 
-class AxeAudit extends Audit{
+  /**
+   * @param {!Artifacts} artifacts
+   */
+  static audit_() {
+    throw new Error('audit_ unimplemented');
+  }
+}
 
+module.exports = MultiCheckAudit;
 
-
-
-
-static audit(artifacts){
-const violations=artifacts.Accessibility.violations;
-const rule=violations.find(result=>result.id===this.meta.name);
-
-return this.generateAuditResult({
-rawValue:typeof rule==='undefined',
-extendedInfo:{
-formatter:Formatter.SUPPORTED_FORMATS.ACCESSIBILITY,
-value:rule}});
-
-
-}}
-
-
-module.exports=AxeAudit;
-
-},{"../../report/formatter":27,"../audit":3}],3:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"../report/formatter":27,"./audit":2}],5:[function(require,module,exports){
+(function (__dirname){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-const DEFAULT_PASS='defaultPass';
+const defaultConfigPath = './default.js';
+const defaultConfig = require('./default.js');
+const recordsFromLogs = require('../lib/network-recorder').recordsFromLogs;
 
-class Audit{
+const GatherRunner = require('../gather/gather-runner');
+const log = require('../lib/log');
+const path = require('path');
+const Audit = require('../audits/audit');
+const Runner = require('../runner');
 
+const _flatten = arr => [].concat(...arr);
 
+// cleanTrace is run to remove duplicate TracingStartedInPage events,
+// and to change TracingStartedInBrowser events into TracingStartedInPage.
+// This is done by searching for most occuring threads and basing new events
+// off of those.
+function cleanTrace(trace) {
+  const traceEvents = trace.traceEvents;
+  // Keep track of most occuring threads
+  const threads = [];
+  const countsByThread = {};
+  const traceStartEvents = [];
+  const makeMockEvent = (evt, ts) => {
+    return {
+      pid: evt.pid,
+      tid: evt.tid,
+      ts: ts || 0,  // default to 0 for now
+      ph: 'I',
+      cat: 'disabled-by-default-devtools.timeline',
+      name: 'TracingStartedInPage',
+      args: {
+        data: {
+          page: evt.frame
+        }
+      },
+      s: 't'
+    };
+  };
 
-static get DEFAULT_PASS(){
-return DEFAULT_PASS;
+  let frame;
+  let data;
+  let name;
+  let counter;
+
+  traceEvents.forEach((evt, idx) => {
+    if (evt.name.startsWith('TracingStartedIn')) {
+      traceStartEvents.push(idx);
+    }
+
+    // find the event's frame
+    data = evt.args && (evt.args.data || evt.args.beginData || evt.args.counters);
+    frame = (evt.args && evt.args.frame) || data && (data.frame || data.page);
+
+    if (!frame) {
+      return;
+    }
+
+    // Increase occurences count of the frame
+    name = `pid${evt.pid}-tid${evt.tid}-frame${frame}`;
+    counter = countsByThread[name];
+    if (!counter) {
+      counter = {
+        pid: evt.pid,
+        tid: evt.tid,
+        frame: frame,
+        count: 0
+      };
+      countsByThread[name] = counter;
+      threads.push(counter);
+    }
+    counter.count++;
+  });
+
+  // find most active thread (and frame)
+  threads.sort((a, b) => b.count - a.count);
+  const mostActiveFrame = threads[0];
+
+  // Remove all current TracingStartedIn* events, storing
+  // the first events ts.
+  const ts = traceEvents[traceStartEvents[0]] && traceEvents[traceStartEvents[0]].ts;
+
+  // account for offset after removing items
+  let i = 0;
+  for (const dup of traceStartEvents) {
+    traceEvents.splice(dup - i, 1);
+    i++;
+  }
+
+  // Add a new TracingStartedInPage event based on most active thread
+  // and using TS of first found TracingStartedIn* event
+  traceEvents.unshift(makeMockEvent(mostActiveFrame, ts));
+
+  return trace;
 }
 
+function validatePasses(passes, audits, rootPath) {
+  if (!Array.isArray(passes)) {
+    return;
+  }
+  const requiredGatherers = Config.getGatherersNeededByAudits(audits);
 
+  // Log if we are running gathers that are not needed by the audits listed in the config
+  passes.forEach(pass => {
+    pass.gatherers.forEach(gatherer => {
+      const GathererClass = GatherRunner.getGathererClass(gatherer, rootPath);
+      const isGatherRequiredByAudits = requiredGatherers.has(GathererClass.name);
+      if (isGatherRequiredByAudits === false) {
+        const msg = `${GathererClass.name} gatherer requested, however no audit requires it.`;
+        log.warn('config', msg);
+      }
+    });
+  });
 
+  // Log if multiple passes require trace or network recording and could overwrite one another.
+  const usedNames = new Set();
+  passes.forEach((pass, index) => {
+    if (!pass.recordNetwork && !pass.recordTrace) {
+      return;
+    }
 
-static get meta(){
-throw new Error('Audit meta information must be overridden.');
+    const passName = pass.passName || Audit.DEFAULT_PASS;
+    if (usedNames.has(passName)) {
+      log.warn('config', `passes[${index}] may overwrite trace or network ` +
+          `data of earlier pass without a unique passName (repeated name: ${passName}.`);
+    }
+    usedNames.add(passName);
+  });
 }
 
+function assertValidAudit(auditDefinition, auditPath) {
+  const auditName = auditPath || auditDefinition.meta.name;
 
+  if (typeof auditDefinition.audit !== 'function') {
+    throw new Error(`${auditName} has no audit() method.`);
+  }
 
+  if (typeof auditDefinition.meta.name !== 'string') {
+    throw new Error(`${auditName} has no meta.name property, or the property is not a string.`);
+  }
 
+  if (typeof auditDefinition.meta.category !== 'string') {
+    throw new Error(`${auditName} has no meta.category property, or the property is not a string.`);
+  }
 
-static generateErrorAuditResult(debugString){
-return this.generateAuditResult({
-rawValue:null,
-error:true,
-debugString});
+  if (typeof auditDefinition.meta.description !== 'string') {
+    throw new Error(
+      `${auditName} has no meta.description property, or the property is not a string.`
+    );
+  }
 
+  if (!Array.isArray(auditDefinition.meta.requiredArtifacts)) {
+    throw new Error(
+      `${auditName} has no meta.requiredArtifacts property, or the property is not an array.`
+    );
+  }
 }
 
+function expandArtifacts(artifacts) {
+  if (!artifacts) {
+    return null;
+  }
+  // currently only trace logs and performance logs should be imported
+  if (artifacts.traces) {
+    Object.keys(artifacts.traces).forEach(key => {
+      log.log('info', 'Normalizng trace contents into expected state...');
+      let trace = require(artifacts.traces[key]);
+      // Before Chrome 54.0.2816 (codereview.chromium.org/2161583004), trace was
+      // an array of trace events. After this point, trace is an object with a
+      // traceEvents property. Normalize to new format.
+      if (Array.isArray(trace)) {
+        trace = {
+          traceEvents: trace
+        };
+      }
+      trace = cleanTrace(trace);
 
+      artifacts.traces[key] = trace;
+    });
+  }
 
+  if (artifacts.performanceLog) {
+    if (typeof artifacts.performanceLog === 'string') {
+      // Support older format of a single performance log.
+      const log = require(artifacts.performanceLog);
+      artifacts.networkRecords = {
+        [Audit.DEFAULT_PASS]: recordsFromLogs(log)
+      };
+    } else {
+      artifacts.networkRecords = {};
+      Object.keys(artifacts.performanceLog).forEach(key => {
+        const log = require(artifacts.performanceLog[key]);
+        artifacts.networkRecords[key] = recordsFromLogs(log);
+      });
+    }
+  }
 
-
-static generateAuditResult(result){
-if(typeof result.rawValue==='undefined'){
-throw new Error('generateAuditResult requires a rawValue');
+  return artifacts;
 }
 
-const score=typeof result.score==='undefined'?result.rawValue:result.score;
-let displayValue=result.displayValue;
-if(typeof displayValue==='undefined'){
-displayValue=result.rawValue?result.rawValue:'';
+function merge(base, extension) {
+  if (typeof base === 'undefined') {
+    return extension;
+  } else if (Array.isArray(extension)) {
+    if (!Array.isArray(base)) throw new TypeError(`Expected array but got ${typeof base}`);
+    return base.concat(extension);
+  } else if (typeof extension === 'object') {
+    if (typeof base !== 'object') throw new TypeError(`Expected object but got ${typeof base}`);
+    Object.keys(extension).forEach(key => {
+      base[key] = merge(base[key], extension[key]);
+    });
+    return base;
+  }
+
+  return extension;
 }
 
-
-if(displayValue===score){
-displayValue='';
+function deepClone(json) {
+  return JSON.parse(JSON.stringify(json));
 }
 
-return{
-score,
-displayValue:`${displayValue}`,
-rawValue:result.rawValue,
-error:result.error,
-debugString:result.debugString,
-optimalValue:result.optimalValue,
-extendedInfo:result.extendedInfo,
-informative:this.meta.informative,
-name:this.meta.name,
-category:this.meta.category,
-description:this.meta.description,
-helpText:this.meta.helpText};
+class Config {
+  /**
+   * @constructor
+   * @param {!LighthouseConfig} configJSON
+   * @param {string=} configPath The absolute path to the config file, if there is one.
+   */
+  constructor(configJSON, configPath) {
+    if (!configJSON) {
+      configJSON = defaultConfig;
+      configPath = path.resolve(__dirname, defaultConfigPath);
+    }
 
-}}
+    if (configPath && !path.isAbsolute(configPath)) {
+      throw new Error('configPath must be an absolute path.');
+    }
 
+    // We don't want to mutate the original config object
+    const inputConfig = configJSON;
+    configJSON = deepClone(configJSON);
 
-module.exports=Audit;
+    // Copy arrays that could contain plugins to allow for programmatic
+    // injection of plugins.
+    if (Array.isArray(inputConfig.passes)) {
+      configJSON.passes.forEach((pass, i) => {
+        pass.gatherers = Array.from(inputConfig.passes[i].gatherers);
+      });
+    }
+    if (Array.isArray(inputConfig.audits)) {
+      configJSON.audits = Array.from(inputConfig.audits);
+    }
 
-},{}],4:[function(require,module,exports){
+    // Extend the default config if specified
+    if (configJSON.extends) {
+      configJSON = Config.extendConfigJSON(deepClone(defaultConfig), configJSON);
+    }
 
+    // Generate a limited config if specified
+    if (configJSON.settings &&
+        (Array.isArray(configJSON.settings.onlyCategories) ||
+        Array.isArray(configJSON.settings.onlyAudits))) {
+      const categoryIds = configJSON.settings.onlyCategories;
+      const auditIds = configJSON.settings.onlyAudits;
+      configJSON = Config.generateNewFilteredConfig(configJSON, categoryIds, auditIds);
+    }
 
+    // Store the directory of the config path, if one was provided.
+    this._configDir = configPath ? path.dirname(configPath) : undefined;
 
+    this._passes = configJSON.passes || null;
+    this._auditResults = configJSON.auditResults || null;
+    if (this._auditResults && !Array.isArray(this._auditResults)) {
+      throw new Error('config.auditResults must be an array');
+    }
 
+    this._aggregations = configJSON.aggregations || null;
 
+    this._audits = Config.requireAudits(configJSON.audits, this._configDir);
+    this._artifacts = expandArtifacts(configJSON.artifacts);
+    this._categories = configJSON.categories;
 
+    // validatePasses must follow after audits are required
+    validatePasses(configJSON.passes, this._audits, this._configDir);
+  }
 
+  /**
+   * @param {!Object} baseJSON The JSON of the configuration to extend
+   * @param {!Object} extendJSON The JSON of the extensions
+   * @return {!Object}
+   */
+  static extendConfigJSON(baseJSON, extendJSON) {
+    if (extendJSON.passes) {
+      extendJSON.passes.forEach(pass => {
+        const basePass = baseJSON.passes.find(candidate => candidate.passName === pass.passName);
+        if (!basePass || !pass.passName) {
+          baseJSON.passes.push(pass);
+        } else {
+          merge(basePass, pass);
+        }
+      });
 
+      delete extendJSON.passes;
+    }
 
+    return merge(baseJSON, extendJSON);
+  }
 
+  /**
+   * Filter out any unrequested items from the config, based on requested top-level categories.
+   * @param {!Object} oldConfig Lighthouse config object
+   * @param {!Array<string>=} categoryIds ID values of categories to include
+   * @param {!Array<string>=} auditIds ID values of categories to include
+   * @return {!Object} A new config
+   */
+  static generateNewFilteredConfig(oldConfig, categoryIds, auditIds) {
+    // 0. Clone config to avoid mutating it
+    const config = deepClone(oldConfig);
+    // 1. Filter to just the chosen categories
+    config.categories = Config.filterCategoriesAndAudits(config.categories, categoryIds, auditIds);
 
+    // 2. Resolve which audits will need to run
+    const requestedAuditNames = Config.getAuditIdsInCategories(config.categories);
+    const auditPathToNameMap = Config.getMapOfAuditPathToName(config);
+    config.audits = config.audits.filter(auditPath =>
+        requestedAuditNames.has(auditPathToNameMap.get(auditPath)));
 
+    // 3. Resolve which gatherers will need to run
+    const auditObjectsSelected = Config.requireAudits(config.audits);
+    const requiredGatherers = Config.getGatherersNeededByAudits(auditObjectsSelected);
 
+    // 4. Filter to only the neccessary passes
+    config.passes = Config.generatePassesNeededByGatherers(config.passes, requiredGatherers);
+    return config;
+  }
 
+  /**
+   * Filter out any unrequested categories or audits from the categories object.
+   * @param {!Object<string, {audits: !Array<{id: string}>}>} categories
+   * @param {!Array<string>=} categoryIds
+   * @param {!Array<string>=} auditIds
+   * @return {!Object<string, {audits: !Array<{id: string}>}>}
+   */
+  static filterCategoriesAndAudits(oldCategories, categoryIds = [], auditIds = []) {
+    const categories = {};
 
+    // warn if the category is not found
+    categoryIds.forEach(categoryId => {
+      if (!oldCategories[categoryId]) {
+        log.warn('config', `unrecognized category in 'onlyCategories': ${categoryId}`);
+      }
+    });
 
-'use strict';
+    // warn if the audit is not found in a category
+    auditIds.forEach(auditId => {
+      const foundCategory = Object.keys(oldCategories).find(categoryId => {
+        const audits = oldCategories[categoryId].audits;
+        return audits.find(candidate => candidate.id === auditId);
+      });
 
-const Audit=require('../audit');
-const Formatter=require('../../report/formatter');
+      if (!foundCategory) {
+        log.warn('config', `unrecognized audit in 'onlyAudits': ${auditId}`);
+      }
 
-const KB_IN_BYTES=1024;
-const WASTEFUL_THRESHOLD_IN_BYTES=20*KB_IN_BYTES;
+      if (categoryIds.includes(foundCategory)) {
+        log.warn('config', `${auditId} in 'onlyAudits' is already included by ` +
+            `${foundCategory} in 'onlyCategories'`);
+      }
+    });
 
+    Object.keys(oldCategories).forEach(categoryId => {
+      if (categoryIds.includes(categoryId)) {
+        categories[categoryId] = oldCategories[categoryId];
+      } else {
+        const newCategory = deepClone(oldCategories[categoryId]);
+        newCategory.audits = newCategory.audits.filter(audit => auditIds.includes(audit.id));
+        if (newCategory.audits.length) {
+          categories[categoryId] = newCategory;
+        }
+      }
+    });
 
+    return categories;
+  }
 
+  /**
+   * Finds the unique set of audit IDs used by the categories object.
+   * @param {!Object<string, {audits: !Array<{id: string}>}>} categories
+   * @return {!Set<string>}
+   */
+  static getAuditIdsInCategories(categories) {
+    const audits = _flatten(Object.keys(categories).map(id => categories[id].audits));
+    return new Set(audits.map(audit => audit.id));
+  }
 
+ /**
+  * @param {{categories: !Object<string, {name: string}>}} config
+  * @return {!Array<{id: string, name: string}>}
+  */
+  static getCategories(config) {
+    return Object.keys(config.categories).map(id => {
+      const name = config.categories[id].name;
+      return {id, name};
+    });
+  }
 
-class UnusedBytes extends Audit{
+  /**
+   * Creates mapping from audit path (used in config.audits) to audit.name (used in config.aggregations)
+   * @param {!Object} config Lighthouse config object.
+   * @return {Map}
+   */
+  static getMapOfAuditPathToName(config) {
+    const auditObjectsAll = Config.requireAudits(config.audits);
+    const auditPathToName = new Map(auditObjectsAll.map((AuditClass, index) => {
+      const auditPath = config.audits[index];
+      const auditName = AuditClass.meta.name;
+      return [auditPath, auditName];
+    }));
+    return auditPathToName;
+  }
 
+  /**
+   * From some requested audits, return names of all required artifacts
+   * @param {!Object} audits
+   * @return {!Set<string>}
+   */
+  static getGatherersNeededByAudits(audits) {
+    // It's possible we weren't given any audits (but existing audit results), in which case
+    // there is no need to do any work here.
+    if (!audits) {
+      return new Set();
+    }
 
+    return audits.reduce((list, audit) => {
+      audit.meta.requiredArtifacts.forEach(artifact => list.add(artifact));
+      return list;
+    }, new Set());
+  }
 
+  /**
+   * Filters to only required passes and gatherers, returning a new passes object
+   * @param {!Object} oldPasses
+   * @param {!Set<string>} requiredGatherers
+   * @return {!Object} fresh passes object
+   */
+  static generatePassesNeededByGatherers(oldPasses, requiredGatherers) {
+    const passes = JSON.parse(JSON.stringify(oldPasses));
+    const filteredPasses = passes.map(pass => {
+      // remove any unncessary gatherers from within the passes
+      pass.gatherers = pass.gatherers.filter(gathererName => {
+        gathererName = GatherRunner.getGathererClass(gathererName).name;
+        return requiredGatherers.has(gathererName);
+      });
+      return pass;
+    }).filter(pass => {
+      // remove any passes lacking concrete gatherers, unless they are dependent on the trace
+      if (pass.recordTrace) return true;
+      return pass.gatherers.length > 0;
+    });
+    return filteredPasses;
+  }
 
-static bytesToKbString(bytes){
-return Math.round(bytes/KB_IN_BYTES).toLocaleString()+' KB';
+  /**
+   * Take an array of audits and audit paths and require any paths (possibly
+   * relative to the optional `configPath`) using `Runner.resolvePlugin`,
+   * leaving only an array of Audits.
+   * @param {?Array<(string|!Audit)>} audits
+   * @param {string=} configPath
+   * @return {?Array<!Audit>}
+   */
+  static requireAudits(audits, configPath) {
+    if (!audits) {
+      return null;
+    }
+
+    const coreList = Runner.getAuditList();
+    return audits.map(pathOrAuditClass => {
+      let AuditClass;
+      if (typeof pathOrAuditClass === 'string') {
+        const path = pathOrAuditClass;
+        // See if the audit is a Lighthouse core audit.
+        const coreAudit = coreList.find(a => a === `${path}.js`);
+        let requirePath = `../audits/${path}`;
+        if (!coreAudit) {
+          // Otherwise, attempt to find it elsewhere. This throws if not found.
+          requirePath = Runner.resolvePlugin(path, configPath, 'audit');
+        }
+        AuditClass = require(requirePath);
+        assertValidAudit(AuditClass, path);
+      } else {
+        AuditClass = pathOrAuditClass;
+        assertValidAudit(AuditClass);
+      }
+
+      return AuditClass;
+    });
+  }
+
+  /** @type {string} */
+  get configDir() {
+    return this._configDir;
+  }
+
+  /** @type {Array<!Pass>} */
+  get passes() {
+    return this._passes;
+  }
+
+  /** @type {Array<!Audit>} */
+  get audits() {
+    return this._audits;
+  }
+
+  /** @type {Array<!AuditResult>} */
+  get auditResults() {
+    return this._auditResults;
+  }
+
+  /** @type {Array<!Artifacts>} */
+  get artifacts() {
+    return this._artifacts;
+  }
+
+  /** @type {Array<!Aggregation>} */
+  get aggregations() {
+    return this._aggregations;
+  }
+
+  /** @type {Object<{audits: !Array<{id: string, weight: number}>}>} */
+  get categories() {
+    return this._categories;
+  }
 }
 
+module.exports = Config;
 
+}).call(this,"/../lighthouse-core/config")
+},{"../audits/audit":2,"../gather/gather-runner":12,"../lib/log":19,"../lib/network-recorder":21,"../runner":33,"./default.js":6,"path":220}],6:[function(require,module,exports){
+/* eslint-disable */
+module.exports = {
+  "settings": {},
+  "passes": [{
+    "passName": "defaultPass",
+    "recordNetwork": true,
+    "recordTrace": true,
+    "pauseBeforeTraceEndMs": 5000,
+    "useThrottling": true,
+    "gatherers": [
+      "url",
+      "viewport",
+      "viewport-dimensions",
+      "theme-color",
+      "manifest",
+      "image-usage",
+      "accessibility"
+    ]
+  },
+  {
+    "passName": "offlinePass",
+    "recordNetwork": true,
+    "useThrottling": false,
+    "gatherers": [
+      "service-worker",
+      "offline"
+    ]
+  },
+  {
+    "passName": "redirectPass",
+    "useThrottling": false,
+    "gatherers": [
+      "http-redirect",
+      "html-without-javascript"
+    ]
+  }, {
+    "passName": "dbw",
+    "recordNetwork": true,
+    "useThrottling": false,
+    "gatherers": [
+      "chrome-console-messages",
+      "styles",
+      // "css-usage",
+      "dobetterweb/all-event-listeners",
+      "dobetterweb/anchors-with-no-rel-noopener",
+      "dobetterweb/appcache",
+      "dobetterweb/console-time-usage",
+      "dobetterweb/datenow",
+      "dobetterweb/document-write",
+      "dobetterweb/geolocation-on-start",
+      "dobetterweb/notification-on-start",
+      "dobetterweb/domstats",
+      "dobetterweb/optimized-images",
+      "dobetterweb/response-compression",
+      "dobetterweb/tags-blocking-first-paint",
+      "dobetterweb/websql"
+    ]
+  }],
 
+  "audits": [
+    "is-on-https",
+    "redirects-http",
+    "service-worker",
+    "works-offline",
+    "viewport",
+    "without-javascript",
+    "first-meaningful-paint",
+    "load-fast-enough-for-pwa",
+    "speed-index-metric",
+    "estimated-input-latency",
+    "time-to-interactive",
+    "user-timings",
+    "critical-request-chains",
+    "webapp-install-banner",
+    "splash-screen",
+    "themed-omnibox",
+    "manifest-short-name-length",
+    "content-width",
+    "deprecations",
+    "accessibility/accesskeys",
+    "accessibility/aria-allowed-attr",
+    "accessibility/aria-required-attr",
+    "accessibility/aria-required-children",
+    "accessibility/aria-required-parent",
+    "accessibility/aria-roles",
+    "accessibility/aria-valid-attr-value",
+    "accessibility/aria-valid-attr",
+    "accessibility/audio-caption",
+    "accessibility/button-name",
+    "accessibility/bypass",
+    "accessibility/color-contrast",
+    "accessibility/definition-list",
+    "accessibility/dlitem",
+    "accessibility/document-title",
+    "accessibility/duplicate-id",
+    "accessibility/frame-title",
+    "accessibility/html-has-lang",
+    "accessibility/html-lang-valid",
+    "accessibility/image-alt",
+    "accessibility/input-image-alt",
+    "accessibility/label",
+    "accessibility/layout-table",
+    "accessibility/link-name",
+    "accessibility/list",
+    "accessibility/listitem",
+    "accessibility/meta-refresh",
+    "accessibility/meta-viewport",
+    "accessibility/object-alt",
+    "accessibility/tabindex",
+    "accessibility/td-headers-attr",
+    "accessibility/th-has-data-cells",
+    "accessibility/valid-lang",
+    "accessibility/video-caption",
+    "accessibility/video-description",
+    "byte-efficiency/total-byte-weight",
+    // "byte-efficiency/unused-css-rules",
+    "byte-efficiency/offscreen-images",
+    "byte-efficiency/uses-optimized-images",
+    "byte-efficiency/uses-request-compression",
+    "byte-efficiency/uses-responsive-images",
+    "dobetterweb/appcache-manifest",
+    "dobetterweb/dom-size",
+    "dobetterweb/external-anchors-use-rel-noopener",
+    "dobetterweb/geolocation-on-start",
+    "dobetterweb/link-blocking-first-paint",
+    "dobetterweb/no-console-time",
+    "dobetterweb/no-datenow",
+    "dobetterweb/no-document-write",
+    "dobetterweb/no-mutation-events",
+    "dobetterweb/no-old-flexbox",
+    "dobetterweb/no-websql",
+    "dobetterweb/notification-on-start",
+    "dobetterweb/script-blocking-first-paint",
+    "dobetterweb/uses-http2",
+    "dobetterweb/uses-passive-event-listeners"
+  ],
 
-
-
-static toSavingsString(bytes=0,percent=0){
-const kbDisplay=this.bytesToKbString(bytes);
-const percentDisplay=Math.round(percent).toLocaleString()+'%';
-return`${kbDisplay} _${percentDisplay}_`;
+  "aggregations": [{
+    "name": "Progressive Web App",
+    "id": "pwa",
+    "description": "These audits validate the aspects of a Progressive Web App. They are a subset of the [PWA Checklist](https://developers.google.com/web/progressive-web-apps/checklist).",
+    "scored": true,
+    "categorizable": true,
+    "items": [{
+      "name": "App can load on offline/flaky connections",
+      "description": "Ensuring your web app can respond when the network connection is unavailable or flaky is critical to providing your users a good experience. This is achieved through use of a [Service Worker](https://developers.google.com/web/fundamentals/primers/service-worker/).",
+      "audits": {
+        "service-worker": {
+          "expectedValue": true,
+          "weight": 1
+        },
+        "works-offline": {
+          "expectedValue": true,
+          "weight": 1
+        }
+      }
+    },{
+      "name": "Page load performance is fast",
+      "description": "Users notice if sites and apps don't perform well. These top-level metrics capture the most important perceived performance concerns.",
+      "audits": {
+        "load-fast-enough-for-pwa": {
+          "expectedValue": true,
+          "weight": 1
+        },
+        "first-meaningful-paint": {
+          "expectedValue": 100,
+          "weight": 1
+        },
+        "speed-index-metric": {
+          "expectedValue": 100,
+          "weight": 1
+        },
+        "estimated-input-latency": {
+          "expectedValue": 100,
+          "weight": 1
+        },
+        "time-to-interactive": {
+          "expectedValue": 100,
+          "weight": 1
+        }
+      }
+    }, {
+      "name": "Site is progressively enhanced",
+      "description": "Progressive enhancement means that everyone can access the basic content and functionality of a page in any browser, and those without certain browser features may receive a reduced but still functional experience.",
+      "audits": {
+        "without-javascript": {
+          "expectedValue": true,
+          "weight": 1
+        }
+      }
+    }, {
+      "name": "Network connection is secure",
+      "description": "Security is an important part of the web for both developers and users. Moving forward, Transport Layer Security (TLS) support will be required for many APIs.",
+      "audits": {
+        "is-on-https": {
+          "expectedValue": true,
+          "weight": 1
+        },
+        "redirects-http": {
+          "expectedValue": true,
+          "weight": 1
+        }
+      }
+    }, {
+      "name": "User can be prompted to Add to Homescreen",
+      "audits": {
+        "webapp-install-banner": {
+          "expectedValue": true,
+          "weight": 1
+        }
+      }
+    }, {
+      "name": "Installed web app will launch with custom splash screen",
+      "audits": {
+        "splash-screen": {
+          "expectedValue": true,
+          "weight": 1
+        }
+      }
+    }, {
+      "name": "Address bar matches brand colors",
+      "audits": {
+        "themed-omnibox": {
+          "expectedValue": true,
+          "weight": 1
+        }
+      }
+    }, {
+      "name": "Design is mobile-friendly",
+      "description": "Users increasingly experience your app on mobile devices, so it's important to ensure that the experience can adapt to smaller screens.",
+      "audits": {
+        "viewport": {
+          "expectedValue": true,
+          "weight": 1
+        },
+        "content-width": {
+          "expectedValue": true,
+          "weight": 1
+        }
+      }
+    }]
+  }, {
+    "name": "Best Practices",
+    "id": "bp",
+    "description": "We've compiled some recommendations for modernizing your web app and avoiding performance pitfalls. These audits do not affect your score but are worth a look.",
+    "scored": false,
+    "categorizable": true,
+    "items": [{
+      "name": "Using modern offline features",
+      "audits": {
+        "appcache-manifest": {
+          "expectedValue": true,
+          "weight": 1
+        },
+        "no-websql": {
+          "expectedValue": true,
+          "weight": 1
+        }
+      }
+    }, {
+      "name": "Using modern protocols",
+      "audits": {
+        "is-on-https": {
+          "expectedValue": true,
+          "weight": 1
+        },
+        "uses-http2": {
+          "expectedValue": true,
+          "description": "Resources made by this application should be severed over HTTP/2 for improved performance.",
+          "weight": 1
+        }
+      }
+    }, {
+      "name": "Using modern CSS features",
+      "audits": {
+        "no-old-flexbox": {
+          "expectedValue": true,
+          "weight": 1
+        }
+      }
+    }, {
+      "name": "Using modern JavaScript features",
+      "audits": {
+        "uses-passive-event-listeners": {
+          "expectedValue": true,
+          "weight": 1
+        },
+        "no-mutation-events": {
+          "expectedValue": true,
+          "weight": 1
+        }
+      }
+    }, {
+      "name": "Avoiding APIs that harm the user experience",
+      "audits": {
+        "no-document-write": {
+          "expectedValue": true,
+          "weight": 1
+        },
+        "external-anchors-use-rel-noopener": {
+          "expectedValue": true,
+          "weight": 1
+        },
+        "geolocation-on-start": {
+          "expectedValue": true,
+          "weight": 1
+        },
+        "notification-on-start": {
+          "expectedValue": true,
+          "weight": 1
+        }
+      }
+    }, {
+      "name": "Avoiding deprecated APIs and browser interventions",
+      "audits": {
+        "deprecations": {
+          "expectedValue": true,
+          "weight": 1
+        }
+      }
+    }, {
+      "name": "Other",
+      "audits": {
+        "manifest-short-name-length": {
+          "expectedValue": true,
+          "weight": 1
+        }
+      }
+    }]
+  }, {
+      "name": "Accessibility",
+      "id": "accessibility",
+      "description": "These checks highlight opportunities to improve the accessibility of your app.",
+      "scored": false,
+      "categorizable": true,
+      "items": [{
+        "name": "Color contrast of elements is satisfactory",
+        "audits": {
+          "color-contrast": {
+            "expectedValue": true,
+            "weight": 1
+          }
+        }
+      }, {
+        "name": "Elements are well structured",
+        "audits": {
+          "definition-list": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "dlitem": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "duplicate-id": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "list": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "listitem": {
+            "expectedValue": true,
+            "weight": 1
+          }
+        }
+      }, {
+        "name": "Elements avoid incorrect use of attributes",
+        "audits": {
+          "accesskeys": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "audio-caption": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "image-alt": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "input-image-alt": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "tabindex": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "td-headers-attr": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "th-has-data-cells": {
+            "expectedValue": true,
+            "weight": 1
+          }
+        }
+      }, {
+        "name": "Elements applying ARIA follow correct practices",
+        "audits": {
+          "aria-allowed-attr": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "aria-required-attr": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "aria-required-children": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "aria-required-parent": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "aria-roles": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "aria-valid-attr-value": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "aria-valid-attr": {
+            "expectedValue": true,
+            "weight": 1
+          }
+        }
+      }, {
+        "name": "Elements describe their contents well",
+        "audits": {
+          "document-title": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "frame-title": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "label": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "layout-table": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "object-alt": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "video-caption": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "video-description": {
+            "expectedValue": true,
+            "weight": 1
+          }
+        }
+      }, {
+        "name": "The page's language is specified and valid",
+        "audits": {
+          "html-has-lang": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "html-lang-valid": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "valid-lang": {
+            "expectedValue": true,
+            "weight": 1
+          }
+        }
+      }, {
+        "name": "The document does not use <meta http-equiv=\"refresh\">",
+        "audits": {
+          "meta-refresh": {
+            "expectedValue": true,
+            "weight": 1
+          }
+        }
+      }, {
+        "name": "The <meta name=\"viewport\"> element follows best practices",
+        "audits": {
+          "meta-viewport": {
+            "expectedValue": true,
+            "weight": 1
+          }
+        }
+      }, {
+        "name": "Links and buttons have discernable names",
+        "audits": {
+          "button-name": {
+            "expectedValue": true,
+            "weight": 1
+          },
+          "link-name": {
+            "expectedValue": true,
+            "weight": 1
+          }
+        }
+      }, {
+        "name": "Repetitive content can be bypassed",
+        "audits": {
+          "bypass": {
+            "expectedValue": true,
+            "weight": 1
+          }
+        }
+      }]
+    }, {
+    "name": "Performance",
+    "id": "perf",
+    "description": "These encapsulate your app's performance.",
+    "scored": false,
+    "categorizable": false,
+    "items": [{
+      "audits": {
+        "total-byte-weight": {
+          "expectedValue": 100,
+          "weight": 1
+        },
+        "dom-size": {
+          "expectedValue": 100,
+          "weight": 1
+        },
+        "uses-optimized-images": {
+          "expectedValue": true,
+          "weight": 1
+        },
+        "uses-request-compression": {
+          "expectedValue": true,
+          "weight": 1
+        },
+        "uses-responsive-images": {
+          "expectedValue": true,
+          "weight": 1
+        },
+        "offscreen-images": {
+          "expectedValue": true,
+          "weight": 1
+        },
+        // "unused-css-rules": {
+        //   "expectedValue": true,
+        //   "weight": 1
+        // },
+        "link-blocking-first-paint": {
+          "expectedValue": true,
+          "weight": 1
+        },
+        "script-blocking-first-paint": {
+          "expectedValue": true,
+          "weight": 1
+        },
+         "critical-request-chains": {
+          "expectedValue": true,
+          "weight": 1
+        },
+        "user-timings": {
+          "expectedValue": true,
+          "weight": 1
+        }
+      }
+    }]
+  }, {
+    "name": "Fancier stuff",
+    "id": "fancy",
+    "description": "A list of newer features that you could be using in your app. These audits do not affect your score and are just suggestions.",
+    "scored": false,
+    "categorizable": true,
+    "additional": true,
+    "items": [{
+      "name": "New JavaScript features",
+      "audits": {
+        "no-datenow": {
+          "expectedValue": true,
+          "weight": 1
+        },
+        "no-console-time": {
+          "expectedValue": true,
+          "weight": 1
+        }
+      }
+    }]
+  }],
+  "categories": {
+    "pwa": {
+      "name": "Progressive Web App",
+      "weight": 1,
+      "description": "These audits validate the aspects of a Progressive Web App. They are a subset of the [PWA Checklist](https://developers.google.com/web/progressive-web-apps/checklist).",
+      "audits": [
+        {"id": "service-worker", "weight": 1},
+        {"id": "works-offline", "weight": 1},
+        {"id": "without-javascript", "weight": 1},
+        {"id": "is-on-https", "weight": 1},
+        {"id": "redirects-http", "weight": 1},
+        {"id": "load-fast-enough-for-pwa", "weight": 1},
+        {"id": "webapp-install-banner", "weight": 1},
+        {"id": "splash-screen", "weight": 1},
+        {"id": "themed-omnibox", "weight": 1},
+        {"id": "viewport", "weight": 1},
+        {"id": "content-width", "weight": 1}
+      ]
+    },
+    "performance": {
+      "name": "Performance",
+      "description": "These encapsulate your app's performance.",
+      "audits": [
+        {"id": "first-meaningful-paint", "weight": 5},
+        {"id": "speed-index-metric", "weight": 1},
+        {"id": "estimated-input-latency", "weight": 1},
+        {"id": "time-to-interactive", "weight": 5},
+        {"id": "link-blocking-first-paint", "weight": 0},
+        {"id": "script-blocking-first-paint", "weight": 0},
+        // {"id": "unused-css-rules", "weight": 0},
+        {"id": "uses-optimized-images", "weight": 0},
+        {"id": "uses-request-compression", "weight": 0},
+        {"id": "uses-responsive-images", "weight": 0},
+        {"id": "total-byte-weight", "weight": 0},
+        {"id": "dom-size", "weight": 0},
+        {"id": "critical-request-chains", "weight": 0},
+        {"id": "user-timings", "weight": 0}
+      ]
+    },
+    "accessibility": {
+      "name": "Accessibility",
+      "description": "These audits validate that your app [works for all users](https://developers.google.com/web/fundamentals/accessibility/).",
+      "audits": [
+        {"id": "accesskeys", "weight": 1},
+        {"id": "aria-allowed-attr", "weight": 1},
+        {"id": "aria-required-attr", "weight": 1},
+        {"id": "aria-required-children", "weight": 1},
+        {"id": "aria-required-parent", "weight": 1},
+        {"id": "aria-roles", "weight": 1},
+        {"id": "aria-valid-attr-value", "weight": 1},
+        {"id": "aria-valid-attr", "weight": 1},
+        {"id": "audio-caption", "weight": 1},
+        {"id": "button-name", "weight": 1},
+        {"id": "bypass", "weight": 1},
+        {"id": "color-contrast", "weight": 1},
+        {"id": "definition-list", "weight": 1},
+        {"id": "dlitem", "weight": 1},
+        {"id": "document-title", "weight": 1},
+        {"id": "duplicate-id", "weight": 1},
+        {"id": "frame-title", "weight": 1},
+        {"id": "html-has-lang", "weight": 1},
+        {"id": "html-lang-valid", "weight": 1},
+        {"id": "image-alt", "weight": 1},
+        {"id": "input-image-alt", "weight": 1},
+        {"id": "label", "weight": 1},
+        {"id": "layout-table", "weight": 1},
+        {"id": "link-name", "weight": 1},
+        {"id": "list", "weight": 1},
+        {"id": "listitem", "weight": 1},
+        {"id": "meta-refresh", "weight": 1},
+        {"id": "meta-viewport", "weight": 1},
+        {"id": "object-alt", "weight": 1},
+        {"id": "tabindex", "weight": 1},
+        {"id": "td-headers-attr", "weight": 1},
+        {"id": "th-has-data-cells", "weight": 1},
+        {"id": "valid-lang", "weight": 1},
+        {"id": "video-caption", "weight": 1},
+        {"id": "video-description", "weight": 1},
+      ]
+    },
+    "best-practices": {
+      "name": "Best Practices",
+      "description": "We've compiled some recommendations for modernizing your web app and avoiding performance pitfalls. These audits do not affect your score but are worth a look.",
+      "audits": [
+        {"id": "appcache-manifest", "weight": 1},
+        {"id": "no-websql", "weight": 1},
+        {"id": "is-on-https", "weight": 1},
+        {"id": "uses-http2", "weight": 1},
+        {"id": "no-old-flexbox", "weight": 1},
+        {"id": "uses-passive-event-listeners", "weight": 1},
+        {"id": "no-mutation-events", "weight": 1},
+        {"id": "no-document-write", "weight": 1},
+        {"id": "external-anchors-use-rel-noopener", "weight": 1},
+        {"id": "geolocation-on-start", "weight": 1},
+        {"id": "notification-on-start", "weight": 1},
+        {"id": "deprecations", "weight": 1},
+        {"id": "manifest-short-name-length", "weight": 1},
+      ]
+    }
+  }
 }
 
-
-
-
-
-
-static bytesToMsString(bytes,networkThroughput){
-return(Math.round(bytes/networkThroughput*100)*10).toLocaleString()+'ms';
-}
-
-
-
-
-
-static audit(artifacts){
-const networkRecords=artifacts.networkRecords[Audit.DEFAULT_PASS];
-return artifacts.requestNetworkThroughput(networkRecords).then(networkThroughput=>{
-const result=this.audit_(artifacts,networkRecords);
-const debugString=result.debugString;
-const results=result.results.
-map(item=>{
-item.wastedKb=this.bytesToKbString(item.wastedBytes);
-item.wastedMs=this.bytesToMsString(item.wastedBytes,networkThroughput);
-item.totalKb=this.bytesToKbString(item.totalBytes);
-item.totalMs=this.bytesToMsString(item.totalBytes,networkThroughput);
-item.potentialSavings=this.toSavingsString(item.wastedBytes,item.wastedPercent);
-return item;
-}).
-sort((itemA,itemB)=>itemB.wastedBytes-itemA.wastedBytes);
-
-const wastedBytes=results.reduce((sum,item)=>sum+item.wastedBytes,0);
-
-let displayValue=result.displayValue||'';
-if(typeof result.displayValue==='undefined'&&wastedBytes){
-const wastedKbDisplay=this.bytesToKbString(wastedBytes);
-const wastedMsDisplay=this.bytesToMsString(wastedBytes,networkThroughput);
-displayValue=`Potential savings of ${wastedKbDisplay} (~${wastedMsDisplay})`;
-}
-
-return this.generateAuditResult({
-debugString,
-displayValue,
-rawValue:typeof result.passes==='undefined'?
-wastedBytes<WASTEFUL_THRESHOLD_IN_BYTES:
-!!result.passes,
-extendedInfo:{
-formatter:Formatter.SUPPORTED_FORMATS.TABLE,
-value:{results,tableHeadings:result.tableHeadings}}});
-
-
-});
-}
-
-
-
-
-
-
-static audit_(){
-throw new Error('audit_ unimplemented');
-}}
-
-
-module.exports=UnusedBytes;
-
-},{"../../report/formatter":27,"../audit":3}],5:[function(require,module,exports){
-(function(__dirname){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-'use strict';
-
-const defaultConfigPath='./default.json';
-const defaultConfig=require('./default.json');
-const recordsFromLogs=require('../lib/network-recorder').recordsFromLogs;
-
-const GatherRunner=require('../gather/gather-runner');
-const log=require('../lib/log');
-const path=require('path');
-const Audit=require('../audits/audit');
-const Runner=require('../runner');
-
-const _flatten=arr=>[].concat(...arr);
-
-
-
-
-
-function cleanTrace(trace){
-const traceEvents=trace.traceEvents;
-
-const threads=[];
-const countsByThread={};
-const traceStartEvents=[];
-const makeMockEvent=(evt,ts)=>{
-return{
-pid:evt.pid,
-tid:evt.tid,
-ts:ts||0,
-ph:'I',
-cat:'disabled-by-default-devtools.timeline',
-name:'TracingStartedInPage',
-args:{
-data:{
-page:evt.frame}},
-
-
-s:'t'};
-
-};
-
-let frame;
-let data;
-let name;
-let counter;
-
-traceEvents.forEach((evt,idx)=>{
-if(evt.name.startsWith('TracingStartedIn')){
-traceStartEvents.push(idx);
-}
-
-
-data=evt.args&&(evt.args.data||evt.args.beginData||evt.args.counters);
-frame=evt.args&&evt.args.frame||data&&(data.frame||data.page);
-
-if(!frame){
-return;
-}
-
-
-name=`pid${evt.pid}-tid${evt.tid}-frame${frame}`;
-counter=countsByThread[name];
-if(!counter){
-counter={
-pid:evt.pid,
-tid:evt.tid,
-frame:frame,
-count:0};
-
-countsByThread[name]=counter;
-threads.push(counter);
-}
-counter.count++;
-});
-
-
-threads.sort((a,b)=>b.count-a.count);
-const mostActiveFrame=threads[0];
-
-
-
-const ts=traceEvents[traceStartEvents[0]]&&traceEvents[traceStartEvents[0]].ts;
-
-
-let i=0;
-for(const dup of traceStartEvents){
-traceEvents.splice(dup-i,1);
-i++;
-}
-
-
-
-traceEvents.unshift(makeMockEvent(mostActiveFrame,ts));
-
-return trace;
-}
-
-function validatePasses(passes,audits,rootPath){
-if(!Array.isArray(passes)){
-return;
-}
-const requiredGatherers=Config.getGatherersNeededByAudits(audits);
-
-
-passes.forEach(pass=>{
-pass.gatherers.forEach(gatherer=>{
-const GathererClass=GatherRunner.getGathererClass(gatherer,rootPath);
-const isGatherRequiredByAudits=requiredGatherers.has(GathererClass.name);
-if(isGatherRequiredByAudits===false){
-const msg=`${GathererClass.name} gatherer requested, however no audit requires it.`;
-log.warn('config',msg);
-}
-});
-});
-
-
-const usedNames=new Set();
-passes.forEach((pass,index)=>{
-if(!pass.recordNetwork&&!pass.recordTrace){
-return;
-}
-
-const passName=pass.passName||Audit.DEFAULT_PASS;
-if(usedNames.has(passName)){
-log.warn('config',`passes[${index}] may overwrite trace or network `+
-`data of earlier pass without a unique passName (repeated name: ${passName}.`);
-}
-usedNames.add(passName);
-});
-}
-
-function assertValidAudit(auditDefinition,auditPath){
-const auditName=auditPath||auditDefinition.meta.name;
-
-if(typeof auditDefinition.audit!=='function'){
-throw new Error(`${auditName} has no audit() method.`);
-}
-
-if(typeof auditDefinition.meta.name!=='string'){
-throw new Error(`${auditName} has no meta.name property, or the property is not a string.`);
-}
-
-if(typeof auditDefinition.meta.category!=='string'){
-throw new Error(`${auditName} has no meta.category property, or the property is not a string.`);
-}
-
-if(typeof auditDefinition.meta.description!=='string'){
-throw new Error(
-`${auditName} has no meta.description property, or the property is not a string.`);
-
-}
-
-if(!Array.isArray(auditDefinition.meta.requiredArtifacts)){
-throw new Error(
-`${auditName} has no meta.requiredArtifacts property, or the property is not an array.`);
-
-}
-
-if(typeof auditDefinition.generateAuditResult!=='function'){
-throw new Error(
-`${auditName} has no generateAuditResult() method. `+
-'Did you inherit from the proper base class?');
-
-}
-}
-
-function expandArtifacts(artifacts){
-if(!artifacts){
-return null;
-}
-
-if(artifacts.traces){
-Object.keys(artifacts.traces).forEach(key=>{
-log.log('info','Normalizng trace contents into expected state...');
-let trace=require(artifacts.traces[key]);
-
-
-
-if(Array.isArray(trace)){
-trace={
-traceEvents:trace};
-
-}
-trace=cleanTrace(trace);
-
-artifacts.traces[key]=trace;
-});
-}
-
-if(artifacts.performanceLog){
-if(typeof artifacts.performanceLog==='string'){
-
-const log=require(artifacts.performanceLog);
-artifacts.networkRecords={
-[Audit.DEFAULT_PASS]:recordsFromLogs(log)};
-
-}else{
-artifacts.networkRecords={};
-Object.keys(artifacts.performanceLog).forEach(key=>{
-const log=require(artifacts.performanceLog[key]);
-artifacts.networkRecords[key]=recordsFromLogs(log);
-});
-}
-}
-
-return artifacts;
-}
-
-function merge(base,extension){
-if(typeof base==='undefined'){
-return extension;
-}else if(Array.isArray(extension)){
-if(!Array.isArray(base))throw new TypeError(`Expected array but got ${typeof base}`);
-return base.concat(extension);
-}else if(typeof extension==='object'){
-if(typeof base!=='object')throw new TypeError(`Expected object but got ${typeof base}`);
-Object.keys(extension).forEach(key=>{
-base[key]=merge(base[key],extension[key]);
-});
-return base;
-}
-
-return extension;
-}
-
-function deepClone(json){
-return JSON.parse(JSON.stringify(json));
-}
-
-class Config{
-
-
-
-
-
-constructor(configJSON,configPath){
-if(!configJSON){
-configJSON=defaultConfig;
-configPath=path.resolve(__dirname,defaultConfigPath);
-}
-
-if(configPath&&!path.isAbsolute(configPath)){
-throw new Error('configPath must be an absolute path.');
-}
-
-
-const inputConfig=configJSON;
-configJSON=deepClone(configJSON);
-
-
-
-if(Array.isArray(inputConfig.passes)){
-configJSON.passes.forEach((pass,i)=>{
-pass.gatherers=Array.from(inputConfig.passes[i].gatherers);
-});
-}
-if(Array.isArray(inputConfig.audits)){
-configJSON.audits=Array.from(inputConfig.audits);
-}
-
-
-if(configJSON.extends){
-configJSON=Config.extendConfigJSON(deepClone(defaultConfig),configJSON);
-}
-
-
-this._configDir=configPath?path.dirname(configPath):undefined;
-
-this._passes=configJSON.passes||null;
-this._auditResults=configJSON.auditResults||null;
-if(this._auditResults&&!Array.isArray(this._auditResults)){
-throw new Error('config.auditResults must be an array');
-}
-
-this._aggregations=configJSON.aggregations||null;
-
-this._audits=Config.requireAudits(configJSON.audits,this._configDir);
-this._artifacts=expandArtifacts(configJSON.artifacts);
-
-
-validatePasses(configJSON.passes,this._audits,this._configDir);
-}
-
-
-
-
-
-
-static extendConfigJSON(baseJSON,extendJSON){
-if(extendJSON.passes){
-extendJSON.passes.forEach(pass=>{
-const basePass=baseJSON.passes.find(candidate=>candidate.passName===pass.passName);
-if(!basePass||!pass.passName){
-baseJSON.passes.push(pass);
-}else{
-merge(basePass,pass);
-}
-});
-
-delete extendJSON.passes;
-}
-
-return merge(baseJSON,extendJSON);
-}
-
-
-
-
-
-
-
-static generateNewConfigOfAggregations(oldConfig,aggregationIDs){
-
-const config=JSON.parse(JSON.stringify(oldConfig));
-
-config.aggregations=config.aggregations.filter(agg=>aggregationIDs.includes(agg.id));
-
-
-const requestedAuditNames=Config.getAuditsNeededByAggregations(config.aggregations);
-const auditPathToNameMap=Config.getMapOfAuditPathToName(config);
-config.audits=config.audits.filter(auditPath=>
-requestedAuditNames.has(auditPathToNameMap.get(auditPath)));
-
-
-const auditObjectsSelected=Config.requireAudits(config.audits);
-const requiredGatherers=Config.getGatherersNeededByAudits(auditObjectsSelected);
-
-
-config.passes=Config.generatePassesNeededByGatherers(config.passes,requiredGatherers);
-return config;
-}
-
-
-
-
-
-
-static getAggregations(config){
-return config.aggregations.map(agg=>({
-name:agg.name,
-id:agg.id}));
-
-}
-
-
-
-
-
-
-static getAuditsNeededByAggregations(aggregations){
-const requestedItems=_flatten(aggregations.map(aggregation=>aggregation.items));
-const requestedAudits=_flatten(requestedItems.map(item=>Object.keys(item.audits)));
-return new Set(requestedAudits);
-}
-
-
-
-
-
-
-static getMapOfAuditPathToName(config){
-const auditObjectsAll=Config.requireAudits(config.audits);
-const auditPathToName=new Map(auditObjectsAll.map((AuditClass,index)=>{
-const auditPath=config.audits[index];
-const auditName=AuditClass.meta.name;
-return[auditPath,auditName];
-}));
-return auditPathToName;
-}
-
-
-
-
-
-
-static getGatherersNeededByAudits(audits){
-
-
-if(!audits){
-return new Set();
-}
-
-return audits.reduce((list,audit)=>{
-audit.meta.requiredArtifacts.forEach(artifact=>list.add(artifact));
-return list;
-},new Set());
-}
-
-
-
-
-
-
-
-static generatePassesNeededByGatherers(oldPasses,requiredGatherers){
-const passes=JSON.parse(JSON.stringify(oldPasses));
-const filteredPasses=passes.map(pass=>{
-
-pass.gatherers=pass.gatherers.filter(gathererName=>{
-gathererName=GatherRunner.getGathererClass(gathererName).name;
-return requiredGatherers.has(gathererName);
-});
-return pass;
-}).filter(pass=>{
-
-if(pass.recordTrace)return true;
-return pass.gatherers.length>0;
-});
-return filteredPasses;
-}
-
-
-
-
-
-
-
-
-
-static requireAudits(audits,configPath){
-if(!audits){
-return null;
-}
-
-const coreList=Runner.getAuditList();
-return audits.map(pathOrAuditClass=>{
-let AuditClass;
-if(typeof pathOrAuditClass==='string'){
-const path=pathOrAuditClass;
-
-const coreAudit=coreList.find(a=>a===`${path}.js`);
-let requirePath=`../audits/${path}`;
-if(!coreAudit){
-
-requirePath=Runner.resolvePlugin(path,configPath,'audit');
-}
-AuditClass=require(requirePath);
-assertValidAudit(AuditClass,path);
-}else{
-AuditClass=pathOrAuditClass;
-assertValidAudit(AuditClass);
-}
-
-return AuditClass;
-});
-}
-
-
-get configDir(){
-return this._configDir;
-}
-
-
-get passes(){
-return this._passes;
-}
-
-
-get audits(){
-return this._audits;
-}
-
-
-get auditResults(){
-return this._auditResults;
-}
-
-
-get artifacts(){
-return this._artifacts;
-}
-
-
-get aggregations(){
-return this._aggregations;
-}}
-
-
-module.exports=Config;
-
-}).call(this,"/../lighthouse-core/config");
-},{"../audits/audit":3,"../gather/gather-runner":12,"../lib/log":19,"../lib/network-recorder":21,"../runner":32,"./default.json":6,"path":203}],6:[function(require,module,exports){
-module.exports={
-"passes":[{
-"passName":"defaultPass",
-"recordNetwork":true,
-"recordTrace":true,
-"pauseBeforeTraceEndMs":500,
-"useThrottling":true,
-"gatherers":[
-"url",
-"https",
-"viewport",
-"theme-color",
-"manifest",
-"accessibility",
-"image-usage",
-"content-width"]},
-
-
-{
-"passName":"offlinePass",
-"recordNetwork":true,
-"useThrottling":false,
-"gatherers":[
-"service-worker",
-"offline"]},
-
-
-{
-"passName":"redirectPass",
-"useThrottling":false,
-"gatherers":[
-"http-redirect",
-"html-without-javascript"]},
-
-{
-"passName":"dbw",
-"recordNetwork":true,
-"useThrottling":false,
-"gatherers":[
-"chrome-console-messages",
-"styles",
-"css-usage",
-"dobetterweb/all-event-listeners",
-"dobetterweb/anchors-with-no-rel-noopener",
-"dobetterweb/appcache",
-"dobetterweb/console-time-usage",
-"dobetterweb/datenow",
-"dobetterweb/document-write",
-"dobetterweb/geolocation-on-start",
-"dobetterweb/notification-on-start",
-"dobetterweb/domstats",
-"dobetterweb/optimized-images",
-"dobetterweb/tags-blocking-first-paint",
-"dobetterweb/websql"]}],
-
-
-
-"audits":[
-"is-on-https",
-"redirects-http",
-"service-worker",
-"works-offline",
-"viewport",
-"manifest-display",
-"without-javascript",
-"first-meaningful-paint",
-"speed-index-metric",
-"estimated-input-latency",
-"time-to-interactive",
-"user-timings",
-"screenshots",
-"critical-request-chains",
-"manifest-exists",
-"manifest-background-color",
-"manifest-theme-color",
-"manifest-icons-min-192",
-"manifest-icons-min-144",
-"manifest-name",
-"manifest-short-name",
-"manifest-short-name-length",
-"manifest-start-url",
-"theme-color-meta",
-"content-width",
-"deprecations",
-"accessibility/aria-allowed-attr",
-"accessibility/aria-required-attr",
-"accessibility/aria-valid-attr-value",
-"accessibility/aria-valid-attr",
-"accessibility/color-contrast",
-"accessibility/image-alt",
-"accessibility/label",
-"accessibility/tabindex",
-"byte-efficiency/total-byte-weight",
-"byte-efficiency/unused-css-rules",
-"byte-efficiency/uses-optimized-images",
-"byte-efficiency/uses-responsive-images",
-"dobetterweb/appcache-manifest",
-"dobetterweb/dom-size",
-"dobetterweb/external-anchors-use-rel-noopener",
-"dobetterweb/geolocation-on-start",
-"dobetterweb/link-blocking-first-paint",
-"dobetterweb/no-console-time",
-"dobetterweb/no-datenow",
-"dobetterweb/no-document-write",
-"dobetterweb/no-mutation-events",
-"dobetterweb/no-old-flexbox",
-"dobetterweb/no-websql",
-"dobetterweb/notification-on-start",
-"dobetterweb/script-blocking-first-paint",
-"dobetterweb/uses-http2",
-"dobetterweb/uses-passive-event-listeners"],
-
-
-"aggregations":[{
-"name":"Progressive Web App",
-"id":"pwa",
-"description":"These audits validate the aspects of a Progressive Web App. They are a subset of the [PWA Checklist](https://developers.google.com/web/progressive-web-apps/checklist).",
-"scored":true,
-"categorizable":true,
-"items":[{
-"name":"App can load on offline/flaky connections",
-"description":"Ensuring your web app can respond when the network connection is unavailable or flaky is critical to providing your users a good experience. This is achieved through use of a [Service Worker](https://developers.google.com/web/fundamentals/primers/service-worker/).",
-"audits":{
-"service-worker":{
-"expectedValue":true,
-"weight":1},
-
-"works-offline":{
-"expectedValue":true,
-"weight":1}}},
-
-
-{
-"name":"Page load performance is fast",
-"description":"Users notice if sites and apps don't perform well. These top-level metrics capture the most important perceived performance concerns.",
-"audits":{
-"first-meaningful-paint":{
-"expectedValue":100,
-"weight":1},
-
-"speed-index-metric":{
-"expectedValue":100,
-"weight":1},
-
-"estimated-input-latency":{
-"expectedValue":100,
-"weight":1},
-
-"time-to-interactive":{
-"expectedValue":100,
-"weight":1}}},
-
-
-{
-"name":"Site is progressively enhanced",
-"description":"Progressive enhancement means that everyone can access the basic content and functionality of a page in any browser, and those without certain browser features may receive a reduced but still functional experience.",
-"audits":{
-"without-javascript":{
-"expectedValue":true,
-"weight":1}}},
-
-
-{
-"name":"Network connection is secure",
-"description":"Security is an important part of the web for both developers and users. Moving forward, Transport Layer Security (TLS) support will be required for many APIs.",
-"audits":{
-"is-on-https":{
-"expectedValue":true,
-"weight":1},
-
-"redirects-http":{
-"expectedValue":true,
-"weight":1}}},
-
-
-{
-"name":"User can be prompted to Add to Homescreen",
-"description":"While users can manually add your site to their homescreen in the browser menu, the [prompt (aka app install banner)](https://developers.google.com/web/updates/2015/03/increasing-engagement-with-app-install-banners-in-chrome-for-android) will proactively prompt the user to install the app if the below requirements are met and the user has visited your site at least twice (with at least five minutes between visits).",
-"see":"https://github.com/GoogleChrome/lighthouse/issues/23",
-"audits":{
-"service-worker":{
-"expectedValue":true,
-"weight":1},
-
-"manifest-exists":{
-"expectedValue":true,
-"weight":1},
-
-"manifest-start-url":{
-"expectedValue":true,
-"weight":1},
-
-"manifest-icons-min-144":{
-"expectedValue":true,
-"weight":1},
-
-"manifest-short-name":{
-"expectedValue":true,
-"weight":1}}},
-
-
-{
-"name":"Installed web app will launch with custom splash screen",
-"description":"A default splash screen will be constructed, but meeting these requirements guarantee a high-quality and customizable [splash screen](https://developers.google.com/web/updates/2015/10/splashscreen) the user sees between tapping the home screen icon and your app's first paint.",
-"see":"https://github.com/GoogleChrome/lighthouse/issues/24",
-"audits":{
-"manifest-exists":{
-"expectedValue":true,
-"weight":1},
-
-"manifest-name":{
-"expectedValue":true,
-"weight":1},
-
-"manifest-background-color":{
-"expectedValue":true,
-"weight":1},
-
-"manifest-theme-color":{
-"expectedValue":true,
-"weight":1},
-
-"manifest-icons-min-192":{
-"expectedValue":true,
-"weight":1}}},
-
-
-{
-"name":"Address bar matches brand colors",
-"description":"The browser address bar can be themed to match your site. A `theme-color` [meta tag](https://developers.google.com/web/updates/2014/11/Support-for-theme-color-in-Chrome-39-for-Android) will upgrade the address bar when a user browses the site, and the [manifest theme-color](https://developers.google.com/web/updates/2015/08/using-manifest-to-set-sitewide-theme-color) will apply the same theme site-wide once it's been added to homescreen.",
-"audits":{
-"manifest-exists":{
-"expectedValue":true,
-"weight":1},
-
-"theme-color-meta":{
-"expectedValue":true,
-"weight":1},
-
-"manifest-theme-color":{
-"expectedValue":true,
-"weight":1}}},
-
-
-{
-"name":"Design is mobile-friendly",
-"description":"Users increasingly experience your app on mobile devices, so it's important to ensure that the experience can adapt to smaller screens.",
-"audits":{
-"viewport":{
-"expectedValue":true,
-"weight":1},
-
-"content-width":{
-"expectedValue":true,
-"weight":1}}}]},
-
-
-
-{
-"name":"Best Practices",
-"id":"bp",
-"description":"We've compiled some recommendations for modernizing your web app and avoiding performance pitfalls. These audits do not affect your score but are worth a look.",
-"scored":false,
-"categorizable":true,
-"items":[{
-"name":"Using modern offline features",
-"audits":{
-"appcache-manifest":{
-"expectedValue":true,
-"weight":1},
-
-"no-websql":{
-"expectedValue":true,
-"weight":1}}},
-
-
-{
-"name":"Using modern protocols",
-"audits":{
-"is-on-https":{
-"expectedValue":true,
-"weight":1},
-
-"uses-http2":{
-"expectedValue":true,
-"description":"Resources made by this application should be severed over HTTP/2 for improved performance.",
-"weight":1}}},
-
-
-{
-"name":"Using modern CSS features",
-"audits":{
-"no-old-flexbox":{
-"expectedValue":true,
-"weight":1}}},
-
-
-{
-"name":"Using modern JavaScript features",
-"audits":{
-"uses-passive-event-listeners":{
-"expectedValue":true,
-"weight":1},
-
-"no-mutation-events":{
-"expectedValue":true,
-"weight":1}}},
-
-
-{
-"name":"Avoiding APIs that harm the user experience",
-"audits":{
-"no-document-write":{
-"expectedValue":true,
-"weight":1},
-
-"external-anchors-use-rel-noopener":{
-"expectedValue":true,
-"weight":1},
-
-"geolocation-on-start":{
-"expectedValue":true,
-"weight":1},
-
-"notification-on-start":{
-"expectedValue":true,
-"weight":1}}},
-
-
-{
-"name":"Avoiding deprecated APIs and browser interventions",
-"audits":{
-"deprecations":{
-"expectedValue":true,
-"weight":1}}},
-
-
-{
-"name":"Accessibility",
-"audits":{
-"aria-allowed-attr":{
-"expectedValue":true,
-"weight":1},
-
-"aria-required-attr":{
-"expectedValue":true,
-"weight":1},
-
-"aria-valid-attr":{
-"expectedValue":true,
-"weight":1},
-
-"aria-valid-attr-value":{
-"expectedValue":true,
-"weight":1},
-
-"color-contrast":{
-"expectedValue":true,
-"weight":1},
-
-"image-alt":{
-"expectedValue":true,
-"weight":1},
-
-"label":{
-"expectedValue":true,
-"weight":1},
-
-"tabindex":{
-"expectedValue":true,
-"weight":1}}},
-
-
-{
-"name":"Other",
-"audits":{
-"manifest-short-name-length":{
-"expectedValue":true,
-"weight":1},
-
-"manifest-display":{
-"expectedValue":true,
-"weight":1}}}]},
-
-
-
-{
-"name":"Performance",
-"id":"perf",
-"description":"These encapsulate your app's performance.",
-"scored":false,
-"categorizable":false,
-"items":[{
-"audits":{
-"unused-css-rules":{
-"expectedValue":true,
-"weight":1},
-
-"uses-optimized-images":{
-"expectedValue":true,
-"weight":1},
-
-"uses-responsive-images":{
-"expectedValue":true,
-"weight":1},
-
-"critical-request-chains":{
-"expectedValue":true,
-"weight":1},
-
-"link-blocking-first-paint":{
-"expectedValue":true,
-"weight":1},
-
-"script-blocking-first-paint":{
-"expectedValue":true,
-"weight":1},
-
-"total-byte-weight":{
-"expectedValue":100,
-"weight":1},
-
-"dom-size":{
-"expectedValue":100,
-"weight":1},
-
-"user-timings":{
-"expectedValue":true,
-"weight":1}}}]},
-
-
-
-{
-"name":"Fancier stuff",
-"id":"fancy",
-"description":"A list of newer features that you could be using in your app. These audits do not affect your score and are just suggestions.",
-"scored":false,
-"categorizable":true,
-"additional":true,
-"items":[{
-"name":"New JavaScript features",
-"audits":{
-"no-datenow":{
-"expectedValue":true,
-"weight":1},
-
-"no-console-time":{
-"expectedValue":true,
-"weight":1}}}]}]};
-
-
-
-
-
-
 },{}],7:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-const EventEmitter=require('events').EventEmitter;
-const log=require('../../lib/log.js');
+const EventEmitter = require('events').EventEmitter;
+const log = require('../../lib/log.js');
 
-class Connection{
+class Connection {
 
-constructor(){
-this._lastCommandId=0;
+  constructor() {
+    this._lastCommandId = 0;
+    /** @type {!Map<number, {resolve: function(*), reject: function(*), method: string}>}*/
+    this._callbacks = new Map();
+    this._eventEmitter = new EventEmitter();
+  }
 
-this._callbacks=new Map();
-this._eventEmitter=new EventEmitter();
+  /**
+   * @return {!Promise}
+   */
+  connect() {
+    return Promise.reject(new Error('Not implemented'));
+  }
+
+  /**
+   * @return {!Promise}
+   */
+  disconnect() {
+    return Promise.reject(new Error('Not implemented'));
+  }
+
+  /**
+   * Call protocol methods
+   * @param {!string} method
+   * @param {!Object} params
+   * @return {!Promise}
+   */
+  sendCommand(method, params = {}) {
+    log.formatProtocol('method => browser', {method, params}, 'verbose');
+    const id = ++this._lastCommandId;
+    const message = JSON.stringify({id, method, params});
+    this.sendRawMessage(message);
+    return new Promise((resolve, reject) => {
+      this._callbacks.set(id, {resolve, reject, method});
+    });
+  }
+
+  /**
+   * Bind listeners for connection events
+   * @param {!string} eventName
+   * @param {function(...)} cb
+   */
+  on(eventName, cb) {
+    if (eventName !== 'notification') {
+      throw new Error('Only supports "notification" events');
+    }
+    this._eventEmitter.on(eventName, cb);
+  }
+
+  /* eslint-disable no-unused-vars */
+
+  /**
+   * @param {string} message
+   * @return {!Promise}
+   * @protected
+   */
+  sendRawMessage(message) {
+    return Promise.reject(new Error('Not implemented'));
+  }
+
+  /* eslint-enable no-unused-vars */
+
+  /**
+   * @param {string} message
+   * @return {!Promise}
+   * @protected
+   */
+  handleRawMessage(message) {
+    const object = JSON.parse(message);
+    // Remote debugging protocol is JSON RPC 2.0 compiant. In terms of that transport,
+    // responses to the commands carry "id" property, while notifications do not.
+    if (object.id) {
+      const callback = this._callbacks.get(object.id);
+      this._callbacks.delete(object.id);
+
+      return callback.resolve(Promise.resolve().then(_ => {
+        if (object.error) {
+          log.formatProtocol('method <= browser ERR', {method: callback.method}, 'error');
+          throw new Error(`Protocol error (${callback.method}): ${object.error.message}`);
+        }
+
+        log.formatProtocol('method <= browser OK',
+          {method: callback.method, params: object.result}, 'verbose');
+        return object.result;
+      }));
+    }
+
+    log.formatProtocol('<= event',
+        {method: object.method, params: object.params}, 'verbose');
+    this.emitNotification(object.method, object.params);
+  }
+
+  /**
+   * @param {!string} method
+   * @param {!Object} params
+   * @protected
+   */
+  emitNotification(method, params) {
+    this._eventEmitter.emit('notification', {method, params});
+  }
+
+  /**
+   * @protected
+   */
+  dispose() {
+    this._eventEmitter.removeAllListeners();
+    this._eventEmitter = null;
+  }
 }
 
+module.exports = Connection;
 
-
-
-connect(){
-return Promise.reject(new Error('Not implemented'));
-}
-
-
-
-
-disconnect(){
-return Promise.reject(new Error('Not implemented'));
-}
-
-
-
-
-
-
-
-sendCommand(method,params={}){
-log.formatProtocol('method => browser',{method,params},'verbose');
-const id=++this._lastCommandId;
-const message=JSON.stringify({id,method,params});
-this.sendRawMessage(message);
-return new Promise((resolve,reject)=>{
-this._callbacks.set(id,{resolve,reject,method});
-});
-}
-
-
-
-
-
-
-on(eventName,cb){
-if(eventName!=='notification'){
-throw new Error('Only supports "notification" events');
-}
-this._eventEmitter.on(eventName,cb);
-}
-
-
-
-
-
-
-
-
-sendRawMessage(message){
-return Promise.reject(new Error('Not implemented'));
-}
-
-
-
-
-
-
-
-
-handleRawMessage(message){
-const object=JSON.parse(message);
-
-
-if(object.id){
-const callback=this._callbacks.get(object.id);
-this._callbacks.delete(object.id);
-
-return callback.resolve(Promise.resolve().then(_=>{
-if(object.error){
-log.formatProtocol('method <= browser ERR',{method:callback.method},'error');
-throw new Error(`Protocol error (${callback.method}): ${object.error.message}`);
-}
-
-log.formatProtocol('method <= browser OK',
-{method:callback.method,params:object.result},'verbose');
-return object.result;
-}));
-}
-
-log.formatProtocol('<= event',
-{method:object.method,params:object.params},'verbose');
-this.emitNotification(object.method,object.params);
-}
-
-
-
-
-
-
-emitNotification(method,params){
-this._eventEmitter.emit('notification',{method,params});
-}
-
-
-
-
-dispose(){
-this._eventEmitter.removeAllListeners();
-this._eventEmitter=null;
-}}
-
-
-module.exports=Connection;
-
-},{"../../lib/log.js":19,"events":200}],8:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"../../lib/log.js":19,"events":207}],8:[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-const Connection=require('./connection.js');
-const log=require('../../lib/log.js');
+const Connection = require('./connection.js');
+const log = require('../../lib/log.js');
 
+/* globals chrome */
 
+class ExtensionConnection extends Connection {
 
-class ExtensionConnection extends Connection{
+  constructor() {
+    super();
+    this._tabId = null;
 
-constructor(){
-super();
-this._tabId=null;
+    this._onEvent = this._onEvent.bind(this);
+    this._onUnexpectedDetach = this._onUnexpectedDetach.bind(this);
+  }
 
-this._onEvent=this._onEvent.bind(this);
-this._onUnexpectedDetach=this._onUnexpectedDetach.bind(this);
+  _onEvent(source, method, params) {
+    // log events received
+    log.log('<=', method, params);
+    this.emitNotification(method, params);
+  }
+
+  _onUnexpectedDetach(debuggee, detachReason) {
+    this._detachCleanup();
+    throw new Error('Lighthouse detached from browser: ' + detachReason);
+  }
+
+  _detachCleanup() {
+    this._tabId = null;
+    chrome.debugger.onEvent.removeListener(this._onEvent);
+    chrome.debugger.onDetach.removeListener(this._onUnexpectedDetach);
+    this.dispose();
+  }
+
+  /**
+   * @override
+   * @return {!Promise}
+   */
+  connect() {
+    if (this._tabId !== null) {
+      return Promise.resolve();
+    }
+
+    return this._queryCurrentTab()
+      .then(tab => {
+        const tabId = this._tabId = tab.id;
+        chrome.debugger.onEvent.addListener(this._onEvent);
+        chrome.debugger.onDetach.addListener(this._onUnexpectedDetach);
+
+        return new Promise((resolve, reject) => {
+          chrome.debugger.attach({tabId}, '1.1', _ => {
+            if (chrome.runtime.lastError) {
+              return reject(new Error(chrome.runtime.lastError.message));
+            }
+            resolve(tabId);
+          });
+        });
+      });
+  }
+
+  /**
+   * @override
+   * @return {!Promise}
+   */
+  disconnect() {
+    if (this._tabId === null) {
+      log.warn('ExtensionConnection', 'disconnect() was called without an established connection.');
+      return Promise.resolve();
+    }
+
+    const tabId = this._tabId;
+    return new Promise((resolve, reject) => {
+      chrome.debugger.detach({tabId}, _ => {
+        if (chrome.runtime.lastError) {
+          return reject(new Error(chrome.runtime.lastError.message));
+        }
+        // Reload the target page to restore its state.
+        chrome.tabs.reload(tabId);
+        resolve();
+      });
+    }).then(_ => this._detachCleanup());
+  }
+
+  /**
+   * @override
+   * @param {!string} command
+   * @param {!Object} params
+   * @return {!Promise}
+   */
+  sendCommand(command, params) {
+    return new Promise((resolve, reject) => {
+      log.formatProtocol('method => browser', {method: command, params: params}, 'verbose');
+      if (!this._tabId) {
+        log.error('ExtensionConnection', 'No tabId set for sendCommand');
+      }
+
+      chrome.debugger.sendCommand({tabId: this._tabId}, command, params, result => {
+        if (chrome.runtime.lastError) {
+          // The error from the extension has a `message` property that is the
+          // stringified version of the actual protocol error object.
+          const message = chrome.runtime.lastError.message;
+          let errorMessage;
+          try {
+            errorMessage = JSON.parse(message).message;
+          } catch (e) {}
+          errorMessage = errorMessage || 'Unknown debugger protocol error.';
+
+          log.formatProtocol('method <= browser ERR', {method: command}, 'error');
+          return reject(new Error(`Protocol error (${command}): ${errorMessage}`));
+        }
+
+        log.formatProtocol('method <= browser OK', {method: command, params: result}, 'verbose');
+        resolve(result);
+      });
+    });
+  }
+
+  _queryCurrentTab() {
+    return new Promise((resolve, reject) => {
+      const queryOpts = {
+        active: true,
+        currentWindow: true
+      };
+
+      chrome.tabs.query(queryOpts, (tabs => {
+        if (chrome.runtime.lastError) {
+          return reject(chrome.runtime.lastError);
+        }
+        if (tabs.length === 0) {
+          const message = 'Couldn\'t resolve current tab. Please file a bug.';
+          return reject(new Error(message));
+        }
+        if (tabs.length > 1) {
+          log.warn('ExtensionConnection', '_queryCurrentTab returned multiple tabs');
+        }
+        resolve(tabs[0]);
+      }));
+    });
+  }
+
+  /**
+   * Used by lighthouse-background to kick off the run on the current page
+   */
+  getCurrentTabURL() {
+    return this._queryCurrentTab().then(tab => {
+      if (!tab.url) {
+        log.error('ExtensionConnection', 'getCurrentTabURL returned empty string', tab);
+      }
+      return tab.url;
+    });
+  }
 }
 
-_onEvent(source,method,params){
-
-log.log('<=',method,params);
-this.emitNotification(method,params);
-}
-
-_onUnexpectedDetach(debuggee,detachReason){
-this._detachCleanup();
-throw new Error('Lighthouse detached from browser: '+detachReason);
-}
-
-_detachCleanup(){
-this._tabId=null;
-chrome.debugger.onEvent.removeListener(this._onEvent);
-chrome.debugger.onDetach.removeListener(this._onUnexpectedDetach);
-this.dispose();
-}
-
-
-
-
-
-connect(){
-if(this._tabId!==null){
-return Promise.resolve();
-}
-
-return this._queryCurrentTab().
-then(tab=>{
-const tabId=this._tabId=tab.id;
-chrome.debugger.onEvent.addListener(this._onEvent);
-chrome.debugger.onDetach.addListener(this._onUnexpectedDetach);
-
-return new Promise((resolve,reject)=>{
-chrome.debugger.attach({tabId},'1.1',_=>{
-if(chrome.runtime.lastError){
-return reject(new Error(chrome.runtime.lastError.message));
-}
-resolve(tabId);
-});
-});
-});
-}
-
-
-
-
-
-disconnect(){
-if(this._tabId===null){
-log.warn('ExtensionConnection','disconnect() was called without an established connection.');
-return Promise.resolve();
-}
-
-const tabId=this._tabId;
-return new Promise((resolve,reject)=>{
-chrome.debugger.detach({tabId},_=>{
-if(chrome.runtime.lastError){
-return reject(new Error(chrome.runtime.lastError.message));
-}
-
-chrome.tabs.reload(tabId);
-resolve();
-});
-}).then(_=>this._detachCleanup());
-}
-
-
-
-
-
-
-
-sendCommand(command,params){
-return new Promise((resolve,reject)=>{
-log.formatProtocol('method => browser',{method:command,params:params},'verbose');
-if(!this._tabId){
-log.error('ExtensionConnection','No tabId set for sendCommand');
-}
-
-chrome.debugger.sendCommand({tabId:this._tabId},command,params,result=>{
-if(chrome.runtime.lastError){
-
-
-const message=chrome.runtime.lastError.message;
-let errorMessage;
-try{
-errorMessage=JSON.parse(message).message;
-}catch(e){}
-errorMessage=errorMessage||'Unknown debugger protocol error.';
-
-log.formatProtocol('method <= browser ERR',{method:command},'error');
-return reject(new Error(`Protocol error (${command}): ${errorMessage}`));
-}
-
-log.formatProtocol('method <= browser OK',{method:command,params:result},'verbose');
-resolve(result);
-});
-});
-}
-
-_queryCurrentTab(){
-return new Promise((resolve,reject)=>{
-const queryOpts={
-active:true,
-currentWindow:true};
-
-
-chrome.tabs.query(queryOpts,tabs=>{
-if(chrome.runtime.lastError){
-return reject(chrome.runtime.lastError);
-}
-if(tabs.length===0){
-const message='Couldn\'t resolve current tab. Please file a bug.';
-return reject(new Error(message));
-}
-if(tabs.length>1){
-log.warn('ExtensionConnection','_queryCurrentTab returned multiple tabs');
-}
-resolve(tabs[0]);
-});
-});
-}
-
-
-
-
-getCurrentTabURL(){
-return this._queryCurrentTab().then(tab=>{
-if(!tab.url){
-log.error('ExtensionConnection','getCurrentTabURL returned empty string',tab);
-}
-return tab.url;
-});
-}}
-
-
-module.exports=ExtensionConnection;
+module.exports = ExtensionConnection;
 
 },{"../../lib/log.js":19,"./connection.js":7}],9:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-const Connection=require('./connection.js');
+const Connection = require('./connection.js');
 
+/* eslint-disable no-unused-vars */
 
+/**
+ * @interface
+ */
+class Port {
+  /**
+   * @param {!string} eventName, 'message', 'close'
+   * @param {function(string|undefined)} cb
+   */
+  on(eventName, cb) { }
 
+  /**
+   * @param {string} message
+   */
+  send(message) { }
 
-
-
-class Port{
-
-
-
-
-on(eventName,cb){}
-
-
-
-
-send(message){}
-
-close(){}}
-
-
-
-
-class RawConnection extends Connection{
-constructor(port){
-super();
-this._port=port;
-this._port.on('message',this.handleRawMessage.bind(this));
-this._port.on('close',this.dispose.bind(this));
+  close() { }
 }
 
+/* eslint-enable no-unused-vars */
 
+class RawConnection extends Connection {
+  constructor(port) {
+    super();
+    this._port = port;
+    this._port.on('message', this.handleRawMessage.bind(this));
+    this._port.on('close', this.dispose.bind(this));
+  }
 
+  /**
+   * @override
+   * @return {!Promise}
+   */
+  connect() {
+    return Promise.resolve();
+  }
 
+  /**
+   * @override
+   */
+  disconnect() {
+    this._port.close();
+    return Promise.resolve();
+  }
 
-connect(){
-return Promise.resolve();
+  /**
+   * @override
+   * @param {string} message
+   */
+  sendRawMessage(message) {
+    this._port.send(message);
+  }
 }
 
-
-
-
-disconnect(){
-this._port.close();
-return Promise.resolve();
-}
-
-
-
-
-
-sendRawMessage(message){
-this._port.send(message);
-}}
-
-
-module.exports=RawConnection;
+module.exports = RawConnection;
 
 },{"./connection.js":7}],10:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
+/**
+ * @fileoverview This class saves all protocol messages whose method match a particular
+ *    regex filter. Used when saving assets for later analysis by another tool such as
+ *    Webpagetest.
+ */
+class DevtoolsLog {
+  /**
+   * @param {RegExp=} regexFilter
+   */
+  constructor(regexFilter) {
+    this._filter = regexFilter;
+    this._messages = [];
+    this._isRecording = false;
+  }
 
+  /**
+   * @return {!Array<{method: string, params: !Object}>}
+   */
+  get messages() {
+    return this._messages;
+  }
 
+  reset() {
+    this._messages = [];
+  }
 
+  beginRecording() {
+    this._isRecording = true;
+  }
 
+  endRecording() {
+    this._isRecording = false;
+  }
 
-class DevtoolsLog{
-
-
-
-constructor(regexFilter){
-this._filter=regexFilter;
-this._messages=[];
-this._isRecording=false;
+  /**
+   * Records a message if method matches filter and recording has been started.
+   * @param {{method: string, params: !Object}} message
+   */
+  record(message) {
+    if (this._isRecording && (!this._filter || this._filter.test(message.method))) {
+      this._messages.push(message);
+    }
+  }
 }
 
-
-
-
-get messages(){
-return this._messages;
-}
-
-reset(){
-this._messages=[];
-}
-
-beginRecording(){
-this._isRecording=true;
-}
-
-endRecording(){
-this._isRecording=false;
-}
-
-
-
-
-
-record(message){
-if(this._isRecording&&(!this._filter||this._filter.test(message.method))){
-this._messages.push(message);
-}
-}}
-
-
-module.exports=DevtoolsLog;
+module.exports = DevtoolsLog;
 
 },{}],11:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-const NetworkRecorder=require('../lib/network-recorder');
-const emulation=require('../lib/emulation');
-const Element=require('../lib/element');
-const EventEmitter=require('events').EventEmitter;
-const URL=require('../lib/url-shim');
+const NetworkRecorder = require('../lib/network-recorder');
+const emulation = require('../lib/emulation');
+const Element = require('../lib/element');
+const EventEmitter = require('events').EventEmitter;
+const URL = require('../lib/url-shim');
 
-const log=require('../lib/log.js');
-const DevtoolsLog=require('./devtools-log');
+const log = require('../lib/log.js');
+const DevtoolsLog = require('./devtools-log');
 
-const PAUSE_AFTER_LOAD=500;
+const PAUSE_AFTER_LOAD = 500;
 
-class Driver{
-static get MAX_WAIT_FOR_FULLY_LOADED(){
-return 25*1000;
-}
+const _uniq = arr => Array.from(new Set(arr));
 
+class Driver {
+  static get MAX_WAIT_FOR_FULLY_LOADED() {
+    return 25 * 1000;
+  }
 
+  /**
+   * @param {!Connection} connection
+   */
+  constructor(connection) {
+    this._traceEvents = [];
+    this._traceCategories = Driver.traceCategories;
+    this._eventEmitter = new EventEmitter();
+    this._connection = connection;
+    // currently only used by WPT where just Page and Network are needed
+    this._devtoolsLog = new DevtoolsLog(/^(Page|Network)\./);
+    connection.on('notification', event => {
+      this._devtoolsLog.record(event);
+      this._eventEmitter.emit(event.method, event.params);
+    });
+    this.online = true;
+    this._domainEnabledCounts = new Map();
+  }
 
+  static get traceCategories() {
+    return [
+      '-*', // exclude default
+      'toplevel',
+      'blink.console',
+      'blink.user_timing',
+      'benchmark',
+      'loading',
+      'latencyInfo',
+      'devtools.timeline',
+      'disabled-by-default-devtools.timeline',
+      'disabled-by-default-devtools.timeline.frame',
+      'disabled-by-default-devtools.timeline.stack',
+      // Flipped off until bugs.chromium.org/p/v8/issues/detail?id=5820 is fixed in Stable
+      // 'disabled-by-default-v8.cpu_profiler',
+      // 'disabled-by-default-v8.cpu_profiler.hires',
+      'disabled-by-default-devtools.screenshot'
+    ];
+  }
 
-constructor(connection){
-this._traceEvents=[];
-this._traceCategories=Driver.traceCategories;
-this._eventEmitter=new EventEmitter();
-this._connection=connection;
+  /**
+   * @return {!Array<{method: string, params: !Object}>}
+   */
+  get devtoolsLog() {
+    return this._devtoolsLog.messages;
+  }
 
-this._devtoolsLog=new DevtoolsLog(/^(Page|Network)\./);
-connection.on('notification',event=>{
-this._devtoolsLog.record(event);
-this._eventEmitter.emit(event.method,event.params);
-});
-this.online=true;
-this._domainEnabledCounts=new Map();
-}
+  /**
+   * @return {!Promise<string>}
+   */
+  getUserAgent() {
+    return this.evaluateAsync('navigator.userAgent');
+  }
 
-static get traceCategories(){
-return[
-'-*',
-'toplevel',
-'blink.console',
-'blink.user_timing',
-'benchmark',
-'loading',
-'latencyInfo',
-'devtools.timeline',
-'disabled-by-default-devtools.timeline',
-'disabled-by-default-devtools.timeline.frame',
-'disabled-by-default-devtools.timeline.stack',
+  /**
+   * @return {!Promise<null>}
+   */
+  connect() {
+    return this._connection.connect();
+  }
 
+  disconnect() {
+    return this._connection.disconnect();
+  }
 
+  /**
+   * Bind listeners for protocol events
+   * @param {!string} eventName
+   * @param {function(...)} cb
+   */
+  on(eventName, cb) {
+    if (this._eventEmitter === null) {
+      throw new Error('connect() must be called before attempting to listen to events.');
+    }
 
-'disabled-by-default-devtools.screenshot'];
+    // log event listeners being bound
+    log.formatProtocol('listen for event =>', {method: eventName}, 'verbose');
+    this._eventEmitter.on(eventName, cb);
+  }
 
-}
+  /**
+   * Bind a one-time listener for protocol events. Listener is removed once it
+   * has been called.
+   * @param {!string} eventName
+   * @param {function(...)} cb
+   */
+  once(eventName, cb) {
+    if (this._eventEmitter === null) {
+      throw new Error('connect() must be called before attempting to listen to events.');
+    }
+    // log event listeners being bound
+    log.formatProtocol('listen once for event =>', {method: eventName}, 'verbose');
+    this._eventEmitter.once(eventName, cb);
+  }
 
+  /**
+   * Unbind event listeners
+   * @param {!string} eventName
+   * @param {function(...)} cb
+   */
+  off(eventName, cb) {
+    if (this._eventEmitter === null) {
+      throw new Error('connect() must be called before attempting to remove an event listener.');
+    }
 
+    this._eventEmitter.removeListener(eventName, cb);
+  }
 
+  /**
+   * Debounce enabling or disabling domains to prevent driver users from
+   * stomping on each other. Maintains an internal count of the times a domain
+   * has been enabled. Returns false if the command would have no effect (domain
+   * is already enabled or disabled), or if command would interfere with another
+   * user of that domain (e.g. two gatherers have enabled a domain, both need to
+   * disable it for it to be disabled). Returns true otherwise.
+   * @param {string} domain
+   * @param {boolean} enable
+   * @return {boolean}
+   * @private
+   */
+  _shouldToggleDomain(domain, enable) {
+    const enabledCount = this._domainEnabledCounts.get(domain) || 0;
+    const newCount = enabledCount + (enable ? 1 : -1);
+    this._domainEnabledCounts.set(domain, Math.max(0, newCount));
 
-get devtoolsLog(){
-return this._devtoolsLog.messages;
-}
+    // Switching to enabled or disabled, respectively.
+    if ((enable && newCount === 1) || (!enable && newCount === 0)) {
+      log.verbose('Driver', `${domain}.${enable ? 'enable' : 'disable'}`);
+      return true;
+    } else {
+      if (newCount < 0) {
+        log.error('Driver', `Attempted to disable domain '${domain}' when already disabled.`);
+      }
+      return false;
+    }
+  }
 
-
-
-
-connect(){
-return this._connection.connect();
-}
-
-disconnect(){
-return this._connection.disconnect();
-}
-
-
-
-
-
-
-on(eventName,cb){
-if(this._eventEmitter===null){
-throw new Error('connect() must be called before attempting to listen to events.');
-}
-
-
-log.formatProtocol('listen for event =>',{method:eventName},'verbose');
-this._eventEmitter.on(eventName,cb);
-}
-
-
-
-
-
-
-
-once(eventName,cb){
-if(this._eventEmitter===null){
-throw new Error('connect() must be called before attempting to listen to events.');
-}
-
-log.formatProtocol('listen once for event =>',{method:eventName},'verbose');
-this._eventEmitter.once(eventName,cb);
-}
-
-
-
-
-
-
-off(eventName,cb){
-if(this._eventEmitter===null){
-throw new Error('connect() must be called before attempting to remove an event listener.');
-}
-
-this._eventEmitter.removeListener(eventName,cb);
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-_shouldToggleDomain(domain,enable){
-const enabledCount=this._domainEnabledCounts.get(domain)||0;
-const newCount=enabledCount+(enable?1:-1);
-this._domainEnabledCounts.set(domain,Math.max(0,newCount));
-
-
-if(enable&&newCount===1||!enable&&newCount===0){
-log.verbose('Driver',`${domain}.${enable?'enable':'disable'}`);
-return true;
-}else{
-if(newCount<0){
-log.error('Driver',`Attempted to disable domain '${domain}' when already disabled.`);
-}
-return false;
-}
-}
-
-
-
-
-
-
-
-sendCommand(method,params){
-const domainCommand=/^(\w+)\.(enable|disable)$/.exec(method);
-if(domainCommand){
-const enable=domainCommand[2]==='enable';
-if(!this._shouldToggleDomain(domainCommand[1],enable)){
-return Promise.resolve();
-}
-}
-
-return this._connection.sendCommand(method,params);
-}
-
-
-
-
-
-
-isDomainEnabled(domain){
-
-return!!this._domainEnabledCounts.get(domain);
-}
-
-
-
-
-
-
-evaluateScriptOnLoad(scriptSource){
-return this.sendCommand('Page.addScriptToEvaluateOnLoad',{
-scriptSource});
-
-}
-
-
-
-
-
-
-
-evaluateAsync(expression){
-return new Promise((resolve,reject)=>{
-
-const asyncTimeout=setTimeout(
-_=>reject(new Error('The asynchronous expression exceeded the allotted time of 60s')),
-60000);
-
-
-this.sendCommand('Runtime.evaluate',{
+  /**
+   * Call protocol methods
+   * @param {!string} method
+   * @param {!Object} params
+   * @return {!Promise}
+   */
+  sendCommand(method, params) {
+    const domainCommand = /^(\w+)\.(enable|disable)$/.exec(method);
+    if (domainCommand) {
+      const enable = domainCommand[2] === 'enable';
+      if (!this._shouldToggleDomain(domainCommand[1], enable)) {
+        return Promise.resolve();
+      }
+    }
 
+    return this._connection.sendCommand(method, params);
+  }
 
+  /**
+   * Returns whether a domain is currently enabled.
+   * @param {string} domain
+   * @return {boolean}
+   */
+  isDomainEnabled(domain) {
+    // Defined, non-zero elements of the domains map are enabled.
+    return !!this._domainEnabledCounts.get(domain);
+  }
 
+  /**
+   * Add a script to run at load time of all future page loads.
+   * @param {string} scriptSource
+   * @return {!Promise<string>} Identifier of the added script.
+   */
+  evaluateScriptOnLoad(scriptSource) {
+    return this.sendCommand('Page.addScriptToEvaluateOnLoad', {
+      scriptSource
+    });
+  }
 
+  /**
+   * Evaluate an expression in the context of the current page.
+   * Returns a promise that resolves on the expression's value.
+   * @param {string} expression
+   * @return {!Promise<*>}
+   */
+  evaluateAsync(expression) {
+    return new Promise((resolve, reject) => {
+      // If this gets to 60s and it hasn't been resolved, reject the Promise.
+      const asyncTimeout = setTimeout(
+        (_ => reject(new Error('The asynchronous expression exceeded the allotted time of 60s'))),
+        60000
+      );
 
-expression:`(function wrapInNativePromise() {
+      this.sendCommand('Runtime.evaluate', {
+        // We need to explicitly wrap the raw expression for several purposes:
+        // 1. Ensure that the expression will be a native Promise and not a polyfill/non-Promise.
+        // 2. Ensure that errors in the expression are captured by the Promise.
+        // 3. Ensure that errors captured in the Promise are converted into plain-old JS Objects
+        //    so that they can be serialized properly b/c JSON.stringify(new Error('foo')) === '{}'
+        expression: `(function wrapInNativePromise() {
           const __nativePromise = window.__nativePromise || Promise;
           return new __nativePromise(function (resolve) {
             return __nativePromise.resolve()
@@ -9045,40339 +10753,48277 @@
               .then(resolve);
           });
         }())`,
-includeCommandLineAPI:true,
-awaitPromise:true,
-returnByValue:true}).
-then(result=>{
-clearTimeout(asyncTimeout);
-const value=result.result.value;
+        includeCommandLineAPI: true,
+        awaitPromise: true,
+        returnByValue: true
+      }).then(result => {
+        clearTimeout(asyncTimeout);
+        const value = result.result.value;
 
-if(result.exceptionDetails){
+        if (result.exceptionDetails) {
+          // An error occurred before we could even create a Promise, should be *very* rare
+          reject(new Error('an unexpected driver error occurred'));
+        } if (value && value.__failedInBrowser) {
+          reject(Object.assign(new Error(), value));
+        } else {
+          resolve(value);
+        }
+      }).catch(err => {
+        clearTimeout(asyncTimeout);
+        reject(err);
+      });
+    });
+  }
 
-reject(new Error('an unexpected driver error occurred'));
-}if(value&&value.__failedInBrowser){
-reject(Object.assign(new Error(),value));
-}else{
-resolve(value);
-}
-}).catch(err=>{
-clearTimeout(asyncTimeout);
-reject(err);
-});
-});
-}
+  getSecurityState() {
+    return new Promise((resolve, reject) => {
+      this.once('Security.securityStateChanged', data => {
+        this.sendCommand('Security.disable')
+          .then(_ => resolve(data), reject);
+      });
 
-getSecurityState(){
-return new Promise((resolve,reject)=>{
-this.once('Security.securityStateChanged',data=>{
-this.sendCommand('Security.disable').
-then(_=>resolve(data),reject);
-});
+      this.sendCommand('Security.enable').catch(reject);
+    });
+  }
 
-this.sendCommand('Security.enable').catch(reject);
-});
-}
+  getServiceWorkerVersions() {
+    return new Promise((resolve, reject) => {
+      this.once('ServiceWorker.workerVersionUpdated', data => {
+        this.sendCommand('ServiceWorker.disable')
+          .then(_ => resolve(data), reject);
+      });
 
-getServiceWorkerVersions(){
-return new Promise((resolve,reject)=>{
-this.once('ServiceWorker.workerVersionUpdated',data=>{
-this.sendCommand('ServiceWorker.disable').
-then(_=>resolve(data),reject);
-});
+      this.sendCommand('ServiceWorker.enable').catch(reject);
+    });
+  }
 
-this.sendCommand('ServiceWorker.enable').catch(reject);
-});
-}
+  getServiceWorkerRegistrations() {
+    return new Promise((resolve, reject) => {
+      this.once('ServiceWorker.workerRegistrationUpdated', data => {
+        this.sendCommand('ServiceWorker.disable')
+          .then(_ => resolve(data), reject);
+      });
 
-getServiceWorkerRegistrations(){
-return new Promise((resolve,reject)=>{
-this.once('ServiceWorker.workerRegistrationUpdated',data=>{
-this.sendCommand('ServiceWorker.disable').
-then(_=>resolve(data),reject);
-});
+      this.sendCommand('ServiceWorker.enable').catch(reject);
+    });
+  }
 
-this.sendCommand('ServiceWorker.enable').catch(reject);
-});
-}
+  /**
+   * Rejects if any open tabs would share a service worker with the target URL.
+   * This includes the target tab, so navigation to something like about:blank
+   * should be done before calling.
+   * @param {!string} pageUrl
+   * @return {!Promise}
+   */
+  assertNoSameOriginServiceWorkerClients(pageUrl) {
+    let registrations;
+    let versions;
+    return this.getServiceWorkerRegistrations().then(data => {
+      registrations = data.registrations;
+    }).then(_ => this.getServiceWorkerVersions()).then(data => {
+      versions = data.versions;
+    }).then(_ => {
+      const origin = new URL(pageUrl).origin;
 
+      registrations
+        .filter(reg => {
+          const swOrigin = new URL(reg.scopeURL).origin;
 
+          return origin === swOrigin;
+        })
+        .forEach(reg => {
+          versions.forEach(ver => {
+            // Ignore workers unaffiliated with this registration
+            if (ver.registrationId !== reg.registrationId) {
+              return;
+            }
 
+            // Throw if service worker for this origin has active controlledClients.
+            if (ver.controlledClients && ver.controlledClients.length > 0) {
+              throw new Error('You probably have multiple tabs open to the same origin.');
+            }
+          });
+        });
+    });
+  }
 
+  /**
+   * If our main document URL redirects, we will update options.url accordingly
+   * As such, options.url will always represent the post-redirected URL.
+   * options.initialUrl is the pre-redirect URL that things started with
+   * @param {!Object} opts
+   */
+  enableUrlUpdateIfRedirected(opts) {
+    this._networkRecorder.on('requestloaded', redirectRequest => {
+      // Quit if this is not a redirected request
+      if (!redirectRequest.redirectSource) {
+        return;
+      }
+      const earlierRequest = redirectRequest.redirectSource;
+      if (earlierRequest.url === opts.url) {
+        opts.url = redirectRequest.url;
+      }
+    });
+  }
 
+  /**
+   * Returns a promise that resolves when the network has been idle for
+   * `pauseAfterLoadMs` ms and a method to cancel internal network listeners and
+   * timeout.
+   * @param {string} pauseAfterLoadMs
+   * @return {{promise: !Promise, cancel: function()}}
+   * @private
+   */
+  _waitForNetworkIdle(pauseAfterLoadMs) {
+    let idleTimeout;
+    let cancel;
 
+    const promise = new Promise((resolve, reject) => {
+      const onIdle = () => {
+        // eslint-disable-next-line no-use-before-define
+        this._networkRecorder.once('networkbusy', onBusy);
+        idleTimeout = setTimeout(_ => {
+          cancel();
+          resolve();
+        }, pauseAfterLoadMs);
+      };
 
+      const onBusy = () => {
+        this._networkRecorder.once('networkidle', onIdle);
+        clearTimeout(idleTimeout);
+      };
 
-assertNoSameOriginServiceWorkerClients(pageUrl){
-let registrations;
-let versions;
-return this.getServiceWorkerRegistrations().then(data=>{
-registrations=data.registrations;
-}).then(_=>this.getServiceWorkerVersions()).then(data=>{
-versions=data.versions;
-}).then(_=>{
-const origin=new URL(pageUrl).origin;
+      cancel = () => {
+        clearTimeout(idleTimeout);
+        this._networkRecorder.removeListener('networkbusy', onBusy);
+        this._networkRecorder.removeListener('networkidle', onIdle);
+      };
 
-registrations.
-filter(reg=>{
-const swOrigin=new URL(reg.scopeURL).origin;
+      if (this._networkRecorder.isIdle()) {
+        onIdle();
+      } else {
+        onBusy();
+      }
+    });
 
-return origin===swOrigin;
-}).
-forEach(reg=>{
-versions.forEach(ver=>{
+    return {
+      promise,
+      cancel
+    };
+  }
 
-if(ver.registrationId!==reg.registrationId){
-return;
-}
+  /**
+   * Return a promise that resolves `pauseAfterLoadMs` after the load event
+   * fires and a method to cancel internal listeners and timeout.
+   * @param {number} pauseAfterLoadMs
+   * @return {{promise: !Promise, cancel: function()}}
+   * @private
+   */
+  _waitForLoadEvent(pauseAfterLoadMs) {
+    let loadListener;
+    let loadTimeout;
 
+    const promise = new Promise((resolve, reject) => {
+      loadListener = function() {
+        loadTimeout = setTimeout(resolve, pauseAfterLoadMs);
+      };
+      this.once('Page.loadEventFired', loadListener);
+    });
+    const cancel = () => {
+      this.off('Page.loadEventFired', loadListener);
+      clearTimeout(loadTimeout);
+    };
 
-if(ver.controlledClients&&ver.controlledClients.length>0){
-throw new Error('You probably have multiple tabs open to the same origin.');
-}
-});
-});
-});
-}
+    return {
+      promise,
+      cancel
+    };
+  }
 
+  /**
+   * Returns a promise that resolves when:
+   * - it's been pauseAfterLoadMs milliseconds after both onload and the network
+   * has gone idle, or
+   * - maxWaitForLoadedMs milliseconds have passed.
+   * See https://github.com/GoogleChrome/lighthouse/issues/627 for more.
+   * @param {number} pauseAfterLoadMs
+   * @param {number} maxWaitForLoadedMs
+   * @return {!Promise}
+   * @private
+   */
+  _waitForFullyLoaded(pauseAfterLoadMs, maxWaitForLoadedMs) {
+    let maxTimeoutHandle;
 
+    // Listener for onload. Resolves pauseAfterLoadMs ms after load.
+    const waitForLoadEvent = this._waitForLoadEvent(pauseAfterLoadMs);
+    // Network listener. Resolves when the network has been idle for pauseAfterLoadMs.
+    const waitForNetworkIdle = this._waitForNetworkIdle(pauseAfterLoadMs);
 
-
-
-
-
-enableUrlUpdateIfRedirected(opts){
-this._networkRecorder.on('requestloaded',redirectRequest=>{
-
-if(!redirectRequest.redirectSource){
-return;
-}
-const earlierRequest=redirectRequest.redirectSource;
-if(earlierRequest.url===opts.url){
-opts.url=redirectRequest.url;
-}
-});
-}
-
-
-
-
-
-
-
-
-
-_waitForNetworkIdle(pauseAfterLoadMs){
-let idleTimeout;
-let cancel;
-
-const promise=new Promise((resolve,reject)=>{
-const onIdle=()=>{
-
-this._networkRecorder.once('networkbusy',onBusy);
-idleTimeout=setTimeout(_=>{
-cancel();
-resolve();
-},pauseAfterLoadMs);
-};
-
-const onBusy=()=>{
-this._networkRecorder.once('networkidle',onIdle);
-clearTimeout(idleTimeout);
-};
-
-cancel=()=>{
-clearTimeout(idleTimeout);
-this._networkRecorder.removeListener('networkbusy',onBusy);
-this._networkRecorder.removeListener('networkidle',onIdle);
-};
-
-if(this._networkRecorder.isIdle()){
-onIdle();
-}else{
-onBusy();
-}
-});
-
-return{
-promise,
-cancel};
-
-}
-
-
-
-
-
-
-
-
-_waitForLoadEvent(pauseAfterLoadMs){
-let loadListener;
-let loadTimeout;
-
-const promise=new Promise((resolve,reject)=>{
-loadListener=function(){
-loadTimeout=setTimeout(resolve,pauseAfterLoadMs);
-};
-this.once('Page.loadEventFired',loadListener);
-});
-const cancel=()=>{
-this.off('Page.loadEventFired',loadListener);
-clearTimeout(loadTimeout);
-};
-
-return{
-promise,
-cancel};
-
-}
-
-
-
-
-
-
-
-
-
-
-
-
-_waitForFullyLoaded(pauseAfterLoadMs,maxWaitForLoadedMs){
-let maxTimeoutHandle;
-
-
-const waitForLoadEvent=this._waitForLoadEvent(pauseAfterLoadMs);
-
-const waitForNetworkIdle=this._waitForNetworkIdle(pauseAfterLoadMs);
-
-
-
-const loadPromise=Promise.all([
-waitForLoadEvent.promise,
-waitForNetworkIdle.promise]).
-then(_=>{
-return function(){
-log.verbose('Driver','loadEventFired and network considered idle');
-clearTimeout(maxTimeoutHandle);
-};
-});
-
-
-
-const maxTimeoutPromise=new Promise((resolve,reject)=>{
-maxTimeoutHandle=setTimeout(resolve,maxWaitForLoadedMs);
-}).then(_=>{
-return function(){
-log.warn('Driver','Timed out waiting for page load. Moving on...');
-waitForLoadEvent.cancel();
-waitForNetworkIdle.cancel();
-};
-});
-
-
-return Promise.race([
-loadPromise,
-maxTimeoutPromise]).
-then(cleanup=>cleanup());
-}
-
-
-
-
-
-
-
-
-
-
-
-gotoURL(url,options={}){
-const waitForLoad=options.waitForLoad||false;
-const disableJS=options.disableJavaScript||false;
-const pauseAfterLoadMs=options.flags&&options.flags.pauseAfterLoad||PAUSE_AFTER_LOAD;
-const maxWaitMs=options.flags&&options.flags.maxWaitForLoad||
-Driver.MAX_WAIT_FOR_FULLY_LOADED;
-
-return this.sendCommand('Page.enable').
-then(_=>this.sendCommand('Emulation.setScriptExecutionDisabled',{value:disableJS})).
-then(_=>this.sendCommand('Page.navigate',{url})).
-then(_=>waitForLoad&&this._waitForFullyLoaded(pauseAfterLoadMs,maxWaitMs));
-}
-
-
-
-
-
-
-getObjectProperty(objectId,propName){
-return new Promise(resolve=>{
-this.sendCommand('Runtime.getProperties',{
-objectId,
-accessorPropertiesOnly:true,
-generatePreview:false,
-ownProperties:false}).
-
-then(properties=>{
-const propertyForName=properties.result.
-find(property=>property.name===propName);
+    // Wait for both load promises. Resolves on cleanup function the clears load
+    // timeout timer.
+    const loadPromise = Promise.all([
+      waitForLoadEvent.promise,
+      waitForNetworkIdle.promise
+    ]).then(_ => {
+      return function() {
+        log.verbose('Driver', 'loadEventFired and network considered idle');
+        clearTimeout(maxTimeoutHandle);
+      };
+    });
 
-if(propertyForName){
-resolve(propertyForName.value.value);
-}else{
-resolve(null);
-}
-});
-});
-}
+    // Last resort timeout. Resolves maxWaitForLoadedMs ms from now on
+    // cleanup function that removes loadEvent and network idle listeners.
+    const maxTimeoutPromise = new Promise((resolve, reject) => {
+      maxTimeoutHandle = setTimeout(resolve, maxWaitForLoadedMs);
+    }).then(_ => {
+      return function() {
+        log.warn('Driver', 'Timed out waiting for page load. Moving on...');
+        waitForLoadEvent.cancel();
+        waitForNetworkIdle.cancel();
+      };
+    });
 
+    // Wait for load or timeout and run the cleanup function the winner returns.
+    return Promise.race([
+      loadPromise,
+      maxTimeoutPromise
+    ]).then(cleanup => cleanup());
+  }
 
+  /**
+   * Navigate to the given URL. Use of this method directly isn't advised: if
+   * the current page is already at the given URL, navigation will not occur and
+   * so the returned promise will only resolve after the MAX_WAIT_FOR_FULLY_LOADED
+   * timeout. See https://github.com/GoogleChrome/lighthouse/pull/185 for one
+   * possible workaround.
+   * @param {string} url
+   * @param {!Object} options
+   * @return {!Promise}
+   */
+  gotoURL(url, options = {}) {
+    const waitForLoad = options.waitForLoad || false;
+    const disableJS = options.disableJavaScript || false;
+    const pauseAfterLoadMs = (options.flags && options.flags.pauseAfterLoad) || PAUSE_AFTER_LOAD;
+    const maxWaitMs = (options.flags && options.flags.maxWaitForLoad) ||
+        Driver.MAX_WAIT_FOR_FULLY_LOADED;
 
+    return this.sendCommand('Page.enable')
+      .then(_ => this.sendCommand('Emulation.setScriptExecutionDisabled', {value: disableJS}))
+      .then(_ => this.sendCommand('Page.navigate', {url}))
+      .then(_ => waitForLoad && this._waitForFullyLoaded(pauseAfterLoadMs, maxWaitMs));
+  }
 
+  /**
+  * @param {string} objectId Object ID for the resolved DOM node
+  * @param {string} propName Name of the property
+  * @return {!Promise<string>} The property value, or null, if property not found
+  */
+  getObjectProperty(objectId, propName) {
+    return new Promise((resolve, reject) => {
+      this.sendCommand('Runtime.getProperties', {
+        objectId,
+        accessorPropertiesOnly: true,
+        generatePreview: false,
+        ownProperties: false,
+      })
+      .then(properties => {
+        const propertyForName = properties.result
+          .find(property => property.name === propName);
 
+        if (propertyForName && propertyForName.value) {
+          resolve(propertyForName.value.value);
+        } else {
+          resolve(null);
+        }
+      }).catch(reject);
+    });
+  }
 
-queryPermissionState(name){
-const expressionToEval=`
+  /**
+   * @param {string} name The name of API whose permission you wish to query
+   * @return {!Promise<string>} The state of permissions, resolved in a promise.
+   *    See https://developer.mozilla.org/en-US/docs/Web/API/Permissions/query.
+   */
+  queryPermissionState(name) {
+    const expressionToEval = `
       navigator.permissions.query({name: '${name}'}).then(result => {
         return result.state;
       })
     `;
 
-return this.evaluateAsync(expressionToEval);
-}
+    return this.evaluateAsync(expressionToEval);
+  }
 
+  /**
+   * @param {string} selector Selector to find in the DOM
+   * @return {!Promise<Element>} The found element, or null, resolved in a promise
+   */
+  querySelector(selector) {
+    return this.sendCommand('DOM.getDocument')
+      .then(result => result.root.nodeId)
+      .then(nodeId => this.sendCommand('DOM.querySelector', {
+        nodeId,
+        selector
+      }))
+      .then(element => {
+        if (element.nodeId === 0) {
+          return null;
+        }
+        return new Element(element, this);
+      });
+  }
 
+  /**
+   * @param {string} selector Selector to find in the DOM
+   * @return {!Promise<!Array<!Element>>} The found elements, or [], resolved in a promise
+   */
+  querySelectorAll(selector) {
+    return this.sendCommand('DOM.getDocument')
+      .then(result => result.root.nodeId)
+      .then(nodeId => this.sendCommand('DOM.querySelectorAll', {
+        nodeId,
+        selector
+      }))
+      .then(nodeList => {
+        const elementList = [];
+        nodeList.nodeIds.forEach(nodeId => {
+          if (nodeId !== 0) {
+            elementList.push(new Element({nodeId}, this));
+          }
+        });
+        return elementList;
+      });
+  }
 
+  /**
+   * @param {{additionalTraceCategories: string=}=} flags
+   */
+  beginTrace(flags) {
+    const additionalCategories = (flags && flags.additionalTraceCategories &&
+        flags.additionalTraceCategories.split(',')) || [];
+    const traceCategories = this._traceCategories.concat(additionalCategories);
+    const tracingOpts = {
+      categories: _uniq(traceCategories).join(','),
+      transferMode: 'ReturnAsStream',
+      options: 'sampling-frequency=10000'  // 1000 is default and too slow.
+    };
 
+    // Check any domains that could interfere with or add overhead to the trace.
+    if (this.isDomainEnabled('Debugger')) {
+      throw new Error('Debugger domain enabled when starting trace');
+    }
+    if (this.isDomainEnabled('CSS')) {
+      throw new Error('CSS domain enabled when starting trace');
+    }
+    if (this.isDomainEnabled('DOM')) {
+      throw new Error('DOM domain enabled when starting trace');
+    }
 
-querySelector(selector){
-return this.sendCommand('DOM.getDocument').
-then(result=>result.root.nodeId).
-then(nodeId=>this.sendCommand('DOM.querySelector',{
-nodeId,
-selector})).
+    this._devtoolsLog.reset();
+    this._devtoolsLog.beginRecording();
 
-then(element=>{
-if(element.nodeId===0){
-return null;
-}
-return new Element(element,this);
-});
-}
+    // Enable Page domain to wait for Page.loadEventFired
+    return this.sendCommand('Page.enable')
+      .then(_ => this.sendCommand('Tracing.start', tracingOpts));
+  }
 
+  /**
+   * @param {number=} pauseBeforeTraceEndMs Wait this many milliseconds before ending the trace
+   */
+  endTrace(pauseBeforeTraceEndMs = 0) {
+    return new Promise((resolve, reject) => {
+      // When the tracing has ended this will fire with a stream handle.
+      this.once('Tracing.tracingComplete', streamHandle => {
+        this._devtoolsLog.endRecording();
+        this._readTraceFromStream(streamHandle)
+            .then(traceContents => resolve(traceContents), reject);
+      });
 
+      // Issue the command to stop tracing after an optional delay.
+      // Audits like TTI may require slightly longer trace to find a minimum window size.
+      setTimeout(() => this.sendCommand('Tracing.end').catch(reject), pauseBeforeTraceEndMs);
+    });
+  }
 
+  _readTraceFromStream(streamHandle) {
+    return new Promise((resolve, reject) => {
+      let isEOF = false;
+      let result = '';
 
+      const readArguments = {
+        handle: streamHandle.stream
+      };
 
-querySelectorAll(selector){
-return this.sendCommand('DOM.getDocument').
-then(result=>result.root.nodeId).
-then(nodeId=>this.sendCommand('DOM.querySelectorAll',{
-nodeId,
-selector})).
+      const onChunkRead = response => {
+        if (isEOF) {
+          return;
+        }
 
-then(nodeList=>{
-const elementList=[];
-nodeList.nodeIds.forEach(nodeId=>{
-if(nodeId!==0){
-elementList.push(new Element({nodeId},this));
-}
-});
-return elementList;
-});
-}
+        result += response.data;
 
-beginTrace(){
-const tracingOpts={
-categories:this._traceCategories.join(','),
-transferMode:'ReturnAsStream',
-options:'sampling-frequency=10000'};
+        if (response.eof) {
+          isEOF = true;
+          return resolve(JSON.parse(result));
+        }
 
+        return this.sendCommand('IO.read', readArguments).then(onChunkRead);
+      };
 
+      this.sendCommand('IO.read', readArguments).then(onChunkRead).catch(reject);
+    });
+  }
 
-if(this.isDomainEnabled('Debugger')){
-throw new Error('Debugger domain enabled when starting trace');
-}
-if(this.isDomainEnabled('CSS')){
-throw new Error('CSS domain enabled when starting trace');
-}
-if(this.isDomainEnabled('DOM')){
-throw new Error('DOM domain enabled when starting trace');
-}
+  beginNetworkCollect(opts) {
+    return new Promise((resolve, reject) => {
+      this._networkRecords = [];
+      this._networkRecorder = new NetworkRecorder(this._networkRecords, this);
+      this.enableUrlUpdateIfRedirected(opts);
 
-this._devtoolsLog.reset();
-this._devtoolsLog.beginRecording();
+      this.on('Network.requestWillBeSent', this._networkRecorder.onRequestWillBeSent);
+      this.on('Network.requestServedFromCache', this._networkRecorder.onRequestServedFromCache);
+      this.on('Network.responseReceived', this._networkRecorder.onResponseReceived);
+      this.on('Network.dataReceived', this._networkRecorder.onDataReceived);
+      this.on('Network.loadingFinished', this._networkRecorder.onLoadingFinished);
+      this.on('Network.loadingFailed', this._networkRecorder.onLoadingFailed);
+      this.on('Network.resourceChangedPriority', this._networkRecorder.onResourceChangedPriority);
 
+      this.sendCommand('Network.enable').then(resolve, reject);
+    });
+  }
 
-return this.sendCommand('Page.enable').
-then(_=>this.sendCommand('Tracing.start',tracingOpts));
-}
+  endNetworkCollect() {
+    return new Promise((resolve, reject) => {
+      this.off('Network.requestWillBeSent', this._networkRecorder.onRequestWillBeSent);
+      this.off('Network.requestServedFromCache', this._networkRecorder.onRequestServedFromCache);
+      this.off('Network.responseReceived', this._networkRecorder.onResponseReceived);
+      this.off('Network.dataReceived', this._networkRecorder.onDataReceived);
+      this.off('Network.loadingFinished', this._networkRecorder.onLoadingFinished);
+      this.off('Network.loadingFailed', this._networkRecorder.onLoadingFailed);
+      this.off('Network.resourceChangedPriority', this._networkRecorder.onResourceChangedPriority);
 
+      resolve(this._networkRecords);
 
+      this._networkRecorder = null;
+      this._networkRecords = [];
+    });
+  }
 
+  enableRuntimeEvents() {
+    return this.sendCommand('Runtime.enable');
+  }
 
-endTrace(pauseBeforeTraceEndMs=0){
-return new Promise((resolve,reject)=>{
+  beginEmulation(flags) {
+    return Promise.resolve().then(_ => {
+      if (!flags.disableDeviceEmulation) return emulation.enableNexus5X(this);
+    }).then(_ => this.setThrottling(flags, {useThrottling: true}));
+  }
 
-this.once('Tracing.tracingComplete',streamHandle=>{
-this._devtoolsLog.endRecording();
-this._readTraceFromStream(streamHandle).
-then(traceContents=>resolve(traceContents),reject);
-});
-
-
-
-setTimeout(()=>this.sendCommand('Tracing.end').catch(reject),pauseBeforeTraceEndMs);
-});
-}
-
-_readTraceFromStream(streamHandle){
-return new Promise((resolve,reject)=>{
-let isEOF=false;
-let result='';
-
-const readArguments={
-handle:streamHandle.stream};
-
-
-const onChunkRead=response=>{
-if(isEOF){
-return;
-}
-
-result+=response.data;
-
-if(response.eof){
-isEOF=true;
-return resolve(JSON.parse(result));
-}
-
-return this.sendCommand('IO.read',readArguments).then(onChunkRead);
-};
-
-this.sendCommand('IO.read',readArguments).then(onChunkRead).catch(reject);
-});
-}
-
-beginNetworkCollect(opts){
-return new Promise((resolve,reject)=>{
-this._networkRecords=[];
-this._networkRecorder=new NetworkRecorder(this._networkRecords);
-this.enableUrlUpdateIfRedirected(opts);
-
-this.on('Network.requestWillBeSent',this._networkRecorder.onRequestWillBeSent);
-this.on('Network.requestServedFromCache',this._networkRecorder.onRequestServedFromCache);
-this.on('Network.responseReceived',this._networkRecorder.onResponseReceived);
-this.on('Network.dataReceived',this._networkRecorder.onDataReceived);
-this.on('Network.loadingFinished',this._networkRecorder.onLoadingFinished);
-this.on('Network.loadingFailed',this._networkRecorder.onLoadingFailed);
-this.on('Network.resourceChangedPriority',this._networkRecorder.onResourceChangedPriority);
-
-this.sendCommand('Network.enable').then(resolve,reject);
-});
-}
-
-endNetworkCollect(){
-return new Promise((resolve,reject)=>{
-this.off('Network.requestWillBeSent',this._networkRecorder.onRequestWillBeSent);
-this.off('Network.requestServedFromCache',this._networkRecorder.onRequestServedFromCache);
-this.off('Network.responseReceived',this._networkRecorder.onResponseReceived);
-this.off('Network.dataReceived',this._networkRecorder.onDataReceived);
-this.off('Network.loadingFinished',this._networkRecorder.onLoadingFinished);
-this.off('Network.loadingFailed',this._networkRecorder.onLoadingFailed);
-this.off('Network.resourceChangedPriority',this._networkRecorder.onResourceChangedPriority);
-
-resolve(this._networkRecords);
-
-this._networkRecorder=null;
-this._networkRecords=[];
-});
-}
-
-enableRuntimeEvents(){
-return this.sendCommand('Runtime.enable');
-}
-
-beginEmulation(flags){
-return Promise.resolve().then(_=>{
-if(!flags.disableDeviceEmulation)return emulation.enableNexus5X(this);
-}).then(_=>this.setThrottling(flags,{useThrottling:true}));
-}
-
-setThrottling(flags,passConfig){
-const p=[];
-if(passConfig.useThrottling){
-if(!flags.disableNetworkThrottling)p.push(emulation.enableNetworkThrottling(this));
-if(!flags.disableCpuThrottling)p.push(emulation.enableCPUThrottling(this));
-}else{
-p.push(emulation.disableNetworkThrottling(this));
-p.push(emulation.disableCPUThrottling(this));
-}
-return Promise.all(p);
-}
-
-
-
-
-
-goOffline(){
-return this.sendCommand('Network.enable').
-then(_=>emulation.goOffline(this)).
-then(_=>this.online=false);
-}
-
-
-
-
-
-
-
-goOnline(options){
-return this.setThrottling(options.flags,options.config).
-then(_=>this.online=true);
-}
-
-cleanAndDisableBrowserCaches(){
-return Promise.all([
-this.clearBrowserCache(),
-this.disableBrowserCache()]);
-
-}
-
-clearBrowserCache(){
-return this.sendCommand('Network.clearBrowserCache');
-}
-
-disableBrowserCache(){
-return this.sendCommand('Network.setCacheDisabled',{cacheDisabled:true});
-}
-
-clearDataForOrigin(url){
-const origin=new URL(url).origin;
-
+  setThrottling(flags, passConfig) {
+    const throttleCpu = passConfig.useThrottling && !flags.disableCpuThrottling;
+    const throttleNetwork = passConfig.useThrottling && !flags.disableNetworkThrottling;
+    const cpuPromise = throttleCpu ?
+        emulation.enableCPUThrottling(this) :
+        emulation.disableCPUThrottling(this);
+    const networkPromise = throttleNetwork ?
+        emulation.enableNetworkThrottling(this) :
+        emulation.disableNetworkThrottling(this);
 
+    return Promise.all([cpuPromise, networkPromise]);
+  }
 
-const typesToClear=[
-'appcache',
+  /**
+   * Emulate internet disconnection.
+   * @return {!Promise}
+   */
+  goOffline() {
+    return this.sendCommand('Network.enable')
+      .then(_ => emulation.goOffline(this))
+      .then(_ => this.online = false);
+  }
 
-'file_systems',
-'indexeddb',
-'local_storage',
-'shader_cache',
-'websql',
-'service_workers',
-'cache_storage'].
-join(',');
+  /**
+   * Enable internet connection, using emulated mobile settings if
+   * `options.flags.disableNetworkThrottling` is false.
+   * @param {!Object} options
+   * @return {!Promise}
+   */
+  goOnline(options) {
+    return this.setThrottling(options.flags, options.config)
+        .then(_ => this.online = true);
+  }
 
-return this.sendCommand('Storage.clearDataForOrigin',{
-origin:origin,
-storageTypes:typesToClear});
+  cleanAndDisableBrowserCaches() {
+    return Promise.all([
+      this.clearBrowserCache(),
+      this.disableBrowserCache()
+    ]);
+  }
 
-}
+  clearBrowserCache() {
+    return this.sendCommand('Network.clearBrowserCache');
+  }
 
+  disableBrowserCache() {
+    return this.sendCommand('Network.setCacheDisabled', {cacheDisabled: true});
+  }
 
+  clearDataForOrigin(url) {
+    const origin = new URL(url).origin;
 
+    // Clear all types of storage except cookies, so the user isn't logged out.
+    //   https://chromedevtools.github.io/debugger-protocol-viewer/tot/Storage/#type-StorageType
+    const typesToClear = [
+      'appcache',
+      // 'cookies',
+      'file_systems',
+      'indexeddb',
+      'local_storage',
+      'shader_cache',
+      'websql',
+      'service_workers',
+      'cache_storage'
+    ].join(',');
 
+    return this.sendCommand('Storage.clearDataForOrigin', {
+      origin: origin,
+      storageTypes: typesToClear
+    });
+  }
 
-cacheNatives(){
-return this.evaluateScriptOnLoad(`window.__nativePromise = Promise;
+  /**
+   * Cache native functions/objects inside window
+   * so we are sure polyfills do not overwrite the native implementations
+   */
+  cacheNatives() {
+    return this.evaluateScriptOnLoad(`window.__nativePromise = Promise;
         window.__nativeError = Error;`);
-}
+  }
 
+  /**
+   * Keeps track of calls to a JS function and returns a list of {url, line, col}
+   * of the usage. Should be called before page load (in beforePass).
+   * @param {string} funcName The function name to track ('Date.now', 'console.time').
+   * @return {function(): !Promise<!Array<{url: string, line: number, col: number}>>}
+   *     Call this method when you want results.
+   */
+  captureFunctionCallSites(funcName) {
+    const globalVarToPopulate = `window['__${funcName}StackTraces']`;
+    const collectUsage = () => {
+      return this.evaluateAsync(
+          `Promise.resolve(Array.from(${globalVarToPopulate}).map(item => JSON.parse(item)))`)
+        .then(result => {
+          if (!Array.isArray(result)) {
+            throw new Error(
+                'Driver failure: Expected evaluateAsync results to be an array ' +
+                `but got "${JSON.stringify(result)}" instead.`);
+          }
+          // Filter out usage from extension content scripts.
+          return result.filter(item => !item.isExtension);
+        });
+    };
 
+    const funcBody = captureJSCallUsage.toString();
 
-
-
-
-
-
-captureFunctionCallSites(funcName){
-const globalVarToPopulate=`window['__${funcName}StackTraces']`;
-const collectUsage=()=>{
-return this.evaluateAsync(
-`Promise.resolve(Array.from(${globalVarToPopulate}).map(item => JSON.parse(item)))`).
-then(result=>{
-if(!Array.isArray(result)){
-throw new Error(
-'Driver failure: Expected evaluateAsync results to be an array '+
-`but got "${JSON.stringify(result)}" instead.`);
-}
-
-return result.filter(item=>!item.isExtension);
-});
-};
-
-const funcBody=captureJSCallUsage.toString();
-
-this.evaluateScriptOnLoad(`
+    this.evaluateScriptOnLoad(`
         ${globalVarToPopulate} = new Set();
         (${funcName} = ${funcBody}(${funcName}, ${globalVarToPopulate}))`);
 
-return collectUsage;
+    return collectUsage;
+  }
+
+  blockUrlPatterns(urlPatterns) {
+    const promiseArr = urlPatterns.map(url => this.sendCommand('Network.addBlockedURL', {url}));
+    return Promise.all(promiseArr);
+  }
 }
 
-blockUrlPatterns(urlPatterns){
-const promiseArr=urlPatterns.map(url=>this.sendCommand('Network.addBlockedURL',{url}));
-return Promise.all(promiseArr);
-}}
+/**
+ * Tracks function call usage. Used by captureJSCalls to inject code into the page.
+ * @param {function(...*): *} funcRef The function call to track.
+ * @param {!Set} set An empty set to populate with stack traces. Should be
+ *     on the global object.
+ * @return {function(...*): *} A wrapper around the original function.
+ */
+function captureJSCallUsage(funcRef, set) {
+  /* global window */
+  const __nativeError = window.__nativeError || Error;
+  const originalFunc = funcRef;
+  const originalPrepareStackTrace = __nativeError.prepareStackTrace;
 
+  return function(...args) {
+    // Note: this function runs in the context of the page that is being audited.
 
+    // See v8's Stack Trace API https://github.com/v8/v8/wiki/Stack-Trace-API#customizing-stack-traces
+    __nativeError.prepareStackTrace = function(error, structStackTrace) {
+      // First frame is the function we injected (the one that just threw).
+      // Second, is the actual callsite of the funcRef we're after.
+      const callFrame = structStackTrace[1];
+      let url = callFrame.getFileName() || callFrame.getEvalOrigin();
+      const line = callFrame.getLineNumber();
+      const col = callFrame.getColumnNumber();
+      const isEval = callFrame.isEval();
+      let isExtension = false;
+      const stackTrace = structStackTrace.slice(1).map(callsite => callsite.toString());
 
+      // If we don't have an URL, (e.g. eval'd code), use the 2nd entry in the
+      // stack trace. First is eval context: eval(<context>):<line>:<col>.
+      // Second is the callsite where eval was called.
+      // See https://crbug.com/646849.
+      if (isEval) {
+        url = stackTrace[1];
+      }
 
+      // Chrome extension content scripts can produce an empty .url and
+      // "<anonymous>:line:col" for the first entry in the stack trace.
+      if (stackTrace[0].startsWith('<anonymous>')) {
+        // Note: Although captureFunctionCallSites filters out crx usage,
+        // filling url here provides context. We may want to keep those results
+        // some day.
+        url = stackTrace[0];
+        isExtension = true;
+      }
 
+      // TODO: add back when we want stack traces.
+      // Stack traces were removed from the return object in
+      // https://github.com/GoogleChrome/lighthouse/issues/957 so callsites
+      // would be unique.
+      return {url, args, line, col, isEval, isExtension}; // return value is e.stack
+    };
+    const e = new __nativeError(`__called ${funcRef.name}__`);
+    set.add(JSON.stringify(e.stack));
 
+    // Restore prepareStackTrace so future errors use v8's formatter and not
+    // our custom one.
+    __nativeError.prepareStackTrace = originalPrepareStackTrace;
 
-
-
-function captureJSCallUsage(funcRef,set){
-
-const __nativeError=window.__nativeError||Error;
-const originalFunc=funcRef;
-const originalPrepareStackTrace=__nativeError.prepareStackTrace;
-
-return function(...args){
-
-
-
-__nativeError.prepareStackTrace=function(error,structStackTrace){
-
-
-const callFrame=structStackTrace[1];
-let url=callFrame.getFileName()||callFrame.getEvalOrigin();
-const line=callFrame.getLineNumber();
-const col=callFrame.getColumnNumber();
-const isEval=callFrame.isEval();
-let isExtension=false;
-const stackTrace=structStackTrace.slice(1).map(callsite=>callsite.toString());
-
-
-
-
-
-if(isEval){
-url=stackTrace[1];
+    // eslint-disable-next-line no-invalid-this
+    return originalFunc.apply(this, args);
+  };
 }
 
+/**
+ * The `exceptionDetails` provided by the debugger protocol does not contain the useful
+ * information such as name, message, and stack trace of the error when it's wrapped in a
+ * promise. Instead, map to a successful object that contains this information.
+ * @param {string|Error} err The error to convert
+ * istanbul ignore next
+ */
+function wrapRuntimeEvalErrorInBrowser(err) {
+  err = err || new Error();
+  const fallbackMessage = typeof err === 'string' ? err : 'unknown error';
 
-
-if(stackTrace[0].startsWith('<anonymous>')){
-
-
-
-url=stackTrace[0];
-isExtension=true;
+  return {
+    __failedInBrowser: true,
+    name: err.name || 'Error',
+    message: err.message || fallbackMessage,
+    stack: err.stack || (new Error()).stack,
+  };
 }
 
+module.exports = Driver;
 
-
-
-
-return{url,args,line,col,isEval,isExtension};
-};
-const e=new __nativeError(`__called ${funcRef.name}__`);
-set.add(JSON.stringify(e.stack));
-
-
-
-__nativeError.prepareStackTrace=originalPrepareStackTrace;
-
-
-return originalFunc.apply(this,args);
-};
-}
-
-
-
-
-
-
-
-
-function wrapRuntimeEvalErrorInBrowser(err){
-err=err||new Error();
-const fallbackMessage=typeof err==='string'?err:'unknown error';
-
-return{
-__failedInBrowser:true,
-name:err.name||'Error',
-message:err.message||fallbackMessage,
-stack:err.stack||new Error().stack};
-
-}
-
-module.exports=Driver;
-
-},{"../lib/element":15,"../lib/emulation":16,"../lib/log.js":19,"../lib/network-recorder":21,"../lib/url-shim":25,"./devtools-log":10,"events":200}],12:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"../lib/element":15,"../lib/emulation":16,"../lib/log.js":19,"../lib/network-recorder":21,"../lib/url-shim":25,"./devtools-log":10,"events":207}],12:[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-const log=require('../lib/log.js');
-const Audit=require('../audits/audit');
-const path=require('path');
-const URL=require('../lib/url-shim');
+const log = require('../lib/log.js');
+const Audit = require('../audits/audit');
+const path = require('path');
+const URL = require('../lib/url-shim');
 
+/**
+ * @typedef {!Object<string, !Array<!Promise<*>>>}
+ */
+let GathererResults; // eslint-disable-line no-unused-vars
 
+/**
+ * Class that drives browser to load the page and runs gatherer lifecycle hooks.
+ * Execution sequence when GatherRunner.run() is called:
+ *
+ * 1. Setup
+ *   A. navigate to about:blank
+ *   B. driver.connect()
+ *   C. GatherRunner.setupDriver()
+ *     i. assertNoSameOriginServiceWorkerClients
+ *     ii. beginEmulation
+ *     iii. enableRuntimeEvents
+ *     iv. evaluateScriptOnLoad rescue native Promise from potential polyfill
+ *     v. cleanAndDisableBrowserCaches
+ *     vi. clearDataForOrigin
+ *     vii. blockUrlPatterns
+ *
+ * 2. For each pass in the config:
+ *   A. GatherRunner.beforePass()
+ *     i. navigate to about:blank
+ *     ii. all gatherer's beforePass()
+ *   B. GatherRunner.pass()
+ *     i. GatherRunner.loadPage()
+ *       b. beginTrace (if requested) & beginNetworkCollect
+ *       c. navigate to options.url (and wait for onload)
+ *     ii. all gatherer's pass()
+ *   C. GatherRunner.afterPass()
+ *     i. endTrace (if requested) & endNetworkCollect & endThrottling
+ *     ii. all gatherer's afterPass()
+ *
+ * 3. Teardown
+ *   A. GatherRunner.disposeDriver()
+ *   B. collect all artifacts and return them
+ *     i. collectArtifacts() from completed passes on each gatherer
+ *     ii. add trace data and computed artifact methods
+ */
+class GatherRunner {
+  /**
+   * Loads about:blank and waits there briefly. Since a Page.reload command does
+   * not let a service worker take over, we navigate away and then come back to
+   * reload. We do not `waitForLoad` on about:blank since a page load event is
+   * never fired on it.
+   * @param {!Driver} driver
+   * @param {url=} url
+   * @param {number=} duration
+   * @return {!Promise}
+   */
+  static loadBlank(driver, url = 'about:blank', duration = 300) {
+    return driver.gotoURL(url).then(_ => new Promise(resolve => setTimeout(resolve, duration)));
+  }
 
+  /**
+   * Loads options.url with specified options.
+   * @param {!Driver} driver
+   * @param {!Object} options
+   * @return {!Promise}
+   */
+  static loadPage(driver, options) {
+    return Promise.resolve()
+      // Begin tracing only if requested by config.
+      .then(_ => options.config.recordTrace && driver.beginTrace(options.flags))
+      // Network is always recorded for internal use, even if not saved as artifact.
+      .then(_ => driver.beginNetworkCollect(options))
+      // Navigate.
+      .then(_ => driver.gotoURL(options.url, {
+        waitForLoad: true,
+        disableJavaScript: !!options.disableJavaScript,
+        flags: options.flags,
+      }));
+  }
 
+  /**
+   * @param {!Driver} driver
+   * @param {!GathererResults} gathererResults
+   * @param {!Object} options
+   * @return {!Promise}
+   */
+  static setupDriver(driver, gathererResults, options) {
+    log.log('status', 'Initializing…');
+    const resetStorage = !options.flags.disableStorageReset;
+    // Enable emulation based on flags
+    return driver.assertNoSameOriginServiceWorkerClients(options.url)
+      .then(_ => driver.beginEmulation(options.flags))
+      .then(_ => driver.enableRuntimeEvents())
+      .then(_ => driver.cacheNatives())
+      .then(_ => resetStorage && driver.cleanAndDisableBrowserCaches())
+      .then(_ => resetStorage && driver.clearDataForOrigin(options.url))
+      .then(_ => driver.blockUrlPatterns(options.flags.blockedUrlPatterns || []))
+      .then(_ => gathererResults.UserAgent = [driver.getUserAgent()]);
+  }
 
+  static disposeDriver(driver) {
+    // We dont need to hold up the reporting for the reload/disconnect,
+    // so we will not return a promise in here.
+    log.log('status', 'Disconnecting from browser...');
+    driver.disconnect().catch(err => {
+      // Ignore disconnecting error if browser was already closed.
+      // See https://github.com/GoogleChrome/lighthouse/issues/1583
+      if (!(/close\/.*status: 500$/.test(err.message))) {
+        log.error('GatherRunner disconnect', err.message);
+      }
+    });
+  }
 
+  /**
+   * Test any error output from the promise, absorbing non-fatal errors and
+   * throwing on fatal ones so that run is stopped.
+   * @param {!Promise<*>} promise
+   * @return {!Promise<*>}
+   */
+  static recoverOrThrow(promise) {
+    return promise.catch(err => {
+      if (err.fatal) {
+        throw err;
+      }
+    });
+  }
 
+  /**
+   * Throws an error if the original network request failed or wasn't found.
+   * @param {string} url The URL of the original requested page.
+   * @param {{online: boolean}} driver
+   * @param {!Array<WebInspector.NetworkRequest>} networkRecords
+   */
+  static assertPageLoaded(url, driver, networkRecords) {
+    const mainRecord = networkRecords.find(record => {
+      // record.url is actual request url, so needs to be compared without any URL fragment.
+      return URL.equalWithExcludedFragments(record.url, url);
+    });
+    if (driver.online && (!mainRecord || mainRecord.failed)) {
+      const message = mainRecord ? mainRecord.localizedFailDescription : 'timeout reached';
+      log.error('GatherRunner', message);
+      const error = new Error(`Unable to load the page: ${message}`);
+      error.code = 'PAGE_LOAD_ERROR';
+      throw error;
+    }
+  }
 
+  /**
+   * Navigates to about:blank and calls beforePass() on gatherers before tracing
+   * has started and before navigation to the target page.
+   * @param {!Object} options
+   * @param {!GathererResults} gathererResults
+   * @return {!Promise}
+   */
+  static beforePass(options, gathererResults) {
+    const blankPage = options.config.blankPage;
+    const blankDuration = options.config.blankDuration;
+    const pass = GatherRunner.loadBlank(options.driver, blankPage, blankDuration);
 
+    return options.config.gatherers.reduce((chain, gatherer) => {
+      return chain.then(_ => {
+        const artifactPromise = Promise.resolve().then(_ => gatherer.beforePass(options));
+        gathererResults[gatherer.name] = [artifactPromise];
+        return GatherRunner.recoverOrThrow(artifactPromise);
+      });
+    }, pass);
+  }
 
+  /**
+   * Navigates to requested URL and then runs pass() on gatherers while trace
+   * (if requested) is still being recorded.
+   * @param {!Object} options
+   * @param {!GathererResults} gathererResults
+   * @return {!Promise}
+   */
+  static pass(options, gathererResults) {
+    const driver = options.driver;
+    const config = options.config;
+    const gatherers = config.gatherers;
 
+    const gatherernames = gatherers.map(g => g.name).join(', ');
+    const status = 'Loading page & waiting for onload';
+    log.log('status', status, gatherernames);
 
+    const pass = GatherRunner.loadPage(driver, options).then(_ => {
+      log.log('statusEnd', status);
+    });
 
+    return gatherers.reduce((chain, gatherer) => {
+      return chain.then(_ => {
+        const artifactPromise = Promise.resolve().then(_ => gatherer.pass(options));
+        gathererResults[gatherer.name].push(artifactPromise);
+        return GatherRunner.recoverOrThrow(artifactPromise);
+      });
+    }, pass);
+  }
 
+  /**
+   * Ends tracing and collects trace data (if requested for this pass), and runs
+   * afterPass() on gatherers with trace data passed in. Promise resolves with
+   * object containing trace and network data.
+   * @param {!Object} options
+   * @param {!GathererResults} gathererResults
+   * @return {!Promise}
+   */
+  static afterPass(options, gathererResults) {
+    const driver = options.driver;
+    const config = options.config;
+    const gatherers = config.gatherers;
+    const passData = {};
 
+    let pass = Promise.resolve();
 
+    if (config.recordTrace) {
+      pass = pass.then(_ => {
+        log.log('status', 'Retrieving trace');
+        return driver.endTrace(config.pauseBeforeTraceEndMs);
+      }).then(traceContents => {
+        // Before Chrome 54.0.2816 (codereview.chromium.org/2161583004),
+        // traceContents was an array of trace events; after, traceContents is
+        // an object with a traceEvents property. Normalize to object form.
+        passData.trace = Array.isArray(traceContents) ?
+            {traceEvents: traceContents} : traceContents;
+        passData.devtoolsLog = driver.devtoolsLog;
+        log.verbose('statusEnd', 'Retrieving trace');
+      });
+    }
 
+    const status = 'Retrieving network records';
+    pass = pass.then(_ => {
+      log.log('status', status);
+      return driver.endNetworkCollect();
+    }).then(networkRecords => {
+      GatherRunner.assertPageLoaded(options.url, driver, networkRecords);
 
+      // Network records only given to gatherers if requested by config.
+      config.recordNetwork && (passData.networkRecords = networkRecords);
+      log.verbose('statusEnd', status);
+    });
 
+    // Disable throttling so the afterPass analysis isn't throttled
+    pass = pass.then(_ => driver.setThrottling(options.flags, {useThrottling: false}));
 
+    pass = gatherers.reduce((chain, gatherer) => {
+      const status = `Retrieving: ${gatherer.name}`;
+      return chain.then(_ => {
+        log.log('status', status);
+        const artifactPromise = Promise.resolve().then(_ => gatherer.afterPass(options, passData));
+        gathererResults[gatherer.name].push(artifactPromise);
+        return GatherRunner.recoverOrThrow(artifactPromise);
+      }).then(_ => {
+        log.verbose('statusEnd', status);
+      });
+    }, pass);
 
+    // Resolve on tracing data using passName from config.
+    return pass.then(_ => passData);
+  }
 
+  /**
+   * Takes the results of each gatherer phase for each gatherer and uses the
+   * last produced value (that's not undefined) as the artifact for that
+   * gatherer. If a non-fatal error was rejected from a gatherer phase,
+   * uses that error object as the artifact instead.
+   * @param {!GathererResults} gathererResults
+   * @return {!Promise<!Artifacts>}
+   */
+  static collectArtifacts(gathererResults) {
+    const artifacts = {};
 
+    return Object.keys(gathererResults).reduce((chain, gathererName) => {
+      return chain.then(_ => {
+        const phaseResultsPromises = gathererResults[gathererName];
+        return Promise.all(phaseResultsPromises).then(phaseResults => {
+          // Take last defined pass result as artifact.
+          const definedResults = phaseResults.filter(element => element !== undefined);
+          const artifact = definedResults[definedResults.length - 1];
+          if (artifact === undefined) {
+            throw new Error(`${gathererName} failed to provide an artifact.`);
+          }
+          artifacts[gathererName] = artifact;
+        }, err => {
+          // To reach this point, all errors are non-fatal, so return err to
+          // runner to handle turning it into an error audit.
+          artifacts[gathererName] = err;
+        });
+      });
+    }, Promise.resolve()).then(_ => {
+      return artifacts;
+    });
+  }
 
+  static run(passes, options) {
+    const driver = options.driver;
+    const tracingData = {
+      traces: {},
+      devtoolsLogs: {},
+      networkRecords: {}
+    };
 
+    if (typeof options.url !== 'string' || options.url.length === 0) {
+      return Promise.reject(new Error('You must provide a url to the gather-runner'));
+    }
 
+    if (typeof options.flags === 'undefined') {
+      options.flags = {};
+    }
 
+    if (typeof options.config === 'undefined') {
+      return Promise.reject(new Error('You must provide a config'));
+    }
 
+    if (typeof options.flags.disableCpuThrottling === 'undefined') {
+      options.flags.disableCpuThrottling = false;
+    }
 
+    passes = this.instantiateGatherers(passes, options.config.configDir);
 
+    const gathererResults = {};
 
+    return driver.connect()
+      .then(_ => GatherRunner.loadBlank(driver))
+      .then(_ => GatherRunner.setupDriver(driver, gathererResults, options))
 
+      // Run each pass
+      .then(_ => {
+        // If the main document redirects, we'll update this to keep track
+        let urlAfterRedirects;
+        return passes.reduce((chain, config, passIndex) => {
+          const runOptions = Object.assign({}, options, {config});
+          return chain
+            .then(_ => driver.setThrottling(options.flags, config))
+            .then(_ => GatherRunner.beforePass(runOptions, gathererResults))
+            .then(_ => GatherRunner.pass(runOptions, gathererResults))
+            .then(_ => GatherRunner.afterPass(runOptions, gathererResults))
+            .then(passData => {
+              // If requested by config, merge trace and network data for this
+              // pass into tracingData.
+              const passName = config.passName || Audit.DEFAULT_PASS;
+              if (config.recordTrace) {
+                tracingData.traces[passName] = passData.trace;
+                tracingData.devtoolsLogs[passName] = passData.devtoolsLog;
+              }
+              config.recordNetwork &&
+                  (tracingData.networkRecords[passName] = passData.networkRecords);
 
+              if (passIndex === 0) {
+                urlAfterRedirects = runOptions.url;
+              }
+            });
+        }, Promise.resolve()).then(_ => {
+          options.url = urlAfterRedirects;
+        });
+      })
+      .then(_ => GatherRunner.disposeDriver(driver))
+      .then(_ => GatherRunner.collectArtifacts(gathererResults))
+      .then(artifacts => {
+        // Add tracing data and computed artifacts to artifacts object.
+        const computedArtifacts = this.instantiateComputedArtifacts();
+        Object.assign(artifacts, computedArtifacts, tracingData);
+        return artifacts;
+      })
+      // cleanup on error
+      .catch(err => {
+        GatherRunner.disposeDriver(driver);
 
+        throw err;
+      });
+  }
 
+  static getGathererClass(nameOrGathererClass, configPath) {
+    const Runner = require('../runner');
+    const coreList = Runner.getGathererList();
 
-class GatherRunner{
+    let GathererClass;
+    if (typeof nameOrGathererClass === 'string') {
+      const name = nameOrGathererClass;
 
+      // See if the gatherer is a Lighthouse core gatherer.
+      const coreGatherer = coreList.find(a => a === `${name}.js`);
+      let requirePath = `./gatherers/${name}`;
+      if (!coreGatherer) {
+        // Otherwise, attempt to find it elsewhere. This throws if not found.
+        requirePath = Runner.resolvePlugin(name, configPath, 'gatherer');
+      }
 
+      GathererClass = require(requirePath);
 
+      this.assertValidGatherer(GathererClass, name);
+    } else {
+      GathererClass = nameOrGathererClass;
+      this.assertValidGatherer(GathererClass);
+    }
 
+    return GathererClass;
+  }
 
+  static assertValidGatherer(GathererDefinition, gathererName) {
+    const gathererInstance = new GathererDefinition();
+    gathererName = gathererName || gathererInstance.name || 'gatherer';
 
+    if (typeof gathererInstance.beforePass !== 'function') {
+      throw new Error(`${gathererName} has no beforePass() method.`);
+    }
 
+    if (typeof gathererInstance.pass !== 'function') {
+      throw new Error(`${gathererName} has no pass() method.`);
+    }
 
+    if (typeof gathererInstance.afterPass !== 'function') {
+      throw new Error(`${gathererName} has no afterPass() method.`);
+    }
+  }
 
+  static instantiateComputedArtifacts() {
+    const computedArtifacts = {};
+    ["computed-artifact.js","critical-request-chains.js","manifest-values.js","network-throughput.js","pushed-requests.js","screenshots.js","speedline.js","trace-of-tab.js","tracing-model.js"].forEach(function(file) {
+      // Drop `.js` suffix to keep browserify import happy.
+      file = file.replace(/\.js$/, '');
+      const ArtifactClass = require('./computed/' + file);
+      const artifact = new ArtifactClass();
+      // define the request* function that will be exposed on `artifacts`
+      computedArtifacts['request' + artifact.name] = artifact.request.bind(artifact);
+    });
+    return computedArtifacts;
+  }
 
-static loadBlank(driver,url='about:blank',duration=300){
-return driver.gotoURL(url).then(_=>new Promise(resolve=>setTimeout(resolve,duration)));
+  static instantiateGatherers(passes, rootPath) {
+    return passes.map(pass => {
+      pass.gatherers = pass.gatherers.map(gatherer => {
+        // If this is already instantiated, don't do anything else.
+        if (typeof gatherer !== 'string') {
+          return gatherer;
+        }
+
+        const GathererClass = GatherRunner.getGathererClass(gatherer, rootPath);
+        return new GathererClass();
+      });
+
+      return pass;
+    });
+  }
 }
 
+module.exports = GatherRunner;
 
-
-
-
-
-
-static loadPage(driver,options){
-return Promise.resolve().
-
-then(_=>options.config.recordTrace&&driver.beginTrace()).
-
-then(_=>driver.beginNetworkCollect(options)).
-
-then(_=>driver.gotoURL(options.url,{
-waitForLoad:true,
-disableJavaScript:!!options.disableJavaScript,
-flags:options.flags}));
-
-}
-
-static setupDriver(driver,options){
-log.log('status','Initializing…');
-const resetStorage=!options.flags.disableStorageReset;
-
-return driver.assertNoSameOriginServiceWorkerClients(options.url).
-then(_=>driver.beginEmulation(options.flags)).
-then(_=>driver.enableRuntimeEvents()).
-then(_=>driver.cacheNatives()).
-then(_=>resetStorage&&driver.cleanAndDisableBrowserCaches()).
-then(_=>resetStorage&&driver.clearDataForOrigin(options.url)).
-then(_=>driver.blockUrlPatterns(options.flags.blockedUrlPatterns||[]));
-}
-
-static disposeDriver(driver){
-
-
-log.log('status','Disconnecting from browser...');
-driver.disconnect().catch(err=>{
-
-
-if(!/close\/.*status: 500$/.test(err.message)){
-log.error('GatherRunner disconnect',err.message);
-}
-});
-}
-
-
-
-
-
-
-
-static recoverOrThrow(promise){
-return promise.catch(err=>{
-if(err.fatal){
-throw err;
-}
-});
-}
-
-
-
-
-
-
-
-static assertPageLoaded(url,driver,networkRecords){
-const mainRecord=networkRecords.find(record=>{
-
-return URL.equalWithExcludedFragments(record.url,url);
-});
-if(driver.online&&(!mainRecord||mainRecord.failed)){
-const message=mainRecord?mainRecord.localizedFailDescription:'timeout reached';
-log.error('GatherRunner',message);
-const error=new Error(`Unable to load the page: ${message}`);
-error.code='PAGE_LOAD_ERROR';
-throw error;
-}
-}
-
-
-
-
-
-
-
-
-static beforePass(options,gathererResults){
-const blankPage=options.config.blankPage;
-const blankDuration=options.config.blankDuration;
-const pass=GatherRunner.loadBlank(options.driver,blankPage,blankDuration);
-
-return options.config.gatherers.reduce((chain,gatherer)=>{
-return chain.then(_=>{
-const artifactPromise=Promise.resolve().then(_=>gatherer.beforePass(options));
-gathererResults[gatherer.name]=[artifactPromise];
-return GatherRunner.recoverOrThrow(artifactPromise);
-});
-},pass);
-}
-
-
-
-
-
-
-
-
-static pass(options,gathererResults){
-const driver=options.driver;
-const config=options.config;
-const gatherers=config.gatherers;
-
-const gatherernames=gatherers.map(g=>g.name).join(', ');
-const status='Loading page & waiting for onload';
-log.log('status',status,gatherernames);
-
-const pass=GatherRunner.loadPage(driver,options).then(_=>{
-log.log('statusEnd',status);
-});
-
-return gatherers.reduce((chain,gatherer)=>{
-return chain.then(_=>{
-const artifactPromise=Promise.resolve().then(_=>gatherer.pass(options));
-gathererResults[gatherer.name].push(artifactPromise);
-return GatherRunner.recoverOrThrow(artifactPromise);
-});
-},pass);
-}
-
-
-
-
-
-
-
-
-
-static afterPass(options,gathererResults){
-const driver=options.driver;
-const config=options.config;
-const gatherers=config.gatherers;
-const passData={};
-
-let pass=Promise.resolve();
-
-if(config.recordTrace){
-pass=pass.then(_=>{
-log.log('status','Retrieving trace');
-return driver.endTrace(config.pauseBeforeTraceEndMs);
-}).then(traceContents=>{
-
-
-
-passData.trace=Array.isArray(traceContents)?
-{traceEvents:traceContents}:traceContents;
-passData.devtoolsLog=driver.devtoolsLog;
-log.verbose('statusEnd','Retrieving trace');
-});
-}
-
-const status='Retrieving network records';
-pass=pass.then(_=>{
-log.log('status',status);
-return driver.endNetworkCollect();
-}).then(networkRecords=>{
-GatherRunner.assertPageLoaded(options.url,driver,networkRecords);
-
-
-config.recordNetwork&&(passData.networkRecords=networkRecords);
-log.verbose('statusEnd',status);
-});
-
-pass=gatherers.reduce((chain,gatherer)=>{
-const status=`Retrieving: ${gatherer.name}`;
-return chain.then(_=>{
-log.log('status',status);
-const artifactPromise=Promise.resolve().then(_=>gatherer.afterPass(options,passData));
-gathererResults[gatherer.name].push(artifactPromise);
-return GatherRunner.recoverOrThrow(artifactPromise);
-}).then(_=>{
-log.verbose('statusEnd',status);
-});
-},pass);
-
-
-return pass.then(_=>passData);
-}
-
-
-
-
-
-
-
-
-
-static collectArtifacts(gathererResults){
-const artifacts={};
-
-return Object.keys(gathererResults).reduce((chain,gathererName)=>{
-return chain.then(_=>{
-const phaseResultsPromises=gathererResults[gathererName];
-return Promise.all(phaseResultsPromises).then(phaseResults=>{
-
-const definedResults=phaseResults.filter(element=>element!==undefined);
-const artifact=definedResults[definedResults.length-1];
-if(artifact===undefined){
-throw new Error(`${gathererName} failed to provide an artifact.`);
-}
-artifacts[gathererName]=artifact;
-},err=>{
-
-
-artifacts[gathererName]=err;
-});
-});
-},Promise.resolve()).then(_=>{
-return artifacts;
-});
-}
-
-static run(passes,options){
-const driver=options.driver;
-const tracingData={
-traces:{},
-devtoolsLogs:{},
-networkRecords:{}};
-
-
-if(typeof options.url!=='string'||options.url.length===0){
-return Promise.reject(new Error('You must provide a url to the gather-runner'));
-}
-
-if(typeof options.flags==='undefined'){
-options.flags={};
-}
-
-if(typeof options.config==='undefined'){
-return Promise.reject(new Error('You must provide a config'));
-}
-
-
-if(typeof options.flags.disableCpuThrottling==='undefined'){
-options.flags.disableCpuThrottling=true;
-}
-
-passes=this.instantiateGatherers(passes,options.config.configDir);
-
-const gathererResults={};
-
-return driver.connect().
-then(_=>GatherRunner.loadBlank(driver)).
-then(_=>GatherRunner.setupDriver(driver,options)).
-
-
-then(_=>{
-
-let urlAfterRedirects;
-return passes.reduce((chain,config,passIndex)=>{
-const runOptions=Object.assign({},options,{config});
-return chain.
-then(_=>driver.setThrottling(options.flags,config)).
-then(_=>GatherRunner.beforePass(runOptions,gathererResults)).
-then(_=>GatherRunner.pass(runOptions,gathererResults)).
-then(_=>GatherRunner.afterPass(runOptions,gathererResults)).
-then(passData=>{
-
-
-const passName=config.passName||Audit.DEFAULT_PASS;
-if(config.recordTrace){
-tracingData.traces[passName]=passData.trace;
-tracingData.devtoolsLogs[passName]=passData.devtoolsLog;
-}
-config.recordNetwork&&(
-tracingData.networkRecords[passName]=passData.networkRecords);
-
-if(passIndex===0){
-urlAfterRedirects=runOptions.url;
-}
-});
-},Promise.resolve()).then(_=>{
-options.url=urlAfterRedirects;
-});
-}).
-then(_=>GatherRunner.disposeDriver(driver)).
-then(_=>GatherRunner.collectArtifacts(gathererResults)).
-then(artifacts=>{
-
-const computedArtifacts=this.instantiateComputedArtifacts();
-Object.assign(artifacts,computedArtifacts,tracingData);
-return artifacts;
-}).
-
-catch(err=>{
-GatherRunner.disposeDriver(driver);
-
-throw err;
-});
-}
-
-static getGathererClass(nameOrGathererClass,configPath){
-const Runner=require('../runner');
-const coreList=Runner.getGathererList();
-
-let GathererClass;
-if(typeof nameOrGathererClass==='string'){
-const name=nameOrGathererClass;
-
-
-const coreGatherer=coreList.find(a=>a===`${name}.js`);
-let requirePath=`./gatherers/${name}`;
-if(!coreGatherer){
-
-requirePath=Runner.resolvePlugin(name,configPath,'gatherer');
-}
-
-GathererClass=require(requirePath);
-
-this.assertValidGatherer(GathererClass,name);
-}else{
-GathererClass=nameOrGathererClass;
-this.assertValidGatherer(GathererClass);
-}
-
-return GathererClass;
-}
-
-static assertValidGatherer(GathererDefinition,gathererName){
-const gathererInstance=new GathererDefinition();
-gathererName=gathererName||gathererInstance.name||'gatherer';
-
-if(typeof gathererInstance.beforePass!=='function'){
-throw new Error(`${gathererName} has no beforePass() method.`);
-}
-
-if(typeof gathererInstance.pass!=='function'){
-throw new Error(`${gathererName} has no pass() method.`);
-}
-
-if(typeof gathererInstance.afterPass!=='function'){
-throw new Error(`${gathererName} has no afterPass() method.`);
-}
-}
-
-static instantiateComputedArtifacts(){
-const computedArtifacts={};
-["computed-artifact.js","critical-request-chains.js","network-throughput.js","pushed-requests.js","screenshots.js","speedline.js","trace-of-tab.js","tracing-model.js"].forEach(function(file){
-
-file=file.replace(/\.js$/,'');
-const ArtifactClass=require('./computed/'+file);
-const artifact=new ArtifactClass();
-
-computedArtifacts['request'+artifact.name]=artifact.request.bind(artifact);
-});
-return computedArtifacts;
-}
-
-static instantiateGatherers(passes,rootPath){
-return passes.map(pass=>{
-pass.gatherers=pass.gatherers.map(gatherer=>{
-
-if(typeof gatherer!=='string'){
-return gatherer;
-}
-
-const GathererClass=GatherRunner.getGathererClass(gatherer,rootPath);
-return new GathererClass();
-});
-
-return pass;
-});
-}}
-
-
-module.exports=GatherRunner;
-
-},{"../audits/audit":3,"../lib/log.js":19,"../lib/url-shim":25,"../runner":32,"path":203}],13:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"../audits/audit":2,"../lib/log.js":19,"../lib/url-shim":25,"../runner":33,"path":220}],13:[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
+/**
+ * Base class for all gatherers; defines pass lifecycle methods. The artifact
+ * from the gatherer is the last not-undefined value returned by a lifecycle
+ * method. All methods can return the artifact value directly or return a
+ * Promise that resolves to that value.
+ *
+ * If an Error is thrown (or a Promise that rejects on an Error), the
+ * GatherRunner will check for a `fatal` property on the Error. If not set to
+ * `true`, the runner will treat it as an error internal to the gatherer and
+ * continue execution of any remaining gatherers.
+ */
+class Gatherer {
+  /**
+   * @return {string}
+   */
+  get name() {
+    return this.constructor.name;
+  }
 
+  /* eslint-disable no-unused-vars */
 
+  /**
+   * Called before navigation to target url.
+   * @param {!Object} options
+   */
+  beforePass(options) { }
 
+  /**
+   * Called after target page is loaded. If a trace is enabled for this pass,
+   * the trace is still being recorded.
+   * @param {!Object} options
+   */
+  pass(options) { }
 
+  /**
+   * Called after target page is loaded, all gatherer `pass` methods have been
+   * executed, and — if generated in this pass — the trace is ended. The trace
+   * and record of network activity are provided in `loadData`.
+   * @param {!Object} options
+   * @param {networkRecords: !Array, trace: {traceEvents: !Array}} loadData
+   * @return {*|!Promise<*>}
+   */
+  afterPass(options, loadData) { }
 
+  /* eslint-enable no-unused-vars */
 
-
-
-
-
-
-class Gatherer{
-
-
-
-get name(){
-return this.constructor.name;
 }
 
-
-
-
-
-
-
-beforePass(options){}
-
-
-
-
-
-
-pass(options){}
-
-
-
-
-
-
-
-
-
-afterPass(options,loadData){}}
-
-
-
-
-
-module.exports=Gatherer;
+module.exports = Gatherer;
 
 },{}],14:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
+/* eslint-disable no-console */
 
+const log = require('./log.js');
 
-const log=require('./log.js');
+class ConsoleQuieter {
 
-class ConsoleQuieter{
+  static mute(opts) {
+    ConsoleQuieter._logs = ConsoleQuieter._logs || [];
 
-static mute(opts){
-ConsoleQuieter._logs=ConsoleQuieter._logs||[];
+    console.log = function(...args) {
+      ConsoleQuieter._logs.push({type: 'log', args, prefix: opts.prefix});
+    };
+    console.warn = function(...args) {
+      ConsoleQuieter._logs.push({type: 'warn', args, prefix: opts.prefix});
+    };
+    console.error = function(...args) {
+      ConsoleQuieter._logs.push({type: 'error', args, prefix: opts.prefix});
+    };
+  }
 
-console.log=function(...args){
-ConsoleQuieter._logs.push({type:'log',args,prefix:opts.prefix});
-};
-console.warn=function(...args){
-ConsoleQuieter._logs.push({type:'warn',args,prefix:opts.prefix});
-};
-console.error=function(...args){
-ConsoleQuieter._logs.push({type:'error',args,prefix:opts.prefix});
-};
+  static unmuteAndFlush() {
+    console.log = ConsoleQuieter._consolelog;
+    console.warn = ConsoleQuieter._consolewarn;
+    console.error = ConsoleQuieter._consoleerror;
+
+    ConsoleQuieter._logs.forEach(entry => {
+      log.verbose(`${entry.prefix}-${entry.type}`, ...entry.args);
+    });
+    ConsoleQuieter._logs = [];
+  }
 }
 
-static unmuteAndFlush(){
-console.log=ConsoleQuieter._consolelog;
-console.warn=ConsoleQuieter._consolewarn;
-console.error=ConsoleQuieter._consoleerror;
+ConsoleQuieter._consolelog = console.log.bind(console);
+ConsoleQuieter._consolewarn = console.warn.bind(console);
+ConsoleQuieter._consoleerror = console.error.bind(console);
 
-ConsoleQuieter._logs.forEach(entry=>{
-log.verbose(`${entry.prefix}-${entry.type}`,...entry.args);
-});
-ConsoleQuieter._logs=[];
-}}
-
-
-ConsoleQuieter._consolelog=console.log.bind(console);
-ConsoleQuieter._consolewarn=console.warn.bind(console);
-ConsoleQuieter._consoleerror=console.error.bind(console);
-
-module.exports=ConsoleQuieter;
+module.exports = ConsoleQuieter;
 
 },{"./log.js":19}],15:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-class Element{
+class Element {
 
-constructor(element,driver){
-if(!element||!driver){
-throw Error('Driver and element required to create Element');
-}
-this.driver=driver;
-this.element=element;
+  constructor(element, driver) {
+    if (!element || !driver) {
+      throw Error('Driver and element required to create Element');
+    }
+    this.driver = driver;
+    this.element = element;
+  }
+
+  /**
+   * @param {!string} name Attribute name
+   * @return {!Promise<?string>} The attribute value or null if not found
+   */
+  getAttribute(name) {
+    return this.driver
+      .sendCommand('DOM.getAttributes', {
+        nodeId: this.element.nodeId
+      })
+      /**
+       * @param {!{attributes: !Array<!string>}} resp The element attribute names & values are interleaved
+       */
+      .then(resp => {
+        const attrIndex = resp.attributes.indexOf(name);
+        if (attrIndex === -1) {
+          return null;
+        }
+
+        return resp.attributes[attrIndex + 1];
+      });
+  }
+
+  /**
+   * @param {!string} propName Property name
+   * @return {!Promise<?string>} The property value
+   */
+  getProperty(propName) {
+    return this.driver
+      .sendCommand('DOM.resolveNode', {
+        nodeId: this.element.nodeId
+      })
+      .then(resp => {
+        return this.driver.getObjectProperty(resp.object.objectId, propName);
+      });
+  }
 }
 
-
-
-
-
-getAttribute(name){
-return this.driver.
-sendCommand('DOM.getAttributes',{
-nodeId:this.element.nodeId}).
-
-
-
-
-then(resp=>{
-const attrIndex=resp.attributes.indexOf(name);
-if(attrIndex===-1){
-return null;
-}
-
-return resp.attributes[attrIndex+1];
-});
-}
-
-
-
-
-
-getProperty(propName){
-return this.driver.
-sendCommand('DOM.resolveNode',{
-nodeId:this.element.nodeId}).
-
-then(resp=>{
-return this.driver.getObjectProperty(resp.object.objectId,propName);
-});
-}}
-
-
-module.exports=Element;
+module.exports = Element;
 
 },{}],16:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-
-
-
-const NEXUS5X_EMULATION_METRICS={
-mobile:true,
-screenWidth:412,
-screenHeight:732,
-width:412,
-height:732,
-positionX:0,
-positionY:0,
-scale:1,
-deviceScaleFactor:2.625,
-fitWindow:false,
-screenOrientation:{
-angle:0,
-type:'portraitPrimary'}};
-
-
-
-const NEXUS5X_USERAGENT={
-userAgent:'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5 Build/MRA58N) AppleWebKit/537.36'+
-'(KHTML, like Gecko) Chrome/59.0.3033.0 Mobile Safari/537.36'};
-
-
-const TYPICAL_MOBILE_THROTTLING_METRICS={
-latency:150,
-downloadThroughput:Math.floor(1.6*1024*1024/8),
-uploadThroughput:Math.floor(750*1024/8),
-offline:false};
-
-
-const OFFLINE_METRICS={
-offline:true,
-
-latency:0,
-downloadThroughput:0,
-uploadThroughput:0};
-
-
-const NO_THROTTLING_METRICS={
-latency:0,
-downloadThroughput:0,
-uploadThroughput:0,
-offline:false};
-
-
-const NO_CPU_THROTTLE_METRICS={
-rate:1};
-
-const CPU_THROTTLE_METRICS={
-rate:5};
-
-
-function enableNexus5X(driver){
-
-
-
-
-
-
-
-const injectedTouchEventsFunction=function(){
-const touchEvents=['ontouchstart','ontouchend','ontouchmove','ontouchcancel'];
-const recepients=[window.__proto__,document.__proto__];
-for(let i=0;i<touchEvents.length;++i){
-for(let j=0;j<recepients.length;++j){
-if(!(touchEvents[i]in recepients[j])){
-Object.defineProperty(recepients[j],touchEvents[i],{
-value:null,writable:true,configurable:true,enumerable:true});
-
-}
-}
-}
+/**
+ * Nexus 5X metrics adapted from emulated_devices/module.json
+ */
+const NEXUS5X_EMULATION_METRICS = {
+  mobile: true,
+  screenWidth: 412,
+  screenHeight: 732,
+  width: 412,
+  height: 732,
+  positionX: 0,
+  positionY: 0,
+  scale: 1,
+  deviceScaleFactor: 2.625,
+  fitWindow: false,
+  screenOrientation: {
+    angle: 0,
+    type: 'portraitPrimary'
+  }
 };
 
+const NEXUS5X_USERAGENT = {
+  userAgent: 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5 Build/MRA58N) AppleWebKit/537.36' +
+    '(KHTML, like Gecko) Chrome/59.0.3033.0 Mobile Safari/537.36'
+};
 
-return Promise.all([
-driver.sendCommand('Emulation.setDeviceMetricsOverride',NEXUS5X_EMULATION_METRICS),
+const TYPICAL_MOBILE_THROTTLING_METRICS = {
+  latency: 150, // 150ms
+  downloadThroughput: Math.floor(1.6 * 1024 * 1024 / 8), // 1.6Mbps
+  uploadThroughput: Math.floor(750 * 1024 / 8), // 750Kbps
+  offline: false
+};
 
-driver.sendCommand('Emulation.setVisibleSize',{
-width:NEXUS5X_EMULATION_METRICS.screenWidth,
-height:NEXUS5X_EMULATION_METRICS.screenHeight}),
+const OFFLINE_METRICS = {
+  offline: true,
+  // values of 0 remove any active throttling. crbug.com/456324#c9
+  latency: 0,
+  downloadThroughput: 0,
+  uploadThroughput: 0
+};
 
+const NO_THROTTLING_METRICS = {
+  latency: 0,
+  downloadThroughput: 0,
+  uploadThroughput: 0,
+  offline: false
+};
 
-driver.sendCommand('Network.enable'),
-driver.sendCommand('Network.setUserAgentOverride',NEXUS5X_USERAGENT),
-driver.sendCommand('Emulation.setTouchEmulationEnabled',{
-enabled:true,
-configuration:'mobile'}),
+const NO_CPU_THROTTLE_METRICS = {
+  rate: 1
+};
+const CPU_THROTTLE_METRICS = {
+  rate: 4
+};
 
-driver.sendCommand('Page.addScriptToEvaluateOnLoad',{
-scriptSource:'('+injectedTouchEventsFunction.toString()+')()'})]);
+function enableNexus5X(driver) {
+  /**
+   * Finalizes touch emulation by enabling `"ontouchstart" in window` feature detect
+   * to work. Messy hack, though copied verbatim from DevTools' emulation/TouchModel.js
+   * where it's been working for years. addScriptToEvaluateOnLoad runs before any of the
+   * page's JavaScript executes.
+   */
+  /* eslint-disable no-proto */ /* global window, document */ /* istanbul ignore next */
+  const injectedTouchEventsFunction = function() {
+    const touchEvents = ['ontouchstart', 'ontouchend', 'ontouchmove', 'ontouchcancel'];
+    const recepients = [window.__proto__, document.__proto__];
+    for (let i = 0; i < touchEvents.length; ++i) {
+      for (let j = 0; j < recepients.length; ++j) {
+        if (!(touchEvents[i] in recepients[j])) {
+          Object.defineProperty(recepients[j], touchEvents[i], {
+            value: null, writable: true, configurable: true, enumerable: true
+          });
+        }
+      }
+    }
+  };
+  /* eslint-enable */
 
-
+  return Promise.all([
+    driver.sendCommand('Emulation.setDeviceMetricsOverride', NEXUS5X_EMULATION_METRICS),
+    // required for screenshotting emulated page size, rather than full size
+    driver.sendCommand('Emulation.setVisibleSize', {
+      width: NEXUS5X_EMULATION_METRICS.screenWidth,
+      height: NEXUS5X_EMULATION_METRICS.screenHeight
+    }),
+    // Network.enable must be called for UA overriding to work
+    driver.sendCommand('Network.enable'),
+    driver.sendCommand('Network.setUserAgentOverride', NEXUS5X_USERAGENT),
+    driver.sendCommand('Emulation.setTouchEmulationEnabled', {
+      enabled: true,
+      configuration: 'mobile'
+    }),
+    driver.sendCommand('Page.addScriptToEvaluateOnLoad', {
+      scriptSource: '(' + injectedTouchEventsFunction.toString() + ')()'
+    })
+  ]);
 }
 
-function enableNetworkThrottling(driver){
-return driver.sendCommand('Network.emulateNetworkConditions',TYPICAL_MOBILE_THROTTLING_METRICS);
+function enableNetworkThrottling(driver) {
+  return driver.sendCommand('Network.emulateNetworkConditions', TYPICAL_MOBILE_THROTTLING_METRICS);
 }
 
-function disableNetworkThrottling(driver){
-return driver.sendCommand('Network.emulateNetworkConditions',NO_THROTTLING_METRICS);
+function disableNetworkThrottling(driver) {
+  return driver.sendCommand('Network.emulateNetworkConditions', NO_THROTTLING_METRICS);
 }
 
-function goOffline(driver){
-return driver.sendCommand('Network.emulateNetworkConditions',OFFLINE_METRICS);
+function goOffline(driver) {
+  return driver.sendCommand('Network.emulateNetworkConditions', OFFLINE_METRICS);
 }
 
-function enableCPUThrottling(driver){
-return driver.sendCommand('Emulation.setCPUThrottlingRate',CPU_THROTTLE_METRICS);
+function enableCPUThrottling(driver) {
+  return driver.sendCommand('Emulation.setCPUThrottlingRate', CPU_THROTTLE_METRICS);
 }
 
-function disableCPUThrottling(driver){
-return driver.sendCommand('Emulation.setCPUThrottlingRate',NO_CPU_THROTTLE_METRICS);
+function disableCPUThrottling(driver) {
+  return driver.sendCommand('Emulation.setCPUThrottlingRate', NO_CPU_THROTTLE_METRICS);
 }
 
-function getEmulationDesc(){
-const{latency,downloadThroughput,uploadThroughput}=TYPICAL_MOBILE_THROTTLING_METRICS;
-const byteToMbit=bytes=>(bytes/1024/1024*8).toFixed(1);
-return{
-'deviceEmulation':'Nexus 5X',
-'cpuThrottling':`${CPU_THROTTLE_METRICS.rate}x slowdown`,
-'networkThrottling':`${latency}ms RTT, ${byteToMbit(downloadThroughput)}Mbps down, `+
-`${byteToMbit(uploadThroughput)}Mbps up`};
-
+function getEmulationDesc() {
+  const {latency, downloadThroughput, uploadThroughput} = TYPICAL_MOBILE_THROTTLING_METRICS;
+  const byteToMbit = bytes => (bytes / 1024 / 1024 * 8).toFixed(1);
+  return {
+    'deviceEmulation': 'Nexus 5X',
+    'cpuThrottling': `${CPU_THROTTLE_METRICS.rate}x slowdown`,
+    'networkThrottling': `${latency}ms RTT, ${byteToMbit(downloadThroughput)}Mbps down, ` +
+        `${byteToMbit(uploadThroughput)}Mbps up`
+  };
 }
 
-module.exports={
-enableNexus5X,
-enableNetworkThrottling,
-disableNetworkThrottling,
-enableCPUThrottling,
-disableCPUThrottling,
-goOffline,
-getEmulationDesc};
-
+module.exports = {
+  enableNexus5X,
+  enableNetworkThrottling,
+  disableNetworkThrottling,
+  enableCPUThrottling,
+  disableCPUThrottling,
+  goOffline,
+  getEmulationDesc,
+  settings: {
+    NEXUS5X_EMULATION_METRICS,
+    NEXUS5X_USERAGENT,
+    TYPICAL_MOBILE_THROTTLING_METRICS,
+    OFFLINE_METRICS,
+    NO_THROTTLING_METRICS,
+    NO_CPU_THROTTLE_METRICS,
+    CPU_THROTTLE_METRICS
+  }
+};
 
 },{}],17:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-
-
-
-
-
-
-
-
-
-function addFormattedCodeSnippet(listener){
-const handler=listener.handler?listener.handler.description:'...';
-const objectName=listener.objectName.toLowerCase().replace('#document','document');
-return Object.assign({
-label:`line: ${listener.line}, col: ${listener.col}`,
-pre:`${objectName}.addEventListener('${listener.type}', ${handler})`},
-listener);
+/**
+ * Adds line/col information to an event listener object along with a formatted
+ * code snippet of violation.
+ *
+ * @param {!Object} listener A modified EventListener object as returned
+ *     by the driver in the all events gatherer.
+ * @return {!Object} A copy of the original listener object with the added
+ *     properties.
+ */
+function addFormattedCodeSnippet(listener) {
+  const handler = listener.handler ? listener.handler.description : '...';
+  const objectName = listener.objectName.toLowerCase().replace('#document', 'document');
+  return Object.assign({
+    label: `line: ${listener.line}, col: ${listener.col}`,
+    pre: `${objectName}.addEventListener('${listener.type}', ${handler})`
+  }, listener);
 }
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * Groups event listeners under url/line/col "violation buckets".
+ *
+ * The listener gatherer returns a list of (url/line/col) src locations where
+ * event handlers were attached to DOM nodes. This location is where
+ * addEventListener was invoked, but it's not guaranteed to be where
+ * the user's event handler was defined. An example is libraries, where the
+ * user provides a callback and the library calls addEventListener (another
+ * part of the codebase). Instead we map url/line/col/type to array of event
+ * handlers so the user doesn't see a redundant list of url/line/col from the
+ * same location.
+ *
+ * @param {!Array<!Object>} listeners Results from the event listener gatherer.
+ * @return {!Array<{line: number, col: number, url: string, type: string, pre: string, label: string}>}
+ *     A list of slimmed down listener objects.
+ */
+function groupCodeSnippetsByLocation(listeners) {
+  const locToListenersMap = new Map();
+  listeners.forEach(loc => {
+    const key = JSON.stringify({line: loc.line, col: loc.col, url: loc.url, type: loc.type});
+    if (locToListenersMap.has(key)) {
+      locToListenersMap.get(key).push(loc);
+    } else {
+      locToListenersMap.set(key, [loc]);
+    }
+  });
 
+  const results = [];
+  locToListenersMap.forEach((listenersForLocation, key) => {
+    const lineColUrlObj = JSON.parse(key);
+    // Aggregate the code snippets.
+    const codeSnippets = listenersForLocation.reduce((prev, loc) => {
+      return prev + loc.pre.trim() + '\n\n';
+    }, '');
+    lineColUrlObj.pre = codeSnippets;
+    // All listeners under this bucket have the same line/col. We use the first's
+    // label as the label for all of them.
+    lineColUrlObj.label = listenersForLocation[0].label;
+    results.push(lineColUrlObj);
+  });
 
-function groupCodeSnippetsByLocation(listeners){
-const locToListenersMap=new Map();
-listeners.forEach(loc=>{
-const key=JSON.stringify({line:loc.line,col:loc.col,url:loc.url,type:loc.type});
-if(locToListenersMap.has(key)){
-locToListenersMap.get(key).push(loc);
-}else{
-locToListenersMap.set(key,[loc]);
+  return results;
 }
-});
 
-const results=[];
-locToListenersMap.forEach((listenersForLocation,key)=>{
-const lineColUrlObj=JSON.parse(key);
-
-const codeSnippets=listenersForLocation.reduce((prev,loc)=>{
-return prev+loc.pre.trim()+'\n\n';
-},'');
-lineColUrlObj.pre=codeSnippets;
-
-
-lineColUrlObj.label=listenersForLocation[0].label;
-results.push(lineColUrlObj);
-});
-
-return results;
-}
-
-module.exports={
-addFormattedCodeSnippet,
-groupCodeSnippetsByLocation};
-
-
-},{}],18:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-'use strict';
-
-
-
-
-
-function doExist(manifest){
-if(!manifest||!manifest.icons){
-return false;
-}
-if(manifest.icons.value.length===0){
-return false;
-}
-return true;
-}
-
-
-
-
-
-
-function sizeAtLeast(sizeRequirement,manifest){
-
-
-const iconValues=manifest.icons.value;
-const nestedSizes=iconValues.map(icon=>icon.value.sizes.value);
-const flattenedSizes=[].concat(...nestedSizes);
-
-return flattenedSizes.
-
-filter(size=>typeof size==='string').
-
-filter(size=>/\d+x\d+/.test(size)).
-filter(size=>{
-
-const sizeStrs=size.split(/x/i);
-
-const sizeNums=[parseFloat(sizeStrs[0]),parseFloat(sizeStrs[1])];
-
-const areIconsBigEnough=sizeNums[0]>=sizeRequirement&&sizeNums[1]>=sizeRequirement;
-
-const areIconsSquare=sizeNums[0]===sizeNums[1];
-return areIconsBigEnough&&areIconsSquare;
-});
-}
-
-module.exports={
-doExist,
-sizeAtLeast};
-
-
-},{}],19:[function(require,module,exports){
-(function(process){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-'use strict';
-
-const debug=require('debug');
-const EventEmitter=require('events').EventEmitter;
-const isWindows=process.platform==='win32';
-
-
-const isBrowser=process.browser;
-
-const colors={
-red:isBrowser?'crimson':1,
-yellow:isBrowser?'gold':3,
-cyan:isBrowser?'darkturquoise':6,
-green:isBrowser?'forestgreen':2,
-blue:isBrowser?'steelblue':4,
-magenta:isBrowser?'palevioletred':5};
-
-
-
-debug.colors=[colors.cyan,colors.green,colors.blue,colors.magenta];
-
-class Emitter extends EventEmitter{
-
-
-
-
-
-
-issueStatus(title,argsArray){
-if(title==='status'||title==='statusEnd'){
-this.emit(title,[title,...argsArray]);
-}
-}
-
-
-
-
-
-
-
-issueWarning(title,argsArray){
-this.emit('warning',[title,...argsArray]);
-}}
-
-
-const loggersByTitle={};
-const loggingBufferColumns=25;
-
-class Log{
-
-static _logToStdErr(title,argsArray){
-const log=Log.loggerfn(title);
-log(...argsArray);
-}
-
-static loggerfn(title){
-let log=loggersByTitle[title];
-if(!log){
-log=debug(title);
-loggersByTitle[title]=log;
-
-if(title.endsWith('error')){
-log.color=colors.red;
-}else if(title.endsWith('warn')){
-log.color=colors.yellow;
-}
-}
-return log;
-}
-
-static setLevel(level){
-switch(level){
-case'silent':
-debug.enable('-*');
-break;
-case'verbose':
-debug.enable('*');
-break;
-case'error':
-debug.enable('-*, *:error');
-break;
-default:
-debug.enable('*, -*:verbose');}
-
-}
-
-
-
-
-
-
-
-static formatProtocol(prefix,data,level){
-const columns=!process||process.browser?Infinity:process.stdout.columns;
-const maxLength=columns-data.method.length-prefix.length-loggingBufferColumns;
-
-const snippet=data.params&&data.method!=='IO.read'?
-JSON.stringify(data.params).substr(0,maxLength):'';
-Log._logToStdErr(`${prefix}:${level||''}`,[data.method,snippet]);
-}
-
-static log(title,...args){
-Log.events.issueStatus(title,args);
-return Log._logToStdErr(title,args);
-}
-
-static warn(title,...args){
-Log.events.issueWarning(title,args);
-return Log._logToStdErr(`${title}:warn`,args);
-}
-
-static error(title,...args){
-return Log._logToStdErr(`${title}:error`,args);
-}
-
-static verbose(title,...args){
-Log.events.issueStatus(title,args);
-return Log._logToStdErr(`${title}:verbose`,args);
-}
-
-
-
-
-
-
-static greenify(str){
-return`${Log.green}${str}${Log.reset}`;
-}
-
-
-
-
-
-
-static redify(str){
-return`${Log.red}${str}${Log.reset}`;
-}
-
-static get green(){
-return'\x1B[32m';
-}
-
-static get red(){
-return'\x1B[31m';
-}
-
-static get yellow(){
-return'\x1b[33m';
-}
-
-static get purple(){
-return'\x1b[95m';
-}
-
-static get reset(){
-return'\x1B[0m';
-}
-
-static get bold(){
-return'\x1b[1m';
-}
-
-static get tick(){
-return isWindows?'\u221A':'✓';
-}
-
-static get cross(){
-return isWindows?'\u00D7':'✘';
-}
-
-static get whiteSmallSquare(){
-return isWindows?'\u0387':'▫';
-}
-
-static get heavyHorizontal(){
-return isWindows?'\u2500':'━';
-}
-
-static get heavyVertical(){
-return isWindows?'\u2502 ':'┃ ';
-}
-
-static get heavyUpAndRight(){
-return isWindows?'\u2514':'┗';
-}
-
-static get heavyVerticalAndRight(){
-return isWindows?'\u251C':'┣';
-}
-
-static get heavyDownAndHorizontal(){
-return isWindows?'\u252C':'┳';
-}
-
-static get doubleLightHorizontal(){
-return'──';
-}}
-
-
-Log.events=new Emitter();
-
-module.exports=Log;
-
-}).call(this,require('_process'));
-},{"_process":204,"debug":233,"events":200}],20:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-'use strict';
-
-const URL=require('./url-shim');
-const validateColor=require('./web-inspector').Color.parse;
-
-const ALLOWED_DISPLAY_VALUES=[
-'fullscreen',
-'standalone',
-'minimal-ui',
-'browser'];
-
-
-
-
-
-const DEFAULT_DISPLAY_MODE='browser';
-
-const ALLOWED_ORIENTATION_VALUES=[
-'any',
-'natural',
-'landscape',
-'portrait',
-'portrait-primary',
-'portrait-secondary',
-'landscape-primary',
-'landscape-secondary'];
-
-
-function parseString(raw,trim){
-let value;
-let debugString;
-
-if(typeof raw==='string'){
-value=trim?raw.trim():raw;
-}else{
-if(raw!==undefined){
-debugString='ERROR: expected a string.';
-}
-value=undefined;
-}
-
-return{
-raw,
-value,
-debugString};
-
-}
-
-function parseColor(raw){
-const color=parseString(raw);
-
-
-if(color.value===undefined){
-return color;
-}
-
-
-const validatedColor=validateColor(color.raw);
-if(!validatedColor){
-color.value=undefined;
-color.debugString='ERROR: color parsing failed.';
-}
-
-return color;
-}
-
-function parseName(jsonInput){
-return parseString(jsonInput.name,true);
-}
-
-function parseShortName(jsonInput){
-return parseString(jsonInput.short_name,true);
-}
-
-
-
-
-
-
-
-function checkSameOrigin(url1,url2){
-const parsed1=new URL(url1);
-const parsed2=new URL(url2);
-
-return parsed1.origin===parsed2.origin;
-}
-
-
-
-
-function parseStartUrl(jsonInput,manifestUrl,documentUrl){
-const raw=jsonInput.start_url;
-
-
-if(raw===''){
-return{
-raw,
-value:documentUrl,
-debugString:'ERROR: start_url string empty'};
-
-}
-const parsedAsString=parseString(raw);
-if(!parsedAsString.value){
-parsedAsString.value=documentUrl;
-return parsedAsString;
-}
-
-
-let startUrl;
-try{
-startUrl=new URL(raw,manifestUrl).href;
-}catch(e){
-
-return{
-raw,
-value:documentUrl,
-debugString:'ERROR: invalid start_url relative to ${manifestUrl}'};
-
-}
-
-
-if(!checkSameOrigin(startUrl,documentUrl)){
-return{
-raw,
-value:documentUrl,
-debugString:'ERROR: start_url must be same-origin as document'};
-
-}
-
-return{
-raw,
-value:startUrl};
-
-}
-
-function parseDisplay(jsonInput){
-const display=parseString(jsonInput.display,true);
-
-if(!display.value){
-display.value=DEFAULT_DISPLAY_MODE;
-return display;
-}
-
-display.value=display.value.toLowerCase();
-if(!ALLOWED_DISPLAY_VALUES.includes(display.value)){
-display.debugString='ERROR: \'display\' has invalid value '+display.value+
-` will fall back to ${DEFAULT_DISPLAY_MODE}.`;
-display.value=DEFAULT_DISPLAY_MODE;
-}
-
-return display;
-}
-
-function parseOrientation(jsonInput){
-const orientation=parseString(jsonInput.orientation,true);
-
-if(orientation.value&&
-!ALLOWED_ORIENTATION_VALUES.includes(orientation.value.toLowerCase())){
-orientation.value=undefined;
-orientation.debugString='ERROR: \'orientation\' has an invalid value, will be ignored.';
-}
-
-return orientation;
-}
-
-function parseIcon(raw,manifestUrl){
-
-const src=parseString(raw.src,true);
-
-if(src.value===''){
-src.value=undefined;
-}
-if(src.value){
-
-src.value=new URL(src.value,manifestUrl).href;
-}
-
-const type=parseString(raw.type,true);
-
-const density={
-raw:raw.density,
-value:1,
-debugString:undefined};
-
-if(density.raw!==undefined){
-density.value=parseFloat(density.raw);
-if(isNaN(density.value)||!isFinite(density.value)||density.value<=0){
-density.value=1;
-density.debugString='ERROR: icon density cannot be NaN, +∞, or less than or equal to +0.';
-}
-}
-
-const sizes=parseString(raw.sizes);
-if(sizes.value!==undefined){
-const set=new Set();
-sizes.value.trim().split(/\s+/).forEach(size=>set.add(size.toLowerCase()));
-sizes.value=set.size>0?Array.from(set):undefined;
-}
-
-return{
-raw,
-value:{
-src,
-type,
-density,
-sizes},
-
-debugString:undefined};
-
-}
-
-function parseIcons(jsonInput,manifestUrl){
-const raw=jsonInput.icons;
-
-if(raw===undefined){
-return{
-raw,
-value:[],
-debugString:undefined};
-
-}
-
-if(!Array.isArray(raw)){
-return{
-raw,
-value:[],
-debugString:'ERROR: \'icons\' expected to be an array but is not.'};
-
-}
-
-
-
-const value=raw.
-
-filter(icon=>icon.src!==undefined).
-
-map(icon=>parseIcon(icon,manifestUrl)).
-
-filter(parsedIcon=>parsedIcon.value.src.value!==undefined);
-
-return{
-raw,
-value,
-debugString:undefined};
-
-}
-
-function parseApplication(raw){
-const platform=parseString(raw.platform,true);
-const id=parseString(raw.id,true);
-
-
-const appUrl=parseString(raw.url,true);
-if(appUrl.value){
-try{
-
-appUrl.value=new URL(appUrl.value).href;
-}catch(e){
-appUrl.value=undefined;
-appUrl.debugString='ERROR: invalid application URL ${raw.url}';
-}
-}
-
-return{
-raw,
-value:{
-platform,
-id,
-url:appUrl},
-
-debugString:undefined};
-
-}
-
-function parseRelatedApplications(jsonInput){
-const raw=jsonInput.related_applications;
-
-if(raw===undefined){
-return{
-raw,
-value:undefined,
-debugString:undefined};
-
-}
-
-if(!Array.isArray(raw)){
-return{
-raw,
-value:undefined,
-debugString:'ERROR: \'related_applications\' expected to be an array but is not.'};
-
-}
-
-
-
-const value=raw.
-filter(application=>!!application.platform).
-map(parseApplication).
-filter(parsedApp=>!!parsedApp.value.id.value||!!parsedApp.value.url.value);
-
-return{
-raw,
-value,
-debugString:undefined};
-
-}
-
-function parsePreferRelatedApplications(jsonInput){
-const raw=jsonInput.prefer_related_applications;
-let value;
-let debugString;
-
-if(typeof raw==='boolean'){
-value=raw;
-}else{
-if(raw!==undefined){
-debugString='ERROR: \'prefer_related_applications\' expected to be a boolean.';
-}
-value=undefined;
-}
-
-return{
-raw,
-value,
-debugString};
-
-}
-
-function parseThemeColor(jsonInput){
-return parseColor(jsonInput.theme_color);
-}
-
-function parseBackgroundColor(jsonInput){
-return parseColor(jsonInput.background_color);
-}
-
-
-
-
-
-
-
-
-function parse(string,manifestUrl,documentUrl){
-if(manifestUrl===undefined||documentUrl===undefined){
-throw new Error('Manifest and document URLs required for manifest parsing.');
-}
-
-let jsonInput;
-
-try{
-jsonInput=JSON.parse(string);
-}catch(e){
-return{
-raw:string,
-value:undefined,
-debugString:'ERROR: file isn\'t valid JSON: '+e};
-
-}
-
-
-const manifest={
-name:parseName(jsonInput),
-short_name:parseShortName(jsonInput),
-start_url:parseStartUrl(jsonInput,manifestUrl,documentUrl),
-display:parseDisplay(jsonInput),
-orientation:parseOrientation(jsonInput),
-icons:parseIcons(jsonInput,manifestUrl),
-related_applications:parseRelatedApplications(jsonInput),
-prefer_related_applications:parsePreferRelatedApplications(jsonInput),
-theme_color:parseThemeColor(jsonInput),
-background_color:parseBackgroundColor(jsonInput)};
-
-
-
-return{
-raw:string,
-value:manifest,
-debugString:undefined};
-
-}
-
-module.exports=parse;
-
-},{"./url-shim":25,"./web-inspector":26}],21:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-'use strict';
-
-const NetworkManager=require('./web-inspector').NetworkManager;
-const EventEmitter=require('events').EventEmitter;
-const log=require('../lib/log.js');
-
-class NetworkRecorder extends EventEmitter{
-constructor(recordArray){
-super();
-
-this._records=recordArray;
-this.networkManager=NetworkManager.createWithFakeTarget();
-
-this.startedRequestCount=0;
-this.finishedRequestCount=0;
-
-this.networkManager.addEventListener(this.EventTypes.RequestStarted,
-this.onRequestStarted.bind(this));
-this.networkManager.addEventListener(this.EventTypes.RequestFinished,
-this.onRequestFinished.bind(this));
-
-this.onRequestWillBeSent=this.onRequestWillBeSent.bind(this);
-this.onRequestServedFromCache=this.onRequestServedFromCache.bind(this);
-this.onResponseReceived=this.onResponseReceived.bind(this);
-this.onDataReceived=this.onDataReceived.bind(this);
-this.onLoadingFinished=this.onLoadingFinished.bind(this);
-this.onLoadingFailed=this.onLoadingFailed.bind(this);
-this.onResourceChangedPriority=this.onResourceChangedPriority.bind(this);
-}
-
-get EventTypes(){
-return NetworkManager.Events;
-}
-
-activeRequestCount(){
-return this.startedRequestCount-this.finishedRequestCount;
-}
-
-isIdle(){
-return this.activeRequestCount()===0;
-}
-
-
-
-
-
-
-onRequestStarted(){
-this.startedRequestCount++;
-
-const activeCount=this.activeRequestCount();
-log.verbose('NetworkRecorder',`Request started. ${activeCount} requests in progress`+
-` (${this.startedRequestCount} started and ${this.finishedRequestCount} finished).`);
-
-
-
-if(activeCount===1){
-this.emit('networkbusy');
-}
-}
-
-
-
-
-
-
-
-
-onRequestFinished(request){
-this.finishedRequestCount++;
-this._records.push(request.data);
-this.emit('requestloaded',request.data);
-
-const activeCount=this.activeRequestCount();
-log.verbose('NetworkRecorder',`Request finished. ${activeCount} requests in progress`+
-` (${this.startedRequestCount} started and ${this.finishedRequestCount} finished).`);
-
-
-
-if(this.isIdle()){
-this.emit('networkidle');
-}
-}
-
-
-
-
-onRequestWillBeSent(data){
-
-this.networkManager._dispatcher.requestWillBeSent(data.requestId,
-data.frameId,data.loaderId,data.documentURL,data.request,
-data.timestamp,data.wallTime,data.initiator,data.redirectResponse,
-data.type);
-}
-
-onRequestServedFromCache(data){
-this.networkManager._dispatcher.requestServedFromCache(data.requestId);
-}
-
-onResponseReceived(data){
-
-this.networkManager._dispatcher.responseReceived(data.requestId,
-data.frameId,data.loaderId,data.timestamp,data.type,data.response);
-}
-
-onDataReceived(data){
-
-this.networkManager._dispatcher.dataReceived(data.requestId,data.timestamp,
-data.dataLength,data.encodedDataLength);
-}
-
-onLoadingFinished(data){
-
-this.networkManager._dispatcher.loadingFinished(data.requestId,
-data.timestamp,data.encodedDataLength);
-}
-
-onLoadingFailed(data){
-
-
-this.networkManager._dispatcher.loadingFailed(data.requestId,
-data.timestamp,data.type,data.errorText,data.canceled,
-data.blockedReason);
-}
-
-onResourceChangedPriority(data){
-this.networkManager._dispatcher.resourceChangedPriority(data.requestId,
-data.newPriority,data.timestamp);
-}
-
-static recordsFromLogs(logs){
-const records=[];
-const nr=new NetworkRecorder(records);
-const dispatcher=method=>{
-switch(method){
-case'Network.requestWillBeSent':return nr.onRequestWillBeSent;
-case'Network.requestServedFromCache':return nr.onRequestServedFromCache;
-case'Network.responseReceived':return nr.onResponseReceived;
-case'Network.dataReceived':return nr.onDataReceived;
-case'Network.loadingFinished':return nr.onLoadingFinished;
-case'Network.loadingFailed':return nr.onLoadingFailed;
-case'Network.resourceChangedPriority':return nr.onResourceChangedPriority;
-default:return()=>{};}
-
+module.exports = {
+  addFormattedCodeSnippet,
+  groupCodeSnippetsByLocation
 };
 
-logs.forEach(networkEvent=>{
-dispatcher(networkEvent.method)(networkEvent.params);
-});
-
-return records;
-}}
-
-
-module.exports=NetworkRecorder;
-
-},{"../lib/log.js":19,"./web-inspector":26,"events":200}],22:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{}],18:[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-
-
-
-
-
-
-
-
-
-
-
-
-function filterStylesheetsByUsage(stylesheets,propName,propVal){
-if(!propName&&!propVal){
-return[];
+/**
+ * @param {!Manifest=} manifest
+ * @return {boolean} Does the manifest have any icons?
+ */
+function doExist(manifest) {
+  if (!manifest || !manifest.icons) {
+    return false;
+  }
+  if (manifest.icons.value.length === 0) {
+    return false;
+  }
+  return true;
 }
 
+/**
+ * @param {number} sizeRequirement
+ * @param {!Manifest} manifest
+ * @return {!Array<string>} Value of satisfactory sizes (eg. ['192x192', '256x256'])
+ */
+function sizeAtLeast(sizeRequirement, manifest) {
+  // An icon can be provided for a single size, or for multiple sizes.
+  // To handle both, we flatten all found sizes into a single array.
+  const iconValues = manifest.icons.value;
+  const nestedSizes = iconValues.map(icon => icon.value.sizes.value);
+  const flattenedSizes = [].concat(...nestedSizes);
 
-const deepClone=stylesheets.map(sheet=>Object.assign({},sheet));
-
-return deepClone.filter(s=>{
-if(s.isDuplicate){
-return false;
+  return flattenedSizes
+      // First, filter out any undefined values, in case an icon was defined without a size
+      .filter(size => typeof size === 'string')
+      // discard sizes that are not AAxBB (eg. "any")
+      .filter(size => /\d+x\d+/.test(size))
+      .filter(size => {
+        // Split the '24x24' strings into ['24','24'] arrays
+        const sizeStrs = size.split(/x/i);
+        // Cast the ['24','24'] strings into [24,24] numbers
+        const sizeNums = [parseFloat(sizeStrs[0]), parseFloat(sizeStrs[1])];
+        // Only keep sizes that are as big as our required size
+        const areIconsBigEnough = sizeNums[0] >= sizeRequirement && sizeNums[1] >= sizeRequirement;
+        // Square is required: https://code.google.com/p/chromium/codesearch#chromium/src/chrome/browser/manifest/manifest_icon_selector.cc&q=ManifestIconSelector::IconSizesContainsBiggerThanMinimumSize&sq=package:chromium
+        const areIconsSquare = sizeNums[0] === sizeNums[1];
+        return areIconsBigEnough && areIconsSquare;
+      });
 }
 
-s.parsedContent=s.parsedContent.filter(item=>{
-let usedName='';
-let usedVal='';
+module.exports = {
+  doExist,
+  sizeAtLeast
+};
 
-if(propName){
-propName=Array.isArray(propName)?propName:[propName];
-usedName=propName.includes(item.property.name);
-}
-if(propVal){
-propVal=Array.isArray(propVal)?propVal:[propVal];
-usedVal=propVal.includes(item.property.val);
+},{}],19:[function(require,module,exports){
+(function (process){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+const debug = require('debug');
+const EventEmitter = require('events').EventEmitter;
+const isWindows = process.platform === 'win32';
+
+// process.browser is set when browserify'd via the `process` npm module
+const isBrowser = process.browser;
+
+const colors = {
+  red: isBrowser ? 'crimson' : 1,
+  yellow: isBrowser ? 'gold' : 3,
+  cyan: isBrowser ? 'darkturquoise' : 6,
+  green: isBrowser ? 'forestgreen' : 2,
+  blue: isBrowser ? 'steelblue' : 4,
+  magenta: isBrowser ? 'palevioletred' : 5
+};
+
+// whitelist non-red/yellow colors for debug()
+debug.colors = [colors.cyan, colors.green, colors.blue, colors.magenta];
+
+class Emitter extends EventEmitter {
+  /**
+   * Fires off all status updates. Listen with
+   * `require('lib/log').events.addListener('status', callback)`
+   * @param {string} title
+   * @param {!Array<*>} argsArray
+   */
+  issueStatus(title, argsArray) {
+    if (title === 'status' || title === 'statusEnd') {
+      this.emit(title, [title, ...argsArray]);
+    }
+  }
+
+  /**
+   * Fires off all warnings. Listen with
+   * `require('lib/log').events.addListener('warning', callback)`
+   * @param {string} title
+   * @param {!Array<*>} argsArray
+   */
+  issueWarning(title, argsArray) {
+    this.emit('warning', [title, ...argsArray]);
+  }
 }
 
-if(propName&&!propVal){
-return usedName;
-}else if(!propName&&propVal){
-return usedVal;
-}else if(propName&&propVal){
-return usedName&&usedVal;
-}
-return false;
-});
-return s.parsedContent.length>0;
-});
+const loggersByTitle = {};
+const loggingBufferColumns = 25;
+
+class Log {
+
+  static _logToStdErr(title, argsArray) {
+    const log = Log.loggerfn(title);
+    log(...argsArray);
+  }
+
+  static loggerfn(title) {
+    let log = loggersByTitle[title];
+    if (!log) {
+      log = debug(title);
+      loggersByTitle[title] = log;
+      // errors with red, warnings with yellow.
+      if (title.endsWith('error')) {
+        log.color = colors.red;
+      } else if (title.endsWith('warn')) {
+        log.color = colors.yellow;
+      }
+    }
+    return log;
+  }
+
+  static setLevel(level) {
+    switch (level) {
+      case 'silent':
+        debug.enable('-*');
+        break;
+      case 'verbose':
+        debug.enable('*');
+        break;
+      case 'error':
+        debug.enable('-*, *:error');
+        break;
+      default:
+        debug.enable('*, -*:verbose');
+    }
+  }
+
+  /**
+   * A simple formatting utility for event logging.
+   * @param {string} prefix
+   * @param {!Object} data A JSON-serializable object of event data to log.
+   * @param {string=} level Optional logging level. Defaults to 'log'.
+   */
+  static formatProtocol(prefix, data, level) {
+    const columns = (!process || process.browser) ? Infinity : process.stdout.columns;
+    const maxLength = columns - data.method.length - prefix.length - loggingBufferColumns;
+    // IO.read blacklisted here to avoid logging megabytes of trace data
+    const snippet = (data.params && data.method !== 'IO.read') ?
+      JSON.stringify(data.params).substr(0, maxLength) : '';
+    Log._logToStdErr(`${prefix}:${level || ''}`, [data.method, snippet]);
+  }
+
+  static log(title, ...args) {
+    Log.events.issueStatus(title, args);
+    return Log._logToStdErr(title, args);
+  }
+
+  static warn(title, ...args) {
+    Log.events.issueWarning(title, args);
+    return Log._logToStdErr(`${title}:warn`, args);
+  }
+
+  static error(title, ...args) {
+    return Log._logToStdErr(`${title}:error`, args);
+  }
+
+  static verbose(title, ...args) {
+    Log.events.issueStatus(title, args);
+    return Log._logToStdErr(`${title}:verbose`, args);
+  }
+
+  /**
+   * Add surrounding escape sequences to turn a string green when logged.
+   * @param {string} str
+   * @return {string}
+   */
+  static greenify(str) {
+    return `${Log.green}${str}${Log.reset}`;
+  }
+
+  /**
+   * Add surrounding escape sequences to turn a string red when logged.
+   * @param {string} str
+   * @return {string}
+   */
+  static redify(str) {
+    return `${Log.red}${str}${Log.reset}`;
+  }
+
+  static get green() {
+    return '\x1B[32m';
+  }
+
+  static get red() {
+    return '\x1B[31m';
+  }
+
+  static get yellow() {
+    return '\x1b[33m';
+  }
+
+  static get purple() {
+    return '\x1b[95m';
+  }
+
+  static get reset() {
+    return '\x1B[0m';
+  }
+
+  static get bold() {
+    return '\x1b[1m';
+  }
+
+  static get tick() {
+    return isWindows ? '\u221A' : '✓';
+  }
+
+  static get cross() {
+    return isWindows ? '\u00D7' : '✘';
+  }
+
+  static get whiteSmallSquare() {
+    return isWindows ? '\u0387' : '▫';
+  }
+
+  static get heavyHorizontal() {
+    return isWindows ? '\u2500' : '━';
+  }
+
+  static get heavyVertical() {
+    return isWindows ? '\u2502 ' : '┃ ';
+  }
+
+  static get heavyUpAndRight() {
+    return isWindows ? '\u2514' : '┗';
+  }
+
+  static get heavyVerticalAndRight() {
+    return isWindows ? '\u251C' : '┣';
+  }
+
+  static get heavyDownAndHorizontal() {
+    return isWindows ? '\u252C' : '┳';
+  }
+
+  static get doubleLightHorizontal() {
+    return '──';
+  }
 }
 
+Log.events = new Emitter();
 
+module.exports = Log;
 
+}).call(this,require('_process'))
+},{"_process":222,"debug":262,"events":207}],20:[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
 
+const URL = require('./url-shim');
+const validateColor = require('./web-inspector').Color.parse;
 
+const ALLOWED_DISPLAY_VALUES = [
+  'fullscreen',
+  'standalone',
+  'minimal-ui',
+  'browser'
+];
+/**
+ * All display-mode fallbacks, including when unset, lead to default display mode 'browser'.
+ * @see https://w3c.github.io/manifest/#dfn-default-display-mode
+ */
+const DEFAULT_DISPLAY_MODE = 'browser';
 
+const ALLOWED_ORIENTATION_VALUES = [
+  'any',
+  'natural',
+  'landscape',
+  'portrait',
+  'portrait-primary',
+  'portrait-secondary',
+  'landscape-primary',
+  'landscape-secondary'
+];
 
+function parseString(raw, trim) {
+  let value;
+  let debugString;
 
-function getFormattedStyleRule(content,parsedContent){
-const lines=content.split('\n');
+  if (typeof raw === 'string') {
+    value = trim ? raw.trim() : raw;
+  } else {
+    if (raw !== undefined) {
+      debugString = 'ERROR: expected a string.';
+    }
+    value = undefined;
+  }
 
-const declarationRange=parsedContent.declarationRange;
-
-const startLine=declarationRange.startLine;
-const endLine=declarationRange.endLine;
-const start=declarationRange.startColumn;
-const end=declarationRange.endColumn;
-
-let rule;
-if(startLine===endLine){
-rule=lines[startLine].substring(start,end);
-}else{
-
-
-rule=lines.slice(startLine,endLine+1).reduce((prev,line)=>{
-prev.push(line);
-return prev;
-},[]).join('\n');
+  return {
+    raw,
+    value,
+    debugString
+  };
 }
 
-const block=parsedContent.selector+' {\n'+
-`  ${rule.trim()}\n`+
-'}';
+function parseColor(raw) {
+  const color = parseString(raw);
 
-return{
-styleRule:block.trim(),
-startLine,
-location:`${start}:${end}`};
+  // Finished if color missing or not a string.
+  if (color.value === undefined) {
+    return color;
+  }
 
+  // Use DevTools's color parser to check CSS3 Color parsing.
+  const validatedColor = validateColor(color.raw);
+  if (!validatedColor) {
+    color.value = undefined;
+    color.debugString = 'ERROR: color parsing failed.';
+  }
+
+  return color;
 }
 
-
-
-
-
-
-
-function addVendorPrefixes(propsNames){
-const vendorPrefixes=['-o-','-ms-','-moz-','-webkit-'];
-propsNames=Array.isArray(propsNames)?propsNames:[propsNames];
-let propsNamesWithPrefixes=propsNames;
-
-for(const prefix of vendorPrefixes){
-const temp=propsNames.map(propName=>`${prefix}${propName}`);
-propsNamesWithPrefixes=propsNamesWithPrefixes.concat(temp);
+function parseName(jsonInput) {
+  return parseString(jsonInput.name, true);
 }
 
-return propsNamesWithPrefixes;
+function parseShortName(jsonInput) {
+  return parseString(jsonInput.short_name, true);
 }
-module.exports={
-filterStylesheetsByUsage,
-getFormattedStyleRule,
-addVendorPrefixes};
 
+/**
+ * Returns whether the urls are of the same origin. See https://html.spec.whatwg.org/#same-origin
+ * @param {string} url1
+ * @param {string} url2
+ * @return {boolean}
+ */
+function checkSameOrigin(url1, url2) {
+  const parsed1 = new URL(url1);
+  const parsed2 = new URL(url2);
+
+  return parsed1.origin === parsed2.origin;
+}
+
+/**
+ * https://w3c.github.io/manifest/#start_url-member
+ */
+function parseStartUrl(jsonInput, manifestUrl, documentUrl) {
+  const raw = jsonInput.start_url;
+
+  // 8.10(3) - discard the empty string and non-strings.
+  if (raw === '') {
+    return {
+      raw,
+      value: documentUrl,
+      debugString: 'ERROR: start_url string empty'
+    };
+  }
+  const parsedAsString = parseString(raw);
+  if (!parsedAsString.value) {
+    parsedAsString.value = documentUrl;
+    return parsedAsString;
+  }
+
+  // 8.10(4) - construct URL with raw as input and manifestUrl as the base.
+  let startUrl;
+  try {
+    startUrl = new URL(raw, manifestUrl).href;
+  } catch (e) {
+    // 8.10(5) - discard invalid URLs.
+    return {
+      raw,
+      value: documentUrl,
+      debugString: 'ERROR: invalid start_url relative to ${manifestUrl}'
+    };
+  }
+
+  // 8.10(6) - discard start_urls that are not same origin as documentUrl.
+  if (!checkSameOrigin(startUrl, documentUrl)) {
+    return {
+      raw,
+      value: documentUrl,
+      debugString: 'ERROR: start_url must be same-origin as document'
+    };
+  }
+
+  return {
+    raw,
+    value: startUrl
+  };
+}
+
+function parseDisplay(jsonInput) {
+  const display = parseString(jsonInput.display, true);
+
+  if (!display.value) {
+    display.value = DEFAULT_DISPLAY_MODE;
+    return display;
+  }
+
+  display.value = display.value.toLowerCase();
+  if (!ALLOWED_DISPLAY_VALUES.includes(display.value)) {
+    display.debugString = 'ERROR: \'display\' has invalid value ' + display.value +
+        ` will fall back to ${DEFAULT_DISPLAY_MODE}.`;
+    display.value = DEFAULT_DISPLAY_MODE;
+  }
+
+  return display;
+}
+
+function parseOrientation(jsonInput) {
+  const orientation = parseString(jsonInput.orientation, true);
+
+  if (orientation.value &&
+      !ALLOWED_ORIENTATION_VALUES.includes(orientation.value.toLowerCase())) {
+    orientation.value = undefined;
+    orientation.debugString = 'ERROR: \'orientation\' has an invalid value, will be ignored.';
+  }
+
+  return orientation;
+}
+
+function parseIcon(raw, manifestUrl) {
+  // 9.4(3)
+  const src = parseString(raw.src, true);
+  // 9.4(4) - discard if trimmed value is the empty string.
+  if (src.value === '') {
+    src.value = undefined;
+  }
+  if (src.value) {
+    // 9.4(4) - construct URL with manifest URL as the base
+    src.value = new URL(src.value, manifestUrl).href;
+  }
+
+  const type = parseString(raw.type, true);
+
+  const density = {
+    raw: raw.density,
+    value: 1,
+    debugString: undefined
+  };
+  if (density.raw !== undefined) {
+    density.value = parseFloat(density.raw);
+    if (isNaN(density.value) || !isFinite(density.value) || density.value <= 0) {
+      density.value = 1;
+      density.debugString = 'ERROR: icon density cannot be NaN, +∞, or less than or equal to +0.';
+    }
+  }
+
+  const sizes = parseString(raw.sizes);
+  if (sizes.value !== undefined) {
+    const set = new Set();
+    sizes.value.trim().split(/\s+/).forEach(size => set.add(size.toLowerCase()));
+    sizes.value = set.size > 0 ? Array.from(set) : undefined;
+  }
+
+  return {
+    raw,
+    value: {
+      src,
+      type,
+      density,
+      sizes
+    },
+    debugString: undefined
+  };
+}
+
+function parseIcons(jsonInput, manifestUrl) {
+  const raw = jsonInput.icons;
+
+  if (raw === undefined) {
+    return {
+      raw,
+      value: [],
+      debugString: undefined
+    };
+  }
+
+  if (!Array.isArray(raw)) {
+    return {
+      raw,
+      value: [],
+      debugString: 'ERROR: \'icons\' expected to be an array but is not.'
+    };
+  }
+
+  // TODO(bckenny): spec says to skip icons missing `src`, so debug messages on
+  // individual icons are lost. Warn instead?
+  const value = raw
+    // 9.6(3)(1)
+    .filter(icon => icon.src !== undefined)
+    // 9.6(3)(2)(1)
+    .map(icon => parseIcon(icon, manifestUrl))
+    // 9.6(3)(2)(2)
+    .filter(parsedIcon => parsedIcon.value.src.value !== undefined);
+
+  return {
+    raw,
+    value,
+    debugString: undefined
+  };
+}
+
+function parseApplication(raw) {
+  const platform = parseString(raw.platform, true);
+  const id = parseString(raw.id, true);
+
+  // 10.2.(2) and 10.2.(3)
+  const appUrl = parseString(raw.url, true);
+  if (appUrl.value) {
+    try {
+      // 10.2.(4) - attempt to construct URL.
+      appUrl.value = new URL(appUrl.value).href;
+    } catch (e) {
+      appUrl.value = undefined;
+      appUrl.debugString = 'ERROR: invalid application URL ${raw.url}';
+    }
+  }
+
+  return {
+    raw,
+    value: {
+      platform,
+      id,
+      url: appUrl
+    },
+    debugString: undefined
+  };
+}
+
+function parseRelatedApplications(jsonInput) {
+  const raw = jsonInput.related_applications;
+
+  if (raw === undefined) {
+    return {
+      raw,
+      value: undefined,
+      debugString: undefined
+    };
+  }
+
+  if (!Array.isArray(raw)) {
+    return {
+      raw,
+      value: undefined,
+      debugString: 'ERROR: \'related_applications\' expected to be an array but is not.'
+    };
+  }
+
+  // TODO(bckenny): spec says to skip apps missing `platform`, so debug messages
+  // on individual apps are lost. Warn instead?
+  const value = raw
+    .filter(application => !!application.platform)
+    .map(parseApplication)
+    .filter(parsedApp => !!parsedApp.value.id.value || !!parsedApp.value.url.value);
+
+  return {
+    raw,
+    value,
+    debugString: undefined
+  };
+}
+
+function parsePreferRelatedApplications(jsonInput) {
+  const raw = jsonInput.prefer_related_applications;
+  let value;
+  let debugString;
+
+  if (typeof raw === 'boolean') {
+    value = raw;
+  } else {
+    if (raw !== undefined) {
+      debugString = 'ERROR: \'prefer_related_applications\' expected to be a boolean.';
+    }
+    value = undefined;
+  }
+
+  return {
+    raw,
+    value,
+    debugString
+  };
+}
+
+function parseThemeColor(jsonInput) {
+  return parseColor(jsonInput.theme_color);
+}
+
+function parseBackgroundColor(jsonInput) {
+  return parseColor(jsonInput.background_color);
+}
+
+/**
+ * Parse a manifest from the given inputs.
+ * @param {string} string Manifest JSON string.
+ * @param {string} manifestUrl URL of manifest file.
+ * @param {string} documentUrl URL of document containing manifest link element.
+ * @return {!ManifestNode<(!Manifest|undefined)>}
+ */
+function parse(string, manifestUrl, documentUrl) {
+  if (manifestUrl === undefined || documentUrl === undefined) {
+    throw new Error('Manifest and document URLs required for manifest parsing.');
+  }
+
+  let jsonInput;
+
+  try {
+    jsonInput = JSON.parse(string);
+  } catch (e) {
+    return {
+      raw: string,
+      value: undefined,
+      debugString: 'ERROR: file isn\'t valid JSON: ' + e
+    };
+  }
+
+  /* eslint-disable camelcase */
+  const manifest = {
+    name: parseName(jsonInput),
+    short_name: parseShortName(jsonInput),
+    start_url: parseStartUrl(jsonInput, manifestUrl, documentUrl),
+    display: parseDisplay(jsonInput),
+    orientation: parseOrientation(jsonInput),
+    icons: parseIcons(jsonInput, manifestUrl),
+    related_applications: parseRelatedApplications(jsonInput),
+    prefer_related_applications: parsePreferRelatedApplications(jsonInput),
+    theme_color: parseThemeColor(jsonInput),
+    background_color: parseBackgroundColor(jsonInput)
+  };
+  /* eslint-enable camelcase */
+
+  return {
+    raw: string,
+    value: manifest,
+    debugString: undefined
+  };
+}
+
+module.exports = parse;
+
+},{"./url-shim":25,"./web-inspector":26}],21:[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+const NetworkManager = require('./web-inspector').NetworkManager;
+const EventEmitter = require('events').EventEmitter;
+const log = require('../lib/log.js');
+
+class NetworkRecorder extends EventEmitter {
+  constructor(recordArray, driver) {
+    super();
+
+    this._records = recordArray;
+    this.networkManager = NetworkManager.createWithFakeTarget(driver);
+
+    this.startedRequestCount = 0;
+    this.finishedRequestCount = 0;
+
+    this.networkManager.addEventListener(this.EventTypes.RequestStarted,
+        this.onRequestStarted.bind(this));
+    this.networkManager.addEventListener(this.EventTypes.RequestFinished,
+        this.onRequestFinished.bind(this));
+
+    this.onRequestWillBeSent = this.onRequestWillBeSent.bind(this);
+    this.onRequestServedFromCache = this.onRequestServedFromCache.bind(this);
+    this.onResponseReceived = this.onResponseReceived.bind(this);
+    this.onDataReceived = this.onDataReceived.bind(this);
+    this.onLoadingFinished = this.onLoadingFinished.bind(this);
+    this.onLoadingFailed = this.onLoadingFailed.bind(this);
+    this.onResourceChangedPriority = this.onResourceChangedPriority.bind(this);
+  }
+
+  get EventTypes() {
+    return NetworkManager.Events;
+  }
+
+  activeRequestCount() {
+    return this.startedRequestCount - this.finishedRequestCount;
+  }
+
+  isIdle() {
+    return this.activeRequestCount() === 0;
+  }
+
+  /**
+   * Listener for the NetworkManager's RequestStarted event, which includes both
+   * web socket and normal request creation.
+   * @private
+   */
+  onRequestStarted() {
+    this.startedRequestCount++;
+
+    const activeCount = this.activeRequestCount();
+    log.verbose('NetworkRecorder', `Request started. ${activeCount} requests in progress` +
+        ` (${this.startedRequestCount} started and ${this.finishedRequestCount} finished).`);
+
+    // If only one request in progress, emit event that we've transitioned from
+    // idle to busy.
+    if (activeCount === 1) {
+      this.emit('networkbusy');
+    }
+  }
+
+  /**
+   * Listener for the NetworkManager's RequestFinished event, which includes
+   * request finish, failure, and redirect, as well as the closing of web
+   * sockets.
+   * @param {!WebInspector.NetworkRequest} request
+   * @private
+   */
+  onRequestFinished(request) {
+    this.finishedRequestCount++;
+    this._records.push(request.data);
+    this.emit('requestloaded', request.data);
+
+    const activeCount = this.activeRequestCount();
+    log.verbose('NetworkRecorder', `Request finished. ${activeCount} requests in progress` +
+        ` (${this.startedRequestCount} started and ${this.finishedRequestCount} finished).`);
+
+    // If no requests in progress, emit event that we've transitioned from busy
+    // to idle.
+    if (this.isIdle()) {
+      this.emit('networkidle');
+    }
+  }
+
+  // There are a few differences between the debugging protocol naming and
+  // the parameter naming used in NetworkManager. These are noted below.
+
+  onRequestWillBeSent(data) {
+    // NOTE: data.timestamp -> time, data.type -> resourceType
+    this.networkManager._dispatcher.requestWillBeSent(data.requestId,
+        data.frameId, data.loaderId, data.documentURL, data.request,
+        data.timestamp, data.wallTime, data.initiator, data.redirectResponse,
+        data.type);
+  }
+
+  onRequestServedFromCache(data) {
+    this.networkManager._dispatcher.requestServedFromCache(data.requestId);
+  }
+
+  onResponseReceived(data) {
+    // NOTE: data.timestamp -> time, data.type -> resourceType
+    this.networkManager._dispatcher.responseReceived(data.requestId,
+        data.frameId, data.loaderId, data.timestamp, data.type, data.response);
+  }
+
+  onDataReceived(data) {
+    // NOTE: data.timestamp -> time
+    this.networkManager._dispatcher.dataReceived(data.requestId, data.timestamp,
+        data.dataLength, data.encodedDataLength);
+  }
+
+  onLoadingFinished(data) {
+    // NOTE: data.timestamp -> finishTime
+    this.networkManager._dispatcher.loadingFinished(data.requestId,
+        data.timestamp, data.encodedDataLength);
+  }
+
+  onLoadingFailed(data) {
+    // NOTE: data.timestamp -> time, data.type -> resourceType,
+    // data.errorText -> localizedDescription
+    this.networkManager._dispatcher.loadingFailed(data.requestId,
+        data.timestamp, data.type, data.errorText, data.canceled,
+        data.blockedReason);
+  }
+
+  onResourceChangedPriority(data) {
+    this.networkManager._dispatcher.resourceChangedPriority(data.requestId,
+        data.newPriority, data.timestamp);
+  }
+
+  static recordsFromLogs(logs) {
+    const records = [];
+    const nr = new NetworkRecorder(records);
+    const dispatcher = method => {
+      switch (method) {
+        case 'Network.requestWillBeSent': return nr.onRequestWillBeSent;
+        case 'Network.requestServedFromCache': return nr.onRequestServedFromCache;
+        case 'Network.responseReceived': return nr.onResponseReceived;
+        case 'Network.dataReceived': return nr.onDataReceived;
+        case 'Network.loadingFinished': return nr.onLoadingFinished;
+        case 'Network.loadingFailed': return nr.onLoadingFailed;
+        case 'Network.resourceChangedPriority': return nr.onResourceChangedPriority;
+        default: return () => {};
+      }
+    };
+
+    logs.forEach(networkEvent => {
+      dispatcher(networkEvent.method)(networkEvent.params);
+    });
+
+    return records;
+  }
+}
+
+module.exports = NetworkRecorder;
+
+},{"../lib/log.js":19,"./web-inspector":26,"events":207}],22:[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+/**
+ * Filters a list of stylesheets for usage of a CSS property name, value,
+ * or name/value pair.
+ *
+ * @param {!Array} stylesheets A list of stylesheets used by the page.
+ * @param {string|Array<string>=} propName Optional name of the CSS property/properties to filter
+ *     results on. If propVal is not specified, all stylesheets that use the property are
+ *     returned. Otherwise, stylesheets that use the propName: propVal are returned.
+ * @param {string|Array<string>=} propVal Optional value of the CSS property/propertys to filter
+ *     results on.
+ * @return {!Array} A list of stylesheets that use the CSS property.
+ */
+function filterStylesheetsByUsage(stylesheets, propName, propVal) {
+  if (!propName && !propVal) {
+    return [];
+  }
+  // Create deep clone of arrays so multiple calls to filterStylesheetsByUsage
+  // don't alter the original artifacts in stylesheets arg.
+  const deepClone = stylesheets.map(sheet => Object.assign({}, sheet));
+
+  return deepClone.filter(s => {
+    if (s.isDuplicate) {
+      return false;
+    }
+
+    s.parsedContent = s.parsedContent.filter(item => {
+      let usedName = '';
+      let usedVal = '';
+      // Prevent includes call on null value
+      if (propName) {
+        propName = Array.isArray(propName) ? propName : [propName];
+        usedName = propName.includes(item.property.name);
+      }
+      if (propVal) {
+        propVal = Array.isArray(propVal) ? propVal : [propVal];
+        usedVal = propVal.includes(item.property.val);
+      }
+      // Allow search by css property name, a value, or name/value pair.
+      if (propName && !propVal) {
+        return usedName;
+      } else if (!propName && propVal) {
+        return usedVal;
+      } else if (propName && propVal) {
+        return usedName && usedVal;
+      }
+      return false;
+    });
+    return s.parsedContent.length > 0;
+  });
+}
+
+/**
+ * Returns a formatted snippet of CSS and the location of its use.
+ *
+ * @param {!string} content CSS text content.
+ * @param {!Object} parsedContent The parsed version content.
+ * @return {{styleRule: string, location: string}} Formatted output.
+ */
+function getFormattedStyleRule(content, parsedContent) {
+  const lines = content.split('\n');
+
+  const declarationRange = parsedContent.declarationRange;
+
+  const startLine = declarationRange.startLine;
+  const endLine = declarationRange.endLine;
+  const start = declarationRange.startColumn;
+  const end = declarationRange.endColumn;
+
+  let rule;
+  if (startLine === endLine) {
+    rule = lines[startLine].substring(start, end);
+  } else {
+    // If css property value spans multiple lines, include all of them so it's
+    // obvious where the value was used.
+    rule = lines.slice(startLine, endLine + 1).reduce((prev, line) => {
+      prev.push(line);
+      return prev;
+    }, []).join('\n');
+  }
+
+  const block = parsedContent.selector + ' {\n' +
+      `  ${rule.trim()}\n` +
+      '}';
+
+  return {
+    styleRule: block.trim(),
+    startLine,
+    location: `${start}:${end}`
+  };
+}
+
+/**
+ * Returns an array of all CSS prefixes and the default CSS style names.
+ *
+ * @param {string|Array<string>=} propNames CSS property names.
+ * @return {Array<string>=} CSS property names with and without vendor prefixes.
+ */
+function addVendorPrefixes(propsNames) {
+  const vendorPrefixes = ['-o-', '-ms-', '-moz-', '-webkit-'];
+  propsNames = Array.isArray(propsNames) ? propsNames : [propsNames];
+  let propsNamesWithPrefixes = propsNames;
+  // Map vendorPrefixes to propsNames
+  for (const prefix of vendorPrefixes) {
+    const temp = propsNames.map(propName => `${prefix}${propName}`);
+    propsNamesWithPrefixes = propsNamesWithPrefixes.concat(temp);
+  }
+  // Add original propNames
+  return propsNamesWithPrefixes;
+}
+module.exports = {
+  filterStylesheetsByUsage,
+  getFormattedStyleRule,
+  addVendorPrefixes
+};
 
 },{}],23:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-const WebInspector=require('../web-inspector');
-const ConsoleQuieter=require('../console-quieter');
+const WebInspector = require('../web-inspector');
+const ConsoleQuieter = require('../console-quieter');
 
+// Polyfill the bottom-up and topdown tree sorting.
+const TimelineModelTreeView =
+    require('devtools-timeline-model/lib/timeline-model-treeview.js')(WebInspector);
 
-const TimelineModelTreeView=
-require('devtools-timeline-model/lib/timeline-model-treeview.js')(WebInspector);
+class TimelineModel {
 
-class TimelineModel{
+  constructor(events) {
+    this.init(events);
+  }
 
-constructor(events){
-this.init(events);
+  init(events) {
+    // (devtools) tracing model
+    this._tracingModel =
+        new WebInspector.TracingModel(new WebInspector.TempFileBackingStorage('tracing'));
+    // timeline model
+    this._timelineModel =
+        new WebInspector.TimelineModel(WebInspector.TimelineUIUtils.visibleEventsFilter());
+
+    if (typeof events === 'string') {
+      events = JSON.parse(events);
+    }
+    if (events.hasOwnProperty('traceEvents')) {
+      events = events.traceEvents;
+    }
+
+    // populate with events
+    this._tracingModel.reset();
+
+    ConsoleQuieter.mute({prefix: 'timelineModel'});
+    this._tracingModel.addEvents(events);
+    this._tracingModel.tracingComplete();
+    this._timelineModel.setEvents(this._tracingModel);
+    ConsoleQuieter.unmuteAndFlush();
+
+    return this;
+  }
+
+  _createAggregator() {
+    return WebInspector.AggregatedTimelineTreeView.prototype._createAggregator();
+  }
+
+  timelineModel() {
+    return this._timelineModel;
+  }
+
+  tracingModel() {
+    return this._tracingModel;
+  }
+
+  topDown() {
+    const filters = [];
+    filters.push(WebInspector.TimelineUIUtils.visibleEventsFilter());
+    filters.push(new WebInspector.ExcludeTopLevelFilter());
+    const nonessentialEvents = [
+      WebInspector.TimelineModel.RecordType.EventDispatch,
+      WebInspector.TimelineModel.RecordType.FunctionCall,
+      WebInspector.TimelineModel.RecordType.TimerFire
+    ];
+    filters.push(new WebInspector.ExclusiveNameFilter(nonessentialEvents));
+
+    const topDown = WebInspector.TimelineProfileTree.buildTopDown(
+        this._timelineModel.mainThreadEvents(),
+        filters, /* startTime */ 0, /* endTime */ Infinity,
+        WebInspector.TimelineAggregator.eventId);
+    return topDown;
+  }
+
+  bottomUp() {
+    const topDown = this.topDown();
+    const noGrouping = WebInspector.TimelineAggregator.GroupBy.None;
+    const noGroupAggregator = this._createAggregator().groupFunction(noGrouping);
+    return WebInspector.TimelineProfileTree.buildBottomUp(topDown, noGroupAggregator);
+  }
+
+ /**
+  * @param  {!string} grouping Allowed values: None Category Subdomain Domain URL EventName
+  * @return {!WebInspector.TimelineProfileTree.Node} A grouped and sorted tree
+  */
+  bottomUpGroupBy(grouping) {
+    const topDown = this.topDown();
+
+    const groupSetting = WebInspector.TimelineAggregator.GroupBy[grouping];
+    const groupingAggregator = this._createAggregator().groupFunction(groupSetting);
+    const bottomUpGrouped =
+        WebInspector.TimelineProfileTree.buildBottomUp(topDown, groupingAggregator);
+
+    // sort the grouped tree, in-place
+    new TimelineModelTreeView(bottomUpGrouped).sortingChanged('self', 'desc');
+    return bottomUpGrouped;
+  }
+
+  frameModel() {
+    const frameModel = new WebInspector.TimelineFrameModel(event =>
+      WebInspector.TimelineUIUtils.eventStyle(event).category.name
+    );
+    frameModel.addTraceEvents({ /* target */ },
+      this._timelineModel.inspectedTargetEvents(), this._timelineModel.sessionId() || '');
+    return frameModel;
+  }
+
+  filmStripModel() {
+    return new WebInspector.FilmStripModel(this._tracingModel);
+  }
+
+  interactionModel() {
+    const irModel = new WebInspector.TimelineIRModel();
+    irModel.populate(this._timelineModel);
+    return irModel;
+  }
+
 }
 
-init(events){
+module.exports = TimelineModel;
 
-this._tracingModel=
-new WebInspector.TracingModel(new WebInspector.TempFileBackingStorage('tracing'));
-
-this._timelineModel=
-new WebInspector.TimelineModel(WebInspector.TimelineUIUtils.visibleEventsFilter());
-
-if(typeof events==='string'){
-events=JSON.parse(events);
-}
-if(events.hasOwnProperty('traceEvents')){
-events=events.traceEvents;
-}
-
-
-this._tracingModel.reset();
-
-ConsoleQuieter.mute({prefix:'timelineModel'});
-this._tracingModel.addEvents(events);
-this._tracingModel.tracingComplete();
-this._timelineModel.setEvents(this._tracingModel);
-ConsoleQuieter.unmuteAndFlush();
-
-return this;
-}
-
-_createAggregator(){
-return WebInspector.AggregatedTimelineTreeView.prototype._createAggregator();
-}
-
-timelineModel(){
-return this._timelineModel;
-}
-
-tracingModel(){
-return this._tracingModel;
-}
-
-topDown(){
-const filters=[];
-filters.push(WebInspector.TimelineUIUtils.visibleEventsFilter());
-filters.push(new WebInspector.ExcludeTopLevelFilter());
-const nonessentialEvents=[
-WebInspector.TimelineModel.RecordType.EventDispatch,
-WebInspector.TimelineModel.RecordType.FunctionCall,
-WebInspector.TimelineModel.RecordType.TimerFire];
-
-filters.push(new WebInspector.ExclusiveNameFilter(nonessentialEvents));
-
-const topDown=WebInspector.TimelineProfileTree.buildTopDown(
-this._timelineModel.mainThreadEvents(),
-filters,0,Infinity,
-WebInspector.TimelineAggregator.eventId);
-return topDown;
-}
-
-bottomUp(){
-const topDown=this.topDown();
-const noGrouping=WebInspector.TimelineAggregator.GroupBy.None;
-const noGroupAggregator=this._createAggregator().groupFunction(noGrouping);
-return WebInspector.TimelineProfileTree.buildBottomUp(topDown,noGroupAggregator);
-}
-
-
-
-
-
-bottomUpGroupBy(grouping){
-const topDown=this.topDown();
-
-const groupSetting=WebInspector.TimelineAggregator.GroupBy[grouping];
-const groupingAggregator=this._createAggregator().groupFunction(groupSetting);
-const bottomUpGrouped=
-WebInspector.TimelineProfileTree.buildBottomUp(topDown,groupingAggregator);
-
-
-new TimelineModelTreeView(bottomUpGrouped).sortingChanged('self','desc');
-return bottomUpGrouped;
-}
-
-frameModel(){
-const frameModel=new WebInspector.TimelineFrameModel(event=>
-WebInspector.TimelineUIUtils.eventStyle(event).category.name);
-
-frameModel.addTraceEvents({},
-this._timelineModel.inspectedTargetEvents(),this._timelineModel.sessionId()||'');
-return frameModel;
-}
-
-filmStripModel(){
-return new WebInspector.FilmStripModel(this._tracingModel);
-}
-
-interactionModel(){
-const irModel=new WebInspector.TimelineIRModel();
-irModel.populate(this._timelineModel);
-return irModel;
-}}
-
-
-
-module.exports=TimelineModel;
-
-},{"../console-quieter":14,"../web-inspector":26,"devtools-timeline-model/lib/timeline-model-treeview.js":235}],24:[function(require,module,exports){
-(function(global){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+},{"../console-quieter":14,"../web-inspector":26,"devtools-timeline-model/lib/timeline-model-treeview.js":264}],24:[function(require,module,exports){
+(function (global){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
-if(typeof global.window==='undefined'){
-global.window=global;
+if (typeof global.window === 'undefined') {
+  global.window = global;
 }
 
+// The ideal input response latency, the time between the input task and the
+// first frame of the response.
+const BASE_RESPONSE_LATENCY = 16;
 
+// we need gl-matrix and jszip for traceviewer
+// since it has internal forks for isNode and they get mixed up during
+// browserify, we require them locally here and global-ize them.
 
-const BASE_RESPONSE_LATENCY=16;
-
-
-
-
-
-
-const glMatrixModule=require('gl-matrix');
-Object.keys(glMatrixModule).forEach(exportName=>{
-global[exportName]=glMatrixModule[exportName];
+// from catapult/tracing/tracing/base/math.html
+const glMatrixModule = require('gl-matrix');
+Object.keys(glMatrixModule).forEach(exportName => {
+  global[exportName] = glMatrixModule[exportName];
 });
-
-global.JSZip={};
-global.mannwhitneyu={};
-global.HTMLImportsLoader={};
-global.HTMLImportsLoader.hrefToAbsolutePath=function(path){
-if(path==='/gl-matrix-min.js'){
-return'../../../lib/empty-stub.js';
-}
-if(path==='/jszip.min.js'){
-return'../../../lib/empty-stub.js';
-}
-if(path==='/mannwhitneyu.js'){
-return'../../../lib/empty-stub.js';
-}
+// from catapult/tracing/tracing/extras/importer/jszip.html
+global.JSZip = {};
+global.mannwhitneyu = {};
+global.HTMLImportsLoader = {};
+global.HTMLImportsLoader.hrefToAbsolutePath = function(path) {
+  if (path === '/gl-matrix-min.js') {
+    return '../../../lib/empty-stub.js';
+  }
+  if (path === '/jszip.min.js') {
+    return '../../../lib/empty-stub.js';
+  }
+  if (path === '/mannwhitneyu.js') {
+    return '../../../lib/empty-stub.js';
+  }
 };
 
 require('../../third_party/traceviewer-js/');
-const traceviewer=global.tr;
+const traceviewer = global.tr;
 
-class TraceProcessor{
-get RESPONSE(){
-return'Response';
+class TraceProcessor {
+  get RESPONSE() {
+    return 'Response';
+  }
+
+  get ANIMATION() {
+    return 'Animation';
+  }
+
+  get LOAD() {
+    return 'Load';
+  }
+
+  // Create the importer and import the trace contents to a model.
+  init(trace) {
+    const io = new traceviewer.importer.ImportOptions();
+    io.showImportWarnings = false;
+    io.pruneEmptyContainers = false;
+    io.shiftWorldToZero = true;
+
+    const model = new traceviewer.Model();
+    const importer = new traceviewer.importer.Import(model, io);
+    importer.importTraces([trace]);
+
+    return model;
+  }
+
+  /**
+   * Find a main thread from supplied model with matching processId and
+   * threadId.
+   * @param {!Object} model TraceProcessor Model
+   * @param {number} processId
+   * @param {number} threadId
+   * @return {!Object}
+   * @private
+   */
+  static _findMainThreadFromIds(model, processId, threadId) {
+    const modelHelper = model.getOrCreateHelper(traceviewer.model.helpers.ChromeModelHelper);
+    const renderHelpers = traceviewer.b.dictionaryValues(modelHelper.rendererHelpers);
+    const mainThread = renderHelpers.find(helper => {
+      return helper.mainThread &&
+        helper.pid === processId &&
+        helper.mainThread.tid === threadId;
+    }).mainThread;
+
+    return mainThread;
+  }
+
+  /**
+   * Calculate duration at specified percentiles for given population of
+   * durations.
+   * If one of the durations overlaps the end of the window, the full
+   * duration should be in the duration array, but the length not included
+   * within the window should be given as `clippedLength`. For instance, if a
+   * 50ms duration occurs 10ms before the end of the window, `50` should be in
+   * the `durations` array, and `clippedLength` should be set to 40.
+   * @see https://docs.google.com/document/d/1b9slyaB9yho91YTOkAQfpCdULFkZM9LqsipcX3t7He8/preview
+   * @param {!Array<number>} durations Array of durations, sorted in ascending order.
+   * @param {number} totalTime Total time (in ms) of interval containing durations.
+   * @param {!Array<number>} percentiles Array of percentiles of interest, in ascending order.
+   * @param {number=} clippedLength Optional length clipped from a duration overlapping end of window. Default of 0.
+   * @return {!Array<{percentile: number, time: number}>}
+   * @private
+   */
+  static _riskPercentiles(durations, totalTime, percentiles, clippedLength = 0) {
+    let busyTime = 0;
+    for (let i = 0; i < durations.length; i++) {
+      busyTime += durations[i];
+    }
+    busyTime -= clippedLength;
+
+    // Start with idle time already complete.
+    let completedTime = totalTime - busyTime;
+    let duration = 0;
+    let cdfTime = completedTime;
+    const results = [];
+
+    let durationIndex = -1;
+    let remainingCount = durations.length + 1;
+    if (clippedLength > 0) {
+      // If there was a clipped duration, one less in count since one hasn't started yet.
+      remainingCount--;
+    }
+
+    // Find percentiles of interest, in order.
+    for (const percentile of percentiles) {
+      // Loop over durations, calculating a CDF value for each until it is above
+      // the target percentile.
+      const percentileTime = percentile * totalTime;
+      while (cdfTime < percentileTime && durationIndex < durations.length - 1) {
+        completedTime += duration;
+        remainingCount -= (duration < 0 ? -1 : 1);
+
+        if (clippedLength > 0 && clippedLength < durations[durationIndex + 1]) {
+          duration = -clippedLength;
+          clippedLength = 0;
+        } else {
+          durationIndex++;
+          duration = durations[durationIndex];
+        }
+
+        // Calculate value of CDF (multiplied by totalTime) for the end of this duration.
+        cdfTime = completedTime + Math.abs(duration) * remainingCount;
+      }
+
+      // Negative results are within idle time (0ms wait by definition), so clamp at zero.
+      results.push({
+        percentile,
+        time: Math.max(0, (percentileTime - completedTime) / remainingCount) + BASE_RESPONSE_LATENCY
+      });
+    }
+
+    return results;
+  }
+
+  /**
+   * Calculates the maximum queueing time (in ms) of high priority tasks for
+   * selected percentiles within a window of the main thread.
+   * @see https://docs.google.com/document/d/1b9slyaB9yho91YTOkAQfpCdULFkZM9LqsipcX3t7He8/preview
+   * @param {!traceviewer.Model} model
+   * @param {{traceEvents: !Array<!Object>}} trace
+   * @param {number=} startTime Optional start time (in ms) of range of interest. Defaults to trace start.
+   * @param {number=} endTime Optional end time (in ms) of range of interest. Defaults to trace end.
+   * @param {!Array<number>=} percentiles Optional array of percentiles to compute. Defaults to [0.5, 0.75, 0.9, 0.99, 1].
+   * @return {!Array<{percentile: number, time: number}>}
+   */
+  static getRiskToResponsiveness(model, trace, startTime, endTime, percentiles) {
+    // Range of responsiveness we care about. Default to bounds of model.
+    startTime = startTime === undefined ? model.bounds.min : startTime;
+    endTime = endTime === undefined ? model.bounds.max : endTime;
+    const totalTime = endTime - startTime;
+    if (percentiles) {
+      percentiles.sort((a, b) => a - b);
+    } else {
+      percentiles = [0.5, 0.75, 0.9, 0.99, 1];
+    }
+
+    const ret = TraceProcessor.getMainThreadTopLevelEventDurations(model, trace, startTime,
+        endTime);
+    return TraceProcessor._riskPercentiles(ret.durations, totalTime, percentiles,
+        ret.clippedLength);
+  }
+
+  /**
+   * Provides durations of all main thread top-level events
+   * @param {!traceviewer.Model} model
+   * @param {{traceEvents: !Array<!Object>}} trace
+   * @param {number} startTime Optional start time (in ms) of range of interest. Defaults to trace start.
+   * @param {number} endTime Optional end time (in ms) of range of interest. Defaults to trace end.
+   * @return {{durations: !Array<number>, clippedLength: number}}
+   */
+  static getMainThreadTopLevelEventDurations(model, trace, startTime, endTime) {
+    // Find the main thread via the first TracingStartedInPage event in the trace
+    const startEvent = trace.traceEvents.find(event => {
+      return event.name === 'TracingStartedInPage';
+    });
+    const mainThread = TraceProcessor._findMainThreadFromIds(model, startEvent.pid, startEvent.tid);
+
+    // Find durations of all slices in range of interest.
+    // TODO(bckenny): filter for top level slices ourselves?
+    const durations = [];
+    let clippedLength = 0;
+    mainThread.sliceGroup.topLevelSlices.forEach(slice => {
+      // Discard slices outside range.
+
+      if (slice.end <= startTime || slice.start >= endTime) {
+        return;
+      }
+
+      // Clip any at edges of range.
+      let duration = slice.duration;
+      let sliceStart = slice.start;
+      if (sliceStart < startTime) {
+        // Any part of task before window can be discarded.
+        sliceStart = startTime;
+        duration = slice.end - sliceStart;
+      }
+      if (slice.end > endTime) {
+        // Any part of task after window must be clipped but accounted for.
+        clippedLength = duration - (endTime - sliceStart);
+      }
+
+      durations.push(duration);
+    });
+    durations.sort((a, b) => a - b);
+
+    return {
+      durations,
+      clippedLength
+    };
+  }
+
+  /**
+   * Uses traceviewer's statistics package to create a log-normal distribution.
+   * Specified by providing the median value, at which the score will be 0.5,
+   * and the falloff, the initial point of diminishing returns where any
+   * improvement in value will yield increasingly smaller gains in score. Both
+   * values should be in the same units (e.g. milliseconds). See
+   *   https://www.desmos.com/calculator/tx1wcjk8ch
+   * for an interactive view of the relationship between these parameters and
+   * the typical parameterization (location and shape) of the log-normal
+   * distribution.
+   * @param {number} median
+   * @param {number} falloff
+   * @return {!Statistics.LogNormalDistribution}
+   */
+  static getLogNormalDistribution(median, falloff) {
+    const location = Math.log(median);
+
+    // The "falloff" value specified the location of the smaller of the positive
+    // roots of the third derivative of the log-normal CDF. Calculate the shape
+    // parameter in terms of that value and the median.
+    const logRatio = Math.log(falloff / median);
+    const shape = 0.5 * Math.sqrt(1 - 3 * logRatio -
+        Math.sqrt((logRatio - 3) * (logRatio - 3) - 8));
+
+    return new traceviewer.b.Statistics.LogNormalDistribution(location, shape);
+  }
 }
 
-get ANIMATION(){
-return'Animation';
+module.exports = TraceProcessor;
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../third_party/traceviewer-js/":85,"gl-matrix":265}],25:[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * URL shim so we keep our code DRY
+ */
+
+'use strict';
+
+/* global self */
+
+const ELLIPSIS = '\u2026';
+
+// TODO: Add back node require('url').URL parsing when bug is resolved:
+// https://github.com/GoogleChrome/lighthouse/issues/1186
+const URL = (typeof self !== 'undefined' && self.URL) || require('whatwg-url').URL;
+
+URL.INVALID_URL_DEBUG_STRING =
+    'Lighthouse was unable to determine the URL of some script executions. ' +
+    'It\'s possible a Chrome extension or other eval\'d code is the source.';
+
+/**
+ * @param {string} url
+ * @return {boolean}
+ */
+URL.isValid = function isValid(url) {
+  try {
+    new URL(url);
+    return true;
+  } catch (e) {
+    return false;
+  }
+};
+
+/**
+ * @param {string} urlA
+ * @param {string} urlB
+ * @return {boolean}
+ */
+URL.hostsMatch = function hostsMatch(urlA, urlB) {
+  try {
+    return new URL(urlA).host === new URL(urlB).host;
+  } catch (e) {
+    return false;
+  }
+};
+
+/**
+ * @param {string} urlA
+ * @param {string} urlB
+ * @return {boolean}
+ */
+URL.originsMatch = function originsMatch(urlA, urlB) {
+  try {
+    return new URL(urlA).origin === new URL(urlB).origin;
+  } catch (e) {
+    return false;
+  }
+};
+
+/**
+ * @param {string} url
+ * @param {{numPathParts: number, preserveQuery: boolean, preserveHost: boolean}=} options
+ * @return {string}
+ */
+URL.getDisplayName = function getDisplayName(url, options) {
+  options = Object.assign({
+    numPathParts: 2,
+    preserveQuery: true,
+    preserveHost: false,
+  }, options);
+
+  const parsed = new URL(url);
+
+  let name;
+
+  if (parsed.protocol === 'about:' || parsed.protocol === 'data:') {
+    // Handle 'about:*' and 'data:*' URLs specially since they have no path.
+    name = parsed.href;
+  } else {
+    name = parsed.pathname;
+    const parts = name.split('/').filter(part => part.length);
+    if (options.numPathParts && parts.length > options.numPathParts) {
+      name = ELLIPSIS + parts.slice(-1 * options.numPathParts).join('/');
+    }
+
+    if (options.preserveHost) {
+      name = `${parsed.host}/${name.replace(/^\//, '')}`;
+    }
+    if (options.preserveQuery) {
+      name = `${name}${parsed.search}`;
+    }
+  }
+
+  const MAX_LENGTH = 64;
+  // Always elide hash
+  name = name.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g, `$1${ELLIPSIS}`);
+
+  // Elide query params first
+  if (name.length > MAX_LENGTH && name.includes('?')) {
+    // Try to leave the first query parameter intact
+    name = name.replace(/\?([^=]*)(=)?.*/, `?$1$2${ELLIPSIS}`);
+
+    // Remove it all if it's still too long
+    if (name.length > MAX_LENGTH) {
+      name = name.replace(/\?.*/, `?${ELLIPSIS}`);
+    }
+  }
+
+  // Elide too long names next
+  if (name.length > MAX_LENGTH) {
+    const dotIndex = name.lastIndexOf('.');
+    if (dotIndex >= 0) {
+      name = name.slice(0, MAX_LENGTH - 1 - (name.length - dotIndex)) +
+          // Show file extension
+          `${ELLIPSIS}${name.slice(dotIndex)}`;
+    } else {
+      name = name.slice(0, MAX_LENGTH - 1) + ELLIPSIS;
+    }
+  }
+
+  return name;
+};
+
+// There is fancy URL rewriting logic for the chrome://settings page that we need to work around.
+// Why? Special handling was added by Chrome team to allow a pushState transition between chrome:// pages.
+// As a result, the network URL (chrome://chrome/settings/) doesn't match the final document URL (chrome://settings/).
+function rewriteChromeInternalUrl(url) {
+  if (!url.startsWith('chrome://')) return url;
+  return url.replace(/^chrome:\/\/chrome\//, 'chrome://');
 }
 
-get LOAD(){
-return'Load';
-}
+/**
+ * Determine if url1 equals url2, ignoring URL fragments.
+ * @param {string} url1
+ * @param {string} url2
+ * @return {boolean}
+ */
+URL.equalWithExcludedFragments = function(url1, url2) {
+  [url1, url2] = [url1, url2].map(rewriteChromeInternalUrl);
+  try {
+    url1 = new URL(url1);
+    url1.hash = '';
 
+    url2 = new URL(url2);
+    url2.hash = '';
 
-init(trace){
-const io=new traceviewer.importer.ImportOptions();
-io.showImportWarnings=false;
-io.pruneEmptyContainers=false;
-io.shiftWorldToZero=true;
+    return url1.href === url2.href;
+  } catch (e) {
+    return false;
+  }
+};
 
-const model=new traceviewer.Model();
-const importer=new traceviewer.importer.Import(model,io);
-importer.importTraces([trace]);
+module.exports = URL;
 
-return model;
-}
+},{"whatwg-url":203}],26:[function(require,module,exports){
+(function (global){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
 
+/**
+ * Stubbery to allow portions of the DevTools frontend to be used in lighthouse. `WebInspector`
+ * technically lives on the global object but should be accessed through a normal `require` call.
+ */
+module.exports = (function() {
+  if (global.WebInspector) {
+    return global.WebInspector;
+  }
 
+  // Global pollution.
+  // Check below is to make it worker-friendly where global is worker's self.
+  if (global.self !== global) {
+    global.self = global;
+  }
 
+  if (typeof global.window === 'undefined') {
+    global.window = global;
+  }
 
+  global.Runtime = {};
+  global.Runtime.experiments = {
+    isEnabled(experimentName) {
+      switch (experimentName) {
+        case 'timelineLatencyInfo':
+          return true;
+        default:
+          return false;
+      }
+    }
+  };
+  global.Runtime.queryParam = function(arg) {
+    switch (arg) {
+      case 'remoteFrontend':
+        return false;
+      case 'ws':
+        return false;
+      default:
+        throw Error('Mock queryParam case not implemented.');
+    }
+  };
 
+  global.TreeElement = {};
+  global.WorkerRuntime = {};
 
+  global.Protocol = {
+    Agents() {}
+  };
 
+  global.WebInspector = {};
+  const WebInspector = global.WebInspector;
+  WebInspector._moduleSettings = {
+    cacheDisabled: {
+      addChangeListener() {},
+      get() {
+        return false;
+      }
+    },
+    monitoringXHREnabled: {
+      addChangeListener() {},
+      get() {
+        return false;
+      }
+    },
+    showNativeFunctionsInJSProfile: {
+      addChangeListener() {},
+      get() {
+        return true;
+      }
+    }
+  };
+  WebInspector.moduleSetting = function(settingName) {
+    return this._moduleSettings[settingName];
+  };
 
+  // Enum from chromium//src/third_party/WebKit/Source/core/loader/MixedContentChecker.h
+  global.NetworkAgent = {
+    RequestMixedContentType: {
+      Blockable: 'blockable',
+      OptionallyBlockable: 'optionally-blockable',
+      None: 'none'
+    },
+    BlockedReason: {
+      CSP: 'csp',
+      MixedContent: 'mixed-content',
+      Origin: 'origin',
+      Inspector: 'inspector',
+      Other: 'other'
+    },
+    InitiatorType: {
+      Other: 'other',
+      Parser: 'parser',
+      Redirect: 'redirect',
+      Script: 'script'
+    }
+  };
 
+  // Enum from SecurityState enum in protocol's Security domain
+  global.SecurityAgent = {
+    SecurityState: {
+      Unknown: 'unknown',
+      Neutral: 'neutral',
+      Insecure: 'insecure',
+      Warning: 'warning',
+      Secure: 'secure',
+      Info: 'info'
+    }
+  };
+  // From https://chromium.googlesource.com/chromium/src/third_party/WebKit/Source/devtools/+/master/protocol.json#93
+  global.PageAgent = {
+    ResourceType: {
+      Document: 'document',
+      Stylesheet: 'stylesheet',
+      Image: 'image',
+      Media: 'media',
+      Font: 'font',
+      Script: 'script',
+      TextTrack: 'texttrack',
+      XHR: 'xhr',
+      Fetch: 'fetch',
+      EventSource: 'eventsource',
+      WebSocket: 'websocket',
+      Manifest: 'manifest',
+      Other: 'other'
+    }
+  };
+  // Dependencies for network-recorder
+  require('chrome-devtools-frontend/front_end/common/Object.js');
+  require('chrome-devtools-frontend/front_end/common/ParsedURL.js');
+  require('chrome-devtools-frontend/front_end/common/ResourceType.js');
+  require('chrome-devtools-frontend/front_end/common/UIString.js');
+  require('chrome-devtools-frontend/front_end/platform/utilities.js');
+  require('chrome-devtools-frontend/front_end/sdk/Target.js');
+  require('chrome-devtools-frontend/front_end/sdk/TargetManager.js');
+  require('chrome-devtools-frontend/front_end/sdk/NetworkManager.js');
+  require('chrome-devtools-frontend/front_end/sdk/NetworkRequest.js');
 
-static _findMainThreadFromIds(model,processId,threadId){
-const modelHelper=model.getOrCreateHelper(traceviewer.model.helpers.ChromeModelHelper);
-const renderHelpers=traceviewer.b.dictionaryValues(modelHelper.rendererHelpers);
-const mainThread=renderHelpers.find(helper=>{
-return helper.mainThread&&
-helper.pid===processId&&
-helper.mainThread.tid===threadId;
-}).mainThread;
+  // Dependencies for timeline-model
+  WebInspector.targetManager = {
+    observeTargets() { },
+    addEventListener() { }
+  };
+  WebInspector.settings = {
+    createSetting() {
+      return {
+        get() {
+          return false;
+        },
+        addChangeListener() {}
+      };
+    }
+  };
+  WebInspector.console = {
+    error() {}
+  };
+  WebInspector.VBox = function() {};
+  WebInspector.HBox = function() {};
+  WebInspector.ViewportDataGrid = function() {};
+  WebInspector.ViewportDataGridNode = function() {};
+  global.WorkerRuntime.Worker = function() {};
 
-return mainThread;
-}
+  require('chrome-devtools-frontend/front_end/common/SegmentedRange.js');
+  require('chrome-devtools-frontend/front_end/bindings/TempFile.js');
+  require('chrome-devtools-frontend/front_end/sdk/TracingModel.js');
+  require('chrome-devtools-frontend/front_end/sdk/ProfileTreeModel.js');
+  require('chrome-devtools-frontend/front_end/timeline/TimelineUIUtils.js');
+  require('chrome-devtools-frontend/front_end/timeline_model/TimelineJSProfile.js');
+  require('chrome-devtools-frontend/front_end/sdk/CPUProfileDataModel.js');
+  require('chrome-devtools-frontend/front_end/timeline_model/LayerTreeModel.js');
+  require('chrome-devtools-frontend/front_end/timeline_model/TimelineModel.js');
+  require('chrome-devtools-frontend/front_end/ui_lazy/SortableDataGrid.js');
+  require('chrome-devtools-frontend/front_end/timeline/TimelineTreeView.js');
+  require('chrome-devtools-frontend/front_end/timeline_model/TimelineProfileTree.js');
+  require('chrome-devtools-frontend/front_end/components_lazy/FilmStripModel.js');
+  require('chrome-devtools-frontend/front_end/timeline_model/TimelineIRModel.js');
+  require('chrome-devtools-frontend/front_end/timeline_model/TimelineFrameModel.js');
 
+  // DevTools makes a few assumptions about using backing storage to hold traces.
+  WebInspector.DeferredTempFile = function() {};
+  WebInspector.DeferredTempFile.prototype = {
+    write: function() {},
+    finishWriting: function() {}
+  };
 
+  // Mock for WebInspector code that writes to console.
+  WebInspector.ConsoleMessage = function() {};
+  WebInspector.ConsoleMessage.MessageSource = {
+    Network: 'network'
+  };
+  WebInspector.ConsoleMessage.MessageLevel = {
+    Log: 'log'
+  };
+  WebInspector.ConsoleMessage.MessageType = {
+    Log: 'log'
+  };
 
+  // Mock NetworkLog
+  WebInspector.NetworkLog = function(target) {
+    this._requests = new Map();
+    target.networkManager.addEventListener(
+      WebInspector.NetworkManager.Events.RequestStarted, this._onRequestStarted, this);
+  };
 
+  WebInspector.NetworkLog.prototype = {
+    requestForURL: function(url) {
+      return this._requests.get(url) || null;
+    },
 
+    _onRequestStarted: function(event) {
+      const request = event.data;
+      if (this._requests.has(request.url)) {
+        return;
+      }
+      this._requests.set(request.url, request);
+    }
+  };
 
-
-
-
-
-
-
-
-
-
-
-
-static _riskPercentiles(durations,totalTime,percentiles,clippedLength=0){
-let busyTime=0;
-for(let i=0;i<durations.length;i++){
-busyTime+=durations[i];
-}
-busyTime-=clippedLength;
-
-
-let completedTime=totalTime-busyTime;
-let duration=0;
-let cdfTime=completedTime;
-const results=[];
-
-let durationIndex=-1;
-let remainingCount=durations.length+1;
-if(clippedLength>0){
-
-remainingCount--;
-}
-
-
-for(const percentile of percentiles){
-
-
-const percentileTime=percentile*totalTime;
-while(cdfTime<percentileTime&&durationIndex<durations.length-1){
-completedTime+=duration;
-remainingCount-=duration<0?-1:1;
-
-if(clippedLength>0&&clippedLength<durations[durationIndex+1]){
-duration=-clippedLength;
-clippedLength=0;
-}else{
-durationIndex++;
-duration=durations[durationIndex];
-}
-
-
-cdfTime=completedTime+Math.abs(duration)*remainingCount;
-}
-
-
-results.push({
-percentile,
-time:Math.max(0,(percentileTime-completedTime)/remainingCount)+BASE_RESPONSE_LATENCY});
-
-}
-
-return results;
-}
-
-
-
-
-
-
-
-
-
-
-
-
-static getRiskToResponsiveness(model,trace,startTime,endTime,percentiles){
-
-startTime=startTime===undefined?model.bounds.min:startTime;
-endTime=endTime===undefined?model.bounds.max:endTime;
-const totalTime=endTime-startTime;
-if(percentiles){
-percentiles.sort((a,b)=>a-b);
-}else{
-percentiles=[0.5,0.75,0.9,0.99,1];
-}
-
-const ret=TraceProcessor.getMainThreadTopLevelEventDurations(model,trace,startTime,
-endTime);
-return TraceProcessor._riskPercentiles(ret.durations,totalTime,percentiles,
-ret.clippedLength);
-}
-
-
-
-
-
-
-
-
-
-static getMainThreadTopLevelEventDurations(model,trace,startTime,endTime){
-
-const startEvent=trace.traceEvents.find(event=>{
-return event.name==='TracingStartedInPage';
-});
-const mainThread=TraceProcessor._findMainThreadFromIds(model,startEvent.pid,startEvent.tid);
-
-
-
-const durations=[];
-let clippedLength=0;
-mainThread.sliceGroup.topLevelSlices.forEach(slice=>{
-
-
-if(slice.end<=startTime||slice.start>=endTime){
-return;
-}
-
-
-let duration=slice.duration;
-let sliceStart=slice.start;
-if(sliceStart<startTime){
-
-sliceStart=startTime;
-duration=slice.end-sliceStart;
-}
-if(slice.end>endTime){
-
-clippedLength=duration-(endTime-sliceStart);
-}
-
-durations.push(duration);
-});
-durations.sort((a,b)=>a-b);
-
-return{
-durations,
-clippedLength};
-
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-static getLogNormalDistribution(median,falloff){
-const location=Math.log(median);
-
-
-
-
-const logRatio=Math.log(falloff/median);
-const shape=0.5*Math.sqrt(1-3*logRatio-
-Math.sqrt((logRatio-3)*(logRatio-3)-8));
-
-return new traceviewer.b.Statistics.LogNormalDistribution(location,shape);
-}}
-
-
-module.exports=TraceProcessor;
-
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../third_party/traceviewer-js/":84,"gl-matrix":236}],25:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
+  // Dependencies for color parsing.
+  require('chrome-devtools-frontend/front_end/common/Color.js');
 
+  /**
+   * Creates a new WebInspector NetworkManager using a mocked Target.
+   * @return {!WebInspector.NetworkManager}
+   */
+  WebInspector.NetworkManager.createWithFakeTarget = function(driver) {
+    // Mocked-up WebInspector Target for NetworkManager
+    const fakeNetworkAgent = {
+      enable() {},
+      getResponseBody(requestId, onComplete) {
+        driver.sendCommand('Network.getResponseBody', {
+          requestId,
+        })
+        .then(response => onComplete(null, response.body, response.base64Encoded))
+        .catch(err => onComplete(err));
+      }
+    };
+    const fakeConsoleModel = {
+      addMessage() {},
+      target() {}
+    };
+    const fakeTarget = {
+      _modelByConstructor: new Map(),
+      get consoleModel() {
+        return fakeConsoleModel;
+      },
+      networkAgent() {
+        return fakeNetworkAgent;
+      },
+      registerNetworkDispatcher() { },
+      model() { }
+    };
 
+    fakeTarget.networkManager = new WebInspector.NetworkManager(fakeTarget);
+    fakeTarget.networkLog = new WebInspector.NetworkLog(fakeTarget);
 
+    WebInspector.NetworkLog.fromTarget = () => {
+      return fakeTarget.networkLog;
+    };
 
+    return fakeTarget.networkManager;
+  };
 
+  // Dependencies for CSS parsing.
+  require('chrome-devtools-frontend/front_end/common/TextRange.js');
+  const gonzales = require('chrome-devtools-frontend/front_end/gonzales/gonzales-scss.js');
+  require('chrome-devtools-frontend/front_end/gonzales/SCSSParser.js');
 
+  // Mostly taken from from chrome-devtools-frontend/front_end/gonzales/SCSSParser.js.
+  WebInspector.SCSSParser.prototype.parse = function(content) {
+    let ast = null;
+    try {
+      ast = gonzales.parse(content, {syntax: 'css'});
+    } catch (e) {
+      return {error: e};
+    }
 
+    /** @type {!{properties: !Array<!Gonzales.Node>, node: !Gonzales.Node}} */
+    const rootBlock = {
+      properties: [],
+      node: ast
+    };
+    /** @type {!Array<!{properties: !Array<!Gonzales.Node>, node: !Gonzales.Node}>} */
+    const blocks = [rootBlock];
+    ast.selectors = [];
+    WebInspector.SCSSParser.extractNodes(ast, blocks, rootBlock);
 
+    return ast;
+  };
 
+  return WebInspector;
+})();
 
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"chrome-devtools-frontend/front_end/bindings/TempFile.js":234,"chrome-devtools-frontend/front_end/common/Color.js":235,"chrome-devtools-frontend/front_end/common/Object.js":236,"chrome-devtools-frontend/front_end/common/ParsedURL.js":237,"chrome-devtools-frontend/front_end/common/ResourceType.js":238,"chrome-devtools-frontend/front_end/common/SegmentedRange.js":239,"chrome-devtools-frontend/front_end/common/TextRange.js":240,"chrome-devtools-frontend/front_end/common/UIString.js":241,"chrome-devtools-frontend/front_end/components_lazy/FilmStripModel.js":242,"chrome-devtools-frontend/front_end/gonzales/SCSSParser.js":243,"chrome-devtools-frontend/front_end/gonzales/gonzales-scss.js":244,"chrome-devtools-frontend/front_end/platform/utilities.js":245,"chrome-devtools-frontend/front_end/sdk/CPUProfileDataModel.js":246,"chrome-devtools-frontend/front_end/sdk/NetworkManager.js":247,"chrome-devtools-frontend/front_end/sdk/NetworkRequest.js":248,"chrome-devtools-frontend/front_end/sdk/ProfileTreeModel.js":249,"chrome-devtools-frontend/front_end/sdk/Target.js":250,"chrome-devtools-frontend/front_end/sdk/TargetManager.js":251,"chrome-devtools-frontend/front_end/sdk/TracingModel.js":252,"chrome-devtools-frontend/front_end/timeline/TimelineTreeView.js":253,"chrome-devtools-frontend/front_end/timeline/TimelineUIUtils.js":254,"chrome-devtools-frontend/front_end/timeline_model/LayerTreeModel.js":255,"chrome-devtools-frontend/front_end/timeline_model/TimelineFrameModel.js":256,"chrome-devtools-frontend/front_end/timeline_model/TimelineIRModel.js":257,"chrome-devtools-frontend/front_end/timeline_model/TimelineJSProfile.js":258,"chrome-devtools-frontend/front_end/timeline_model/TimelineModel.js":259,"chrome-devtools-frontend/front_end/timeline_model/TimelineProfileTree.js":260,"chrome-devtools-frontend/front_end/ui_lazy/SortableDataGrid.js":261}],27:[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 'use strict';
 
 
 
-const ELLIPSIS='\u2026';
+// Auto convert partial filenames to lookup for audits to use.
+const formatters = {};
+["accessibility.html","cards.css","cards.html","critical-request-chains.css","critical-request-chains.html","null.html","speedline.html","table.css","table.html","templates","url-list.css","url-list.html","user-timings.css","user-timings.html"]
+  .filter(filename => filename.endsWith('.html'))
+  .forEach(filename => {
+    const baseName = filename.replace(/\.html$/, '');
+    const capsName = baseName.replace(/-/g, '_').toUpperCase();
+    formatters[capsName] = baseName;
+  });
 
 
-
-const URL=typeof self!=='undefined'&&self.URL||require('whatwg-url').URL;
-
-URL.INVALID_URL_DEBUG_STRING=
-'Lighthouse was unable to determine the URL of some script executions. '+
-'It\'s possible a Chrome extension or other eval\'d code is the source.';
-
-
-
-
-
-URL.isValid=function isValid(url){
-try{
-new URL(url);
-return true;
-}catch(e){
-return false;
-}
-};
-
-
-
-
-
-
-URL.hostsMatch=function hostsMatch(urlA,urlB){
-try{
-return new URL(urlA).host===new URL(urlB).host;
-}catch(e){
-return false;
-}
-};
-
-
-
-
-
-
-URL.getDisplayName=function getDisplayName(url,options){
-options=Object.assign({
-numPathParts:2,
-preserveQuery:false,
-preserveHost:false},
-options);
-
-const parsed=new URL(url);
-
-let name;
-
-if(parsed.protocol==='about:'||parsed.protocol==='data:'){
-
-name=parsed.href;
-}else{
-name=parsed.pathname;
-const parts=name.split('/');
-if(options.numPathParts&&parts.length>options.numPathParts){
-name=ELLIPSIS+parts.slice(-1*options.numPathParts).join('/');
+class Formatter {
+  /**
+   * Getter that returns a list of supported formatters, mapping an all-caps
+   * name to the basename of the available formatter partials
+   * (e.g. `CRITICAL_REQUEST_CHAINS` to `critical-request-chains).
+   * @return {!Object<string, string>}
+   */
+  static get SUPPORTED_FORMATS() {
+    return formatters;
+  }
 }
 
-if(options.preserveHost){
-name=`${parsed.host}/${name.replace(/^\//,'')}`;
-}
-if(options.preserveQuery){
-name=`${name}${parsed.search}`;
-}
-}
-
-const MAX_LENGTH=64;
-
-name=name.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g,`$1${ELLIPSIS}`);
-
-
-if(name.length>MAX_LENGTH&&name.includes('?')){
-
-name=name.replace(/\?([^=]*)(=)?.*/,`?$1$2${ELLIPSIS}`);
-
-
-if(name.length>MAX_LENGTH){
-name=name.replace(/\?.*/,`?${ELLIPSIS}`);
-}
-}
-
-
-if(name.length>MAX_LENGTH){
-const dotIndex=name.lastIndexOf('.');
-if(dotIndex>=0){
-name=name.slice(0,MAX_LENGTH-1-(name.length-dotIndex))+
-
-`${ELLIPSIS}${name.slice(dotIndex)}`;
-}else{
-name=name.slice(0,MAX_LENGTH-1)+ELLIPSIS;
-}
-}
-
-return name;
-};
-
-
-
-
-function rewriteChromeInternalUrl(url){
-if(!url.startsWith('chrome://'))return url;
-return url.replace(/^chrome:\/\/chrome\//,'chrome://');
-}
-
-
-
-
-
-
-
-URL.equalWithExcludedFragments=function(url1,url2){
-[url1,url2]=[url1,url2].map(rewriteChromeInternalUrl);
-
-url1=new URL(url1);
-url1.hash='';
-
-url2=new URL(url2);
-url2.hash='';
-
-return url1.href===url2.href;
-};
-
-module.exports=URL;
-
-},{"whatwg-url":198}],26:[function(require,module,exports){
-(function(global){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-'use strict';
-
-
-
-
-
-module.exports=function(){
-if(global.WebInspector){
-return global.WebInspector;
-}
-
-
-
-if(global.self!==global){
-global.self=global;
-}
-
-if(typeof global.window==='undefined'){
-global.window=global;
-}
-
-global.Runtime={};
-global.Runtime.experiments={
-isEnabled(experimentName){
-switch(experimentName){
-case'timelineLatencyInfo':
-return true;
-default:
-return false;}
-
-}};
-
-global.Runtime.queryParam=function(arg){
-switch(arg){
-case'remoteFrontend':
-return false;
-case'ws':
-return false;
-default:
-throw Error('Mock queryParam case not implemented.');}
-
-};
-
-global.TreeElement={};
-global.WorkerRuntime={};
-
-global.Protocol={
-Agents(){}};
-
-
-global.WebInspector={};
-const WebInspector=global.WebInspector;
-WebInspector._moduleSettings={
-cacheDisabled:{
-addChangeListener(){},
-get(){
-return false;
-}},
-
-monitoringXHREnabled:{
-addChangeListener(){},
-get(){
-return false;
-}},
-
-showNativeFunctionsInJSProfile:{
-addChangeListener(){},
-get(){
-return true;
-}}};
-
-
-WebInspector.moduleSetting=function(settingName){
-return this._moduleSettings[settingName];
-};
-
-
-global.NetworkAgent={
-RequestMixedContentType:{
-Blockable:'blockable',
-OptionallyBlockable:'optionally-blockable',
-None:'none'},
-
-BlockedReason:{
-CSP:'csp',
-MixedContent:'mixed-content',
-Origin:'origin',
-Inspector:'inspector',
-Other:'other'},
-
-InitiatorType:{
-Other:'other',
-Parser:'parser',
-Redirect:'redirect',
-Script:'script'}};
-
-
-
-
-global.SecurityAgent={
-SecurityState:{
-Unknown:'unknown',
-Neutral:'neutral',
-Insecure:'insecure',
-Warning:'warning',
-Secure:'secure',
-Info:'info'}};
-
-
-
-global.PageAgent={
-ResourceType:{
-Document:'document',
-Stylesheet:'stylesheet',
-Image:'image',
-Media:'media',
-Font:'font',
-Script:'script',
-TextTrack:'texttrack',
-XHR:'xhr',
-Fetch:'fetch',
-EventSource:'eventsource',
-WebSocket:'websocket',
-Manifest:'manifest',
-Other:'other'}};
-
-
-
-require('chrome-devtools-frontend/front_end/common/Object.js');
-require('chrome-devtools-frontend/front_end/common/ParsedURL.js');
-require('chrome-devtools-frontend/front_end/common/ResourceType.js');
-require('chrome-devtools-frontend/front_end/common/UIString.js');
-require('chrome-devtools-frontend/front_end/platform/utilities.js');
-require('chrome-devtools-frontend/front_end/sdk/Target.js');
-require('chrome-devtools-frontend/front_end/sdk/TargetManager.js');
-require('chrome-devtools-frontend/front_end/sdk/NetworkManager.js');
-require('chrome-devtools-frontend/front_end/sdk/NetworkRequest.js');
-
-
-WebInspector.targetManager={
-observeTargets(){},
-addEventListener(){}};
-
-WebInspector.settings={
-createSetting(){
-return{
-get(){
-return false;
-},
-addChangeListener(){}};
-
-}};
-
-WebInspector.console={
-error(){}};
-
-WebInspector.VBox=function(){};
-WebInspector.HBox=function(){};
-WebInspector.ViewportDataGrid=function(){};
-WebInspector.ViewportDataGridNode=function(){};
-global.WorkerRuntime.Worker=function(){};
-
-require('chrome-devtools-frontend/front_end/common/SegmentedRange.js');
-require('chrome-devtools-frontend/front_end/bindings/TempFile.js');
-require('chrome-devtools-frontend/front_end/sdk/TracingModel.js');
-require('chrome-devtools-frontend/front_end/sdk/ProfileTreeModel.js');
-require('chrome-devtools-frontend/front_end/timeline/TimelineUIUtils.js');
-require('chrome-devtools-frontend/front_end/timeline_model/TimelineJSProfile.js');
-require('chrome-devtools-frontend/front_end/sdk/CPUProfileDataModel.js');
-require('chrome-devtools-frontend/front_end/timeline_model/LayerTreeModel.js');
-require('chrome-devtools-frontend/front_end/timeline_model/TimelineModel.js');
-require('chrome-devtools-frontend/front_end/ui_lazy/SortableDataGrid.js');
-require('chrome-devtools-frontend/front_end/timeline/TimelineTreeView.js');
-require('chrome-devtools-frontend/front_end/timeline_model/TimelineProfileTree.js');
-require('chrome-devtools-frontend/front_end/components_lazy/FilmStripModel.js');
-require('chrome-devtools-frontend/front_end/timeline_model/TimelineIRModel.js');
-require('chrome-devtools-frontend/front_end/timeline_model/TimelineFrameModel.js');
-
-
-WebInspector.DeferredTempFile=function(){};
-WebInspector.DeferredTempFile.prototype={
-write:function(){},
-finishWriting:function(){}};
-
-
-
-WebInspector.ConsoleMessage=function(){};
-WebInspector.ConsoleMessage.MessageSource={
-Network:'network'};
-
-WebInspector.ConsoleMessage.MessageLevel={
-Log:'log'};
-
-WebInspector.ConsoleMessage.MessageType={
-Log:'log'};
-
-
-
-WebInspector.NetworkLog=function(target){
-this._requests=new Map();
-target.networkManager.addEventListener(
-WebInspector.NetworkManager.Events.RequestStarted,this._onRequestStarted,this);
-};
-
-WebInspector.NetworkLog.prototype={
-requestForURL:function(url){
-return this._requests.get(url)||null;
-},
-
-_onRequestStarted:function(event){
-const request=event.data;
-if(this._requests.has(request.url)){
-return;
-}
-this._requests.set(request.url,request);
-}};
-
-
-
-require('chrome-devtools-frontend/front_end/common/Color.js');
-
-
-
-
-
-WebInspector.NetworkManager.createWithFakeTarget=function(){
-
-const fakeNetworkAgent={
-enable(){}};
-
-const fakeConsoleModel={
-addMessage(){},
-target(){}};
-
-const fakeTarget={
-_modelByConstructor:new Map(),
-get consoleModel(){
-return fakeConsoleModel;
-},
-networkAgent(){
-return fakeNetworkAgent;
-},
-registerNetworkDispatcher(){},
-model(){}};
-
-
-fakeTarget.networkManager=new WebInspector.NetworkManager(fakeTarget);
-fakeTarget.networkLog=new WebInspector.NetworkLog(fakeTarget);
-
-WebInspector.NetworkLog.fromTarget=()=>{
-return fakeTarget.networkLog;
-};
-
-return fakeTarget.networkManager;
-};
-
-
-require('chrome-devtools-frontend/front_end/common/TextRange.js');
-const gonzales=require('chrome-devtools-frontend/front_end/gonzales/gonzales-scss.js');
-require('chrome-devtools-frontend/front_end/gonzales/SCSSParser.js');
-
-
-WebInspector.SCSSParser.prototype.parse=function(content){
-let ast=null;
-try{
-ast=gonzales.parse(content,{syntax:'css'});
-}catch(e){
-return{error:e};
-}
-
-
-const rootBlock={
-properties:[],
-node:ast};
-
-
-const blocks=[rootBlock];
-ast.selectors=[];
-WebInspector.SCSSParser.extractNodes(ast,blocks,rootBlock);
-
-return ast;
-};
-
-return WebInspector;
-}();
-
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"chrome-devtools-frontend/front_end/bindings/TempFile.js":205,"chrome-devtools-frontend/front_end/common/Color.js":206,"chrome-devtools-frontend/front_end/common/Object.js":207,"chrome-devtools-frontend/front_end/common/ParsedURL.js":208,"chrome-devtools-frontend/front_end/common/ResourceType.js":209,"chrome-devtools-frontend/front_end/common/SegmentedRange.js":210,"chrome-devtools-frontend/front_end/common/TextRange.js":211,"chrome-devtools-frontend/front_end/common/UIString.js":212,"chrome-devtools-frontend/front_end/components_lazy/FilmStripModel.js":213,"chrome-devtools-frontend/front_end/gonzales/SCSSParser.js":214,"chrome-devtools-frontend/front_end/gonzales/gonzales-scss.js":215,"chrome-devtools-frontend/front_end/platform/utilities.js":216,"chrome-devtools-frontend/front_end/sdk/CPUProfileDataModel.js":217,"chrome-devtools-frontend/front_end/sdk/NetworkManager.js":218,"chrome-devtools-frontend/front_end/sdk/NetworkRequest.js":219,"chrome-devtools-frontend/front_end/sdk/ProfileTreeModel.js":220,"chrome-devtools-frontend/front_end/sdk/Target.js":221,"chrome-devtools-frontend/front_end/sdk/TargetManager.js":222,"chrome-devtools-frontend/front_end/sdk/TracingModel.js":223,"chrome-devtools-frontend/front_end/timeline/TimelineTreeView.js":224,"chrome-devtools-frontend/front_end/timeline/TimelineUIUtils.js":225,"chrome-devtools-frontend/front_end/timeline_model/LayerTreeModel.js":226,"chrome-devtools-frontend/front_end/timeline_model/TimelineFrameModel.js":227,"chrome-devtools-frontend/front_end/timeline_model/TimelineIRModel.js":228,"chrome-devtools-frontend/front_end/timeline_model/TimelineJSProfile.js":229,"chrome-devtools-frontend/front_end/timeline_model/TimelineModel.js":230,"chrome-devtools-frontend/front_end/timeline_model/TimelineProfileTree.js":231,"chrome-devtools-frontend/front_end/ui_lazy/SortableDataGrid.js":232}],27:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-'use strict';
-
-
-
-
-const formatters={};
-["accessibility.html","cards.css","cards.html","critical-request-chains.css","critical-request-chains.html","null.html","speedline.html","table.css","table.html","templates","url-list.css","url-list.html","user-timings.css","user-timings.html"].
-filter(filename=>filename.endsWith('.html')).
-forEach(filename=>{
-const baseName=filename.replace(/\.html$/,'');
-const capsName=baseName.replace(/-/g,'_').toUpperCase();
-formatters[capsName]=baseName;
-});
-
-
-class Formatter{
-
-
-
-
-
-
-static get SUPPORTED_FORMATS(){
-return formatters;
-}}
-
-
-module.exports=Formatter;
+module.exports = Formatter;
 
 },{}],28:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
+/* global Intl */
 
+const Handlebars = require('handlebars/runtime');
+const marked = require('marked');
+const URL = require('../lib/url-shim');
 
-const Handlebars=require('handlebars/runtime');
-const marked=require('marked');
-const URL=require('../lib/url-shim');
-
-const RATINGS={
-GOOD:{label:'good',minScore:75},
-AVERAGE:{label:'average',minScore:45},
-POOR:{label:'poor'}};
-
-
-
-
-
-
-
-function calculateRating(value){
-let rating=RATINGS.POOR.label;
-if(value>=RATINGS.GOOD.minScore){
-rating=RATINGS.GOOD.label;
-}else if(value>=RATINGS.AVERAGE.minScore){
-rating=RATINGS.AVERAGE.label;
-}
-return rating;
-}
-
-
-
-
-
-
-function formatNumber(number){
-return number.toLocaleString(undefined,{maximumFractionDigits:1});
-}
-
-
-
-
-
-
-
-const getItemRating=function(value){
-if(typeof value==='boolean'){
-return value?RATINGS.GOOD.label:RATINGS.POOR.label;
-}
-return calculateRating(value);
+const RATINGS = {
+  GOOD: {label: 'good', minScore: 75},
+  AVERAGE: {label: 'average', minScore: 45},
+  POOR: {label: 'poor'}
 };
 
+/**
+ * Convert a score to a rating label.
+ * @param {number} value
+ * @return {string}
+ */
+function calculateRating(value) {
+  let rating = RATINGS.POOR.label;
+  if (value >= RATINGS.GOOD.minScore) {
+    rating = RATINGS.GOOD.label;
+  } else if (value >= RATINGS.AVERAGE.minScore) {
+    rating = RATINGS.AVERAGE.label;
+  }
+  return rating;
+}
 
+/**
+ * Format number.
+ * @param {number} number
+ * @return {string}
+ */
+function formatNumber(number) {
+  return number.toLocaleString(undefined, {maximumFractionDigits: 1});
+}
 
-
-
-
-const getTotalScore=function(aggregation){
-return Math.round(aggregation.total*100);
+/**
+ * Converts a value to a rating string, which can be used inside the report for
+ * color styling.
+ * @param {(boolean|number)} value
+ * @return {string}
+ */
+const getItemRating = function(value) {
+  if (typeof value === 'boolean') {
+    return value ? RATINGS.GOOD.label : RATINGS.POOR.label;
+  }
+  return calculateRating(value);
 };
 
-const handlebarHelpers={
-
-
-
-
-
-and:(...args)=>{
-let arg=false;
-for(let i=0,n=args.length-1;i<n;i++){
-arg=args[i];
-if(!arg){
-break;
-}
-}
-return arg;
-},
-
-
-
-
-
-
-chainDuration(startTime,endTime){
-return formatNumber((endTime-startTime)*1000);
-},
-
-
-
-
-
-
-
-
-createContextFor(parent,id,treeMarkers,parentIsLastChild,startTime,transferSize,opts){
-const node=parent[id];
-const siblings=Object.keys(parent);
-const isLastChild=siblings.indexOf(id)===siblings.length-1;
-const hasChildren=Object.keys(node.children).length>0;
-
-
-const newTreeMarkers=Array.isArray(treeMarkers)?treeMarkers.slice(0):[];
-
-
-if(typeof parentIsLastChild!=='undefined'){
-newTreeMarkers.push(!parentIsLastChild);
-}
-
-return opts.fn({
-node,
-isLastChild,
-hasChildren,
-startTime,
-transferSize:transferSize+node.request.transferSize,
-treeMarkers:newTreeMarkers});
-
-},
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-createTable(headings,results,opts){
-const headingKeys=Object.keys(headings);
-
-const rows=results.map(result=>{
-const cols=headingKeys.map(key=>{
-let value=result[key];
-if(typeof value==='undefined'){
-value='--';
-}
-
-switch(key){
-case'preview':
-if(/^image/.test(value.mimeType)){
-
-const encodedUrl=value.url.replace(/\)/g,'%29');
-return`[![Image preview](${encodedUrl} "Image preview")](${encodedUrl})`;
-}
-return'';
-case'code':
-return'`'+value.trim()+'`';
-case'pre':
-return'\`\`\`\n'+result[key].trim()+'\`\`\`';
-case'lineCol':
-return`${result.line}:${result.col}`;
-case'isEval':
-return value?'yes':'';
-default:
-return String(value);}
-
-});
-
-return{cols};
-});
-
-headings=headingKeys.map(key=>headings[key]);
-
-const table={headings,rows,headingKeys};
-
-return opts.fn(table);
-},
-
-
-
-
-
-
-
-createTreeRenderContext(tree,opts){
-const transferSize=0;
-let startTime=0;
-const rootNodes=Object.keys(tree);
-
-if(rootNodes.length>0){
-startTime=tree[rootNodes[0]].request.startTime;
-}
-
-return opts.fn({
-tree,
-startTime,
-transferSize});
-
-},
-
-
-
-
-
-
-decimal:maybeNumber=>{
-if(maybeNumber&&maybeNumber.toFixed){
-return maybeNumber.toFixed(2);
-}
-return maybeNumber;
-},
-
-
-
-
-
-
-formatDateTime:date=>{
-const options={
-day:'numeric',month:'numeric',year:'numeric',
-hour:'numeric',minute:'numeric',second:'numeric',
-timeZoneName:'short'};
-
-let formatter=new Intl.DateTimeFormat('en-US',options);
-
-
-
-const tz=formatter.resolvedOptions().timeZone;
-if(!tz||tz.toLowerCase()==='etc/unknown'){
-options.timeZone='UTC';
-formatter=new Intl.DateTimeFormat('en-US',options);
-}
-return formatter.format(new Date(date));
-},
-
-formatNumber,
-
-
-
-
-
-
-formatTransferSize:size=>{
-return(size/1024).toLocaleString(undefined,{maximumFractionDigits:2});
-},
-
-
-
-
-
-
-getAggregationScoreRating:score=>{
-return calculateRating(Math.round(score*100));
-},
-
-getItemRating,
-
-
-
-
-
-
-
-getScoreBadIcon:(informative,additional)=>
-informative||additional?'warning score-warning-bg':'poor score-poor-bg',
-
-
-
-
-
-
-getScoreGoodIcon:informative=>informative?'info':'good',
-
-getTotalScore,
-
-
-
-
-
-
-getTotalScoreRating:aggregation=>{
-const totalScore=getTotalScore(aggregation);
-return calculateRating(totalScore);
-},
-
-
-
-
-
-
-
-gt:(a,b)=>a>b,
-
-
-
-
-
-
-
-
-ifEq:function(lhs,rhs,options){
-if(lhs===rhs){
-
-return options.fn(this);
-}else{
-
-return options.inverse(this);
-}
-},
-
-
-
-
-
-
-
-
-ifNotEq:function(lhs,rhs,options){
-if(lhs!==rhs){
-
-return options.fn(this);
-}else{
-
-return options.inverse(this);
-}
-},
-
-
-
-
-
-isBool:value=>typeof value==='boolean',
-
-
-
-
-
-
-kebabCase:str=>{
-return(str||'').
-
-split(/([A-Z]+[a-z0-9]*)/).
-
-map(part=>part.toLowerCase().replace(/[^a-z0-9]+/gi,'-')).
-
-join('-').
-
-replace(/-+/g,'-').
-
-replace(/(^-|-$)/g,'');
-},
-
-
-
-
-
-
-nameToLink:name=>{
-return name.toLowerCase().replace(/\s/g,'-');
-},
-
-
-
-
-
-
-not:value=>!value,
-
-
-
-
-
-
-
-parseURL:(resourceURL,opts)=>{
-const parsedURL={
-file:URL.getDisplayName(resourceURL),
-hostname:new URL(resourceURL).hostname};
-
-
-return opts.fn(parsedURL);
-},
-
-
-
-
-
-
-
-
-
-sanitize:str=>{
-
-
-const renderer=new marked.Renderer();
-renderer.em=str=>`<em>${str}</em>`;
-renderer.link=(href,title,text)=>{
-const titleAttr=title?`title="${title}"`:'';
-return`<a href="${href}" target="_blank" rel="noopener" ${titleAttr}>${text}</a>`;
-};
-renderer.codespan=function(str){
-return`<code>${str}</code>`;
+/**
+ * Figures out the total score for an aggregation
+ * @param {{total: number}} aggregation
+ * @return {number}
+ */
+const getTotalScore = function(aggregation) {
+  return Math.round(aggregation.total * 100);
 };
 
-renderer.code=function(code,language){
-return`<pre>${Handlebars.Utils.escapeExpression(code)}</pre>`;
-};
-renderer.image=function(src,title,text){
-return`<img src="${src}" alt="${text}" title="${title}">`;
+const handlebarHelpers = {
+  /**
+   * arg1 && arg2 && ... && argn
+   * @param {...*} args
+   * @return {*}
+   */
+  and: (...args) => {
+    let arg = false;
+    for (let i = 0, n = args.length - 1; i < n; i++) {
+      arg = args[i];
+      if (!arg) {
+        break;
+      }
+    }
+    return arg;
+  },
+
+  /**
+   * @param {number} startTime
+   * @param {number} endTime
+   * @return {string}
+   */
+  chainDuration(startTime, endTime) {
+    return formatNumber((endTime - startTime) * 1000);
+  },
+
+  /**
+   * Helper function for Handlebars that creates the context for each
+   * critical-request-chain node based on its parent. Calculates if this node is
+   * the last child, whether it has any children itself and what the tree looks
+   * like all the way back up to the root, so the tree markers can be drawn
+   * correctly.
+   */
+  createContextFor(parent, id, treeMarkers, parentIsLastChild, startTime, transferSize, opts) {
+    const node = parent[id];
+    const siblings = Object.keys(parent);
+    const isLastChild = siblings.indexOf(id) === (siblings.length - 1);
+    const hasChildren = Object.keys(node.children).length > 0;
+
+    // Copy the tree markers so that we don't change by reference.
+    const newTreeMarkers = Array.isArray(treeMarkers) ? treeMarkers.slice(0) : [];
+
+    // Add on the new entry.
+    if (typeof parentIsLastChild !== 'undefined') {
+      newTreeMarkers.push(!parentIsLastChild);
+    }
+
+    return opts.fn({
+      node,
+      isLastChild,
+      hasChildren,
+      startTime,
+      transferSize: (transferSize + node.request.transferSize),
+      treeMarkers: newTreeMarkers
+    });
+  },
+
+  /**
+   * Preps a formatted table (headings/col vals) for output.
+   * @param {!Object<string>} headings for the table. The order of this
+   *     object's key/value pairs determines the order of the HTML table headings.
+   *     There is special handling for certain keys:
+   *       preview {url: string, mimeType: string}: For image mimetypes, wraps
+   *           the value in a markdown image.
+   *       code: wraps the value in single ` for a markdown code snippet.
+   *       pre: wraps the value in triple ``` for a markdown code block.
+   *       lineCol: combines the values for the line and col keys into a single
+   *                value "line/col".
+   *       isEval: returns "yes" if the script was eval'd.
+   *       All other values are passed through as is.
+   * @param {!Array<!Object>} results Audit results.
+   * @param {{fn: function(*): string}} opts
+   * @return {{headings: !Array<string>, rows: !Array<{cols: !Array<*>}>}}
+   */
+  createTable(headings, results, opts) {
+    const headingKeys = Object.keys(headings);
+
+    const rows = results.map(result => {
+      const cols = headingKeys.map(key => {
+        let value = result[key];
+        if (typeof value === 'undefined') {
+          value = '--';
+        }
+
+        switch (key) {
+          case 'preview':
+            if (/^image/.test(value.mimeType)) {
+              // Markdown can't handle URLs with parentheses which aren't automatically encoded
+              const encodedUrl = value.url.replace(/\)/g, '%29');
+              return `[![Image preview](${encodedUrl} "Image preview")](${encodedUrl})`;
+            }
+            return '';
+          case 'code':
+            return '`' + value.trim() + '`';
+          case 'pre':
+            return '\`\`\`\n' + result[key].trim() + '\`\`\`';
+          case 'lineCol':
+            return `${result.line}:${result.col}`;
+          case 'isEval':
+            return value ? 'yes' : '';
+          default:
+            return String(value);
+        }
+      });
+
+      return {cols};
+    });
+
+    headings = headingKeys.map(key => headings[key]);
+
+    const table = {headings, rows, headingKeys};
+
+    return opts.fn(table);
+  },
+
+  /**
+   * Create render context for critical-request-chain tree display.
+   * @param {!Object} tree
+   * @param {!Object} opts
+   * @return {string}
+   */
+  createTreeRenderContext(tree, opts) {
+    const transferSize = 0;
+    let startTime = 0;
+    const rootNodes = Object.keys(tree);
+
+    if (rootNodes.length > 0) {
+      startTime = tree[rootNodes[0]].request.startTime;
+    }
+
+    return opts.fn({
+      tree,
+      startTime,
+      transferSize
+    });
+  },
+
+  /**
+   * Convert numbers to fixed point decimals strings.
+   * @param {*} maybeNumber
+   * @return {*}
+   */
+  decimal: maybeNumber => {
+    if (maybeNumber && maybeNumber.toFixed) {
+      return maybeNumber.toFixed(2);
+    }
+    return maybeNumber;
+  },
+
+  /**
+   * Format time.
+   * @param {string} date
+   * @return {string}
+   */
+  formatDateTime: date => {
+    const options = {
+      day: 'numeric', month: 'numeric', year: 'numeric',
+      hour: 'numeric', minute: 'numeric', second: 'numeric',
+      timeZoneName: 'short'
+    };
+    let formatter = new Intl.DateTimeFormat('en-US', options);
+
+    // Force UTC if runtime timezone could not be detected.
+    // See https://github.com/GoogleChrome/lighthouse/issues/1056
+    const tz = formatter.resolvedOptions().timeZone;
+    if (!tz || tz.toLowerCase() === 'etc/unknown') {
+      options.timeZone = 'UTC';
+      formatter = new Intl.DateTimeFormat('en-US', options);
+    }
+    return formatter.format(new Date(date));
+  },
+
+  formatNumber,
+
+  /**
+   * Format transfer size.
+   * @param {number} number
+   * @return {string}
+   */
+  formatTransferSize: size => {
+    return (size / 1024).toLocaleString(undefined, {maximumFractionDigits: 2});
+  },
+
+  /**
+   * Converts an aggregation's score to a rating that can be used for styling.
+   * @param {number} score
+   * @return {string}
+   */
+  getAggregationScoreRating: score => {
+    return calculateRating(Math.round(score * 100));
+  },
+
+  getItemRating,
+
+  /**
+   * Figures out the icon to display when fail or warn.
+   * @param {boolean} informative
+   * @param {boolean} additional
+   * @return {string}
+   */
+  getScoreBadIcon: (informative, additional) =>
+    (informative || additional) ? 'warning score-warning-bg':'poor score-poor-bg',
+
+  /**
+   * Figures out the icon to display when success or info.
+   * @param {boolean} informative
+   * @return {string}
+   */
+  getScoreGoodIcon: informative => informative ? 'info' : 'good',
+
+  getTotalScore,
+
+  /**
+   * Converts the total score to a rating that can be used for styling.
+   * @param {{total: number}} aggregation
+   * @return {string}
+   */
+  getTotalScoreRating: aggregation => {
+    const totalScore = getTotalScore(aggregation);
+    return calculateRating(totalScore);
+  },
+
+  /**
+   * a > b
+   * @param {number} a
+   * @param {number} b
+   * @return {boolean}
+   */
+  gt: (a, b) => a > b,
+
+  /**
+   * lhs === rhs
+   * @param {*} lhs
+   * @param {*} rhs
+   * @param {!Object} options
+   * @return {string}
+   */
+  ifEq: function(lhs, rhs, options) {
+    if (lhs === rhs) {
+      // eslint-disable-next-line no-invalid-this
+      return options.fn(this);
+    } else {
+      // eslint-disable-next-line no-invalid-this
+      return options.inverse(this);
+    }
+  },
+
+  /**
+   * lhs !== rhs
+   * @param {*} lhs
+   * @param {*} rhs
+   * @param {!Object} options
+   * @return {string}
+   */
+  ifNotEq: function(lhs, rhs, options) {
+    if (lhs !== rhs) {
+      // eslint-disable-next-line no-invalid-this
+      return options.fn(this);
+    } else {
+      // eslint-disable-next-line no-invalid-this
+      return options.inverse(this);
+    }
+  },
+
+  /**
+   * @param {*} value
+   * @return {boolean}
+   */
+  isBool: value => (typeof value === 'boolean'),
+
+  /**
+   * myFavoriteVar -> my-favorite-var
+   * @param {string} str
+   * @return {string}
+   */
+  kebabCase: str => {
+    return (str || '')
+      // break up camelCase tokens
+      .split(/([A-Z]+[a-z0-9]*)/)
+      // replace all special characters and whitespace with hyphens
+      .map(part => part.toLowerCase().replace(/[^a-z0-9]+/gi, '-'))
+      // rejoin into a single string
+      .join('-')
+      // de-dupe hyphens
+      .replace(/-+/g, '-')
+      // remove leading or trailing hyphens
+      .replace(/(^-|-$)/g, '');
+  },
+
+  /**
+   * Converts a name to a link.
+   * @param {string} name
+   * @return {string}
+   */
+  nameToLink: name => {
+    return name.toLowerCase().replace(/\s/g, '-');
+  },
+
+  /**
+   * Coerces value to boolean, returns the negation.
+   * @param {*} value
+   * @return {boolean}
+   */
+  not: value => !value,
+
+  /**
+   * Split the URL into a file and hostname for easy display.
+   * @param {string} resourceURL
+   * @param {!Object} opts
+   * @return {string}
+   */
+  parseURL: (resourceURL, opts) => {
+    const parsedURL = {
+      file: URL.getDisplayName(resourceURL),
+      hostname: new URL(resourceURL).hostname
+    };
+
+    return opts.fn(parsedURL);
+  },
+
+  /**
+   * Allow the report to inject HTML, but sanitize it first.
+   * Viewer in particular, allows user's to upload JSON. To mitigate against
+   * XSS, define a renderer that only transforms a few types of markdown blocks.
+   * All other markdown and HTML is ignored.
+   * @param {string} str
+   * @return {string}
+   */
+  sanitize: (str) => {
+    // const isViewer = opts.data.root.reportContext === 'viewer';
+
+    const renderer = new marked.Renderer();
+    renderer.em = str => `<em>${str}</em>`;
+    renderer.link = (href, title, text) => {
+      const titleAttr = title ? `title="${title}"` : '';
+      return `<a href="${href}" target="_blank" rel="noopener" ${titleAttr}>${text}</a>`;
+    };
+    renderer.codespan = function(str) {
+      return `<code>${str}</code>`;
+    };
+    // eslint-disable-next-line no-unused-vars
+    renderer.code = function(code, language) {
+      return `<pre>${Handlebars.Utils.escapeExpression(code)}</pre>`;
+    };
+    renderer.image = function(src, title, text) {
+      return `<img src="${src}" alt="${text}" title="${title}">`;
+    };
+
+    // Nuke wrapper <p> tag that gets generated.
+    renderer.paragraph = function(str) {
+      return str;
+    };
+
+    try {
+      str = marked(str, {renderer, sanitize: true});
+    } catch (e) {
+      // Ignore fatal errors from marked js.
+    }
+
+    // The input str has been sanitized and transformed. Mark it as safe so
+    // handlebars renders the text as HTML.
+    return new Handlebars.SafeString(str);
+  },
+
+  /**
+   * @param {(boolean|number)} value
+   * @return {boolean}
+   */
+  shouldShowHelpText: value => (getItemRating(value) !== RATINGS.GOOD.label)
 };
 
+module.exports = handlebarHelpers;
 
-renderer.paragraph=function(str){
-return str;
-};
+},{"../lib/url-shim":25,"handlebars/runtime":293,"marked":298}],29:[function(require,module,exports){
+this["report"] = this["report"] || {};
+this["report"]["partials"] = this["report"]["partials"] || {};
+this["report"]["partials"]["accessibility"] = {"1":function(container,depth0,helpers,partials,data) {
+    var stack1;
 
-try{
-str=marked(str,{renderer,sanitize:true});
-}catch(e){
+  return container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.nodes : depth0)) != null ? stack1.length : stack1), depth0))
+    + " elements fail this test";
+},"3":function(container,depth0,helpers,partials,data) {
+    var stack1;
 
-}
+  return container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.nodes : depth0)) != null ? stack1.length : stack1), depth0))
+    + " element failed this test";
+},"5":function(container,depth0,helpers,partials,data) {
+    return "    <li class=\"subitem__detail\"><code>"
+    + container.escapeExpression(container.lambda((depth0 != null ? depth0.target : depth0), depth0))
+    + "</code></li>\n";
+},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
+    var stack1, alias1=depth0 != null ? depth0 : {};
 
-
-
-return new Handlebars.SafeString(str);
-},
-
-
-
-
-
-shouldShowHelpText:value=>getItemRating(value)!==RATINGS.GOOD.label};
-
-
-module.exports=handlebarHelpers;
-
-},{"../lib/url-shim":25,"handlebars/runtime":264,"marked":269}],29:[function(require,module,exports){
-this["report"]=this["report"]||{};
-this["report"]["partials"]=this["report"]["partials"]||{};
-this["report"]["partials"]["accessibility"]={"1":function(container,depth0,helpers,partials,data){
-var stack1;
-
-return container.escapeExpression(container.lambda((stack1=depth0!=null?depth0.nodes:depth0)!=null?stack1.length:stack1,depth0))+
-" elements fail this test";
-},"3":function(container,depth0,helpers,partials,data){
-var stack1;
-
-return container.escapeExpression(container.lambda((stack1=depth0!=null?depth0.nodes:depth0)!=null?stack1.length:stack1,depth0))+
-" element failed this test";
-},"5":function(container,depth0,helpers,partials,data){
-return"    <li class=\"subitem__detail\"><code>"+
-container.escapeExpression(container.lambda(depth0!=null?depth0.target:depth0,depth0))+
-"</code></li>\n";
-},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data){
-var stack1,alias1=depth0!=null?depth0:{};
-
-return"<details class=\"subitem__details\">\n  <summary class=\"subitem__detail\">"+(
-(stack1=helpers["if"].call(alias1,(helpers.gt||depth0&&depth0.gt||helpers.helperMissing).call(alias1,(stack1=depth0!=null?depth0.nodes:depth0)!=null?stack1.length:stack1,1,{"name":"gt","hash":{},"data":data}),{"name":"if","hash":{},"fn":container.program(1,data,0),"inverse":container.program(3,data,0),"data":data}))!=null?stack1:"")+
-"</summary>\n  <ul class=\"subitem__details\">\n"+(
-(stack1=helpers.each.call(alias1,depth0!=null?depth0.nodes:depth0,{"name":"each","hash":{},"fn":container.program(5,data,0),"inverse":container.noop,"data":data}))!=null?stack1:"")+
-"  </ul>\n</details>\n";
+  return "<details class=\"subitem__details\">\n  <summary class=\"subitem__detail\">"
+    + ((stack1 = helpers["if"].call(alias1,(helpers.gt || (depth0 && depth0.gt) || helpers.helperMissing).call(alias1,((stack1 = (depth0 != null ? depth0.nodes : depth0)) != null ? stack1.length : stack1),1,{"name":"gt","hash":{},"data":data}),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.program(3, data, 0),"data":data})) != null ? stack1 : "")
+    + "</summary>\n  <ul class=\"subitem__details\">\n"
+    + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.nodes : depth0),{"name":"each","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + "  </ul>\n</details>\n";
 },"useData":true};
-this["report"]["partials"]["cards"]={"1":function(container,depth0,helpers,partials,data){
-var stack1,helper,alias1=depth0!=null?depth0:{},alias2=helpers.helperMissing,alias3="function",alias4=container.escapeExpression;
+this["report"]["partials"]["cards"] = {"1":function(container,depth0,helpers,partials,data) {
+    var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
 
-return"      <div class=\"subitem__detail scorecard\" "+(
-(stack1=helpers["if"].call(alias1,depth0!=null?depth0.snippet:depth0,{"name":"if","hash":{},"fn":container.program(2,data,0),"inverse":container.noop,"data":data}))!=null?stack1:"")+
-">\n        <div class=\"scorecard-title\">"+
-alias4((helper=(helper=helpers.title||(depth0!=null?depth0.title:depth0))!=null?helper:alias2,typeof helper===alias3?helper.call(alias1,{"name":"title","hash":{},"data":data}):helper))+
-"</div>\n        <div class=\"scorecard-value\">"+
-alias4((helper=(helper=helpers.value||(depth0!=null?depth0.value:depth0))!=null?helper:alias2,typeof helper===alias3?helper.call(alias1,{"name":"value","hash":{},"data":data}):helper))+
-"</div>\n        "+(
-(stack1=helpers["if"].call(alias1,depth0!=null?depth0.target:depth0,{"name":"if","hash":{},"fn":container.program(4,data,0),"inverse":container.noop,"data":data}))!=null?stack1:"")+
-"\n      </div>\n";
-},"2":function(container,depth0,helpers,partials,data){
-var helper;
+  return "      <div class=\"subitem__detail scorecard\" "
+    + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.snippet : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + ">\n        <div class=\"scorecard-title\">"
+    + alias4(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"title","hash":{},"data":data}) : helper)))
+    + "</div>\n        <div class=\"scorecard-value\">"
+    + alias4(((helper = (helper = helpers.value || (depth0 != null ? depth0.value : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"value","hash":{},"data":data}) : helper)))
+    + "</div>\n        "
+    + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.target : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + "\n      </div>\n";
+},"2":function(container,depth0,helpers,partials,data) {
+    var helper;
 
-return"title=\""+
-container.escapeExpression((helper=(helper=helpers.snippet||(depth0!=null?depth0.snippet:depth0))!=null?helper:helpers.helperMissing,typeof helper==="function"?helper.call(depth0!=null?depth0:{},{"name":"snippet","hash":{},"data":data}):helper))+
-"\"";
-},"4":function(container,depth0,helpers,partials,data){
-var helper;
+  return "title=\""
+    + container.escapeExpression(((helper = (helper = helpers.snippet || (depth0 != null ? depth0.snippet : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"snippet","hash":{},"data":data}) : helper)))
+    + "\"";
+},"4":function(container,depth0,helpers,partials,data) {
+    var helper;
 
-return"<div class=\"scorecard-target\">target: "+
-container.escapeExpression((helper=(helper=helpers.target||(depth0!=null?depth0.target:depth0))!=null?helper:helpers.helperMissing,typeof helper==="function"?helper.call(depth0!=null?depth0:{},{"name":"target","hash":{},"data":data}):helper))+
-"</div>";
-},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data){
-var stack1;
+  return "<div class=\"scorecard-target\">target: "
+    + container.escapeExpression(((helper = (helper = helpers.target || (depth0 != null ? depth0.target : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"target","hash":{},"data":data}) : helper)))
+    + "</div>";
+},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
+    var stack1;
 
-return"<details class=\"subitem__details\">\n  <summary class=\"subitem__detail\">View details</summary>\n  <div class=\"cards__container\">\n"+(
-(stack1=helpers.each.call(depth0!=null?depth0:{},depth0,{"name":"each","hash":{},"fn":container.program(1,data,0),"inverse":container.noop,"data":data}))!=null?stack1:"")+
-"  </div>\n</details>\n";
+  return "<details class=\"subitem__details\">\n  <summary class=\"subitem__detail\">View details</summary>\n  <div class=\"cards__container\">\n"
+    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},depth0,{"name":"each","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + "  </div>\n</details>\n";
 },"useData":true};
-this["report"]["partials"]["critical-request-chains"]={"1":function(container,depth0,helpers,partials,data,blockParams,depths){
-var stack1,helper,options,alias1=depth0!=null?depth0:{},alias2=helpers.helperMissing,alias3="function",alias4=helpers.blockHelperMissing,buffer=
-"  <div class=\"cnc-node\" title=\""+
-container.escapeExpression(container.lambda((stack1=(stack1=depth0!=null?depth0.node:depth0)!=null?stack1.request:stack1)!=null?stack1.url:stack1,depth0))+
-"\">\n    <span class=\"cnc-node__tree-marker\">\n"+(
-(stack1=helpers.each.call(alias1,depth0!=null?depth0.treeMarkers:depth0,{"name":"each","hash":{},"fn":container.program(2,data,1,blockParams,depths),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"");
-stack1=(helper=(helper=helpers.isLastChild||(depth0!=null?depth0.isLastChild:depth0))!=null?helper:alias2,options={"name":"isLastChild","hash":{},"fn":container.program(7,data,0,blockParams,depths),"inverse":container.program(9,data,0,blockParams,depths),"data":data,"blockParams":blockParams},typeof helper===alias3?helper.call(alias1,options):helper);
-if(!helpers.isLastChild){stack1=alias4.call(depth0,stack1,options);}
-if(stack1!=null){buffer+=stack1;}
-buffer+="\n";
-stack1=(helper=(helper=helpers.hasChildren||(depth0!=null?depth0.hasChildren:depth0))!=null?helper:alias2,options={"name":"hasChildren","hash":{},"fn":container.program(11,data,0,blockParams,depths),"inverse":container.program(13,data,0,blockParams,depths),"data":data,"blockParams":blockParams},typeof helper===alias3?helper.call(alias1,options):helper);
-if(!helpers.hasChildren){stack1=alias4.call(depth0,stack1,options);}
-if(stack1!=null){buffer+=stack1;}
-return buffer+"    </span>\n    <span class=\"cnc-node__tree-value\">\n"+(
-(stack1=(helpers.parseURL||depth0&&depth0.parseURL||alias2).call(alias1,(stack1=(stack1=depth0!=null?depth0.node:depth0)!=null?stack1.request:stack1)!=null?stack1.url:stack1,{"name":"parseURL","hash":{},"fn":container.program(15,data,0,blockParams,depths),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+(
-(stack1=helpers.unless.call(alias1,depth0!=null?depth0.hasChildren:depth0,{"name":"unless","hash":{},"fn":container.program(17,data,0,blockParams,depths),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"    </span>\n  </div>\n\n"+(
-(stack1=helpers.each.call(alias1,(stack1=depth0!=null?depth0.node:depth0)!=null?stack1.children:stack1,{"name":"each","hash":{},"fn":container.program(19,data,1,blockParams,depths),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"");
-},"2":function(container,depth0,helpers,partials,data,blockParams){
-var stack1;
+this["report"]["partials"]["critical-request-chains"] = {"1":function(container,depth0,helpers,partials,data,blockParams,depths) {
+    var stack1, helper, options, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=helpers.blockHelperMissing, buffer = 
+  "  <div class=\"cnc-node\" title=\""
+    + container.escapeExpression(container.lambda(((stack1 = ((stack1 = (depth0 != null ? depth0.node : depth0)) != null ? stack1.request : stack1)) != null ? stack1.url : stack1), depth0))
+    + "\">\n    <span class=\"cnc-node__tree-marker\">\n"
+    + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.treeMarkers : depth0),{"name":"each","hash":{},"fn":container.program(2, data, 1, blockParams, depths),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "");
+  stack1 = ((helper = (helper = helpers.isLastChild || (depth0 != null ? depth0.isLastChild : depth0)) != null ? helper : alias2),(options={"name":"isLastChild","hash":{},"fn":container.program(7, data, 0, blockParams, depths),"inverse":container.program(9, data, 0, blockParams, depths),"data":data,"blockParams":blockParams}),(typeof helper === alias3 ? helper.call(alias1,options) : helper));
+  if (!helpers.isLastChild) { stack1 = alias4.call(depth0,stack1,options)}
+  if (stack1 != null) { buffer += stack1; }
+  buffer += "\n";
+  stack1 = ((helper = (helper = helpers.hasChildren || (depth0 != null ? depth0.hasChildren : depth0)) != null ? helper : alias2),(options={"name":"hasChildren","hash":{},"fn":container.program(11, data, 0, blockParams, depths),"inverse":container.program(13, data, 0, blockParams, depths),"data":data,"blockParams":blockParams}),(typeof helper === alias3 ? helper.call(alias1,options) : helper));
+  if (!helpers.hasChildren) { stack1 = alias4.call(depth0,stack1,options)}
+  if (stack1 != null) { buffer += stack1; }
+  return buffer + "    </span>\n    <span class=\"cnc-node__tree-value\">\n"
+    + ((stack1 = (helpers.parseURL || (depth0 && depth0.parseURL) || alias2).call(alias1,((stack1 = ((stack1 = (depth0 != null ? depth0.node : depth0)) != null ? stack1.request : stack1)) != null ? stack1.url : stack1),{"name":"parseURL","hash":{},"fn":container.program(15, data, 0, blockParams, depths),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.hasChildren : depth0),{"name":"unless","hash":{},"fn":container.program(17, data, 0, blockParams, depths),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "    </span>\n  </div>\n\n"
+    + ((stack1 = helpers.each.call(alias1,((stack1 = (depth0 != null ? depth0.node : depth0)) != null ? stack1.children : stack1),{"name":"each","hash":{},"fn":container.program(19, data, 1, blockParams, depths),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "");
+},"2":function(container,depth0,helpers,partials,data,blockParams) {
+    var stack1;
 
-return(stack1=helpers.blockHelperMissing.call(depth0,container.lambda(blockParams[0][0],depth0),{"name":"separator","hash":{},"fn":container.program(3,data,0,blockParams),"inverse":container.program(5,data,0,blockParams),"data":data,"blockParams":blockParams}))!=null?stack1:"";
-},"3":function(container,depth0,helpers,partials,data){
-return"      <span class=\"tree-marker vert\"></span>\n      <span class=\"tree-marker space\"></span>\n";
-},"5":function(container,depth0,helpers,partials,data){
-return"      <span class=\"tree-marker space\"></span>\n      <span class=\"tree-marker space\"></span>\n";
-},"7":function(container,depth0,helpers,partials,data){
-return"      <span class=\"tree-marker up-right\"></span>\n      <span class=\"tree-marker right\"></span>\n";
-},"9":function(container,depth0,helpers,partials,data){
-return"      <span class=\"tree-marker vert-right\"></span>\n      <span class=\"tree-marker right\"></span>\n";
-},"11":function(container,depth0,helpers,partials,data){
-return"      <span class=\"tree-marker horiz-down\"></span>\n";
-},"13":function(container,depth0,helpers,partials,data){
-return"      <span class=\"tree-marker right\"></span>\n";
-},"15":function(container,depth0,helpers,partials,data){
-var alias1=container.lambda,alias2=container.escapeExpression;
+  return ((stack1 = helpers.blockHelperMissing.call(depth0,container.lambda(blockParams[0][0], depth0),{"name":"separator","hash":{},"fn":container.program(3, data, 0, blockParams),"inverse":container.program(5, data, 0, blockParams),"data":data,"blockParams":blockParams})) != null ? stack1 : "");
+},"3":function(container,depth0,helpers,partials,data) {
+    return "      <span class=\"tree-marker vert\"></span>\n      <span class=\"tree-marker space\"></span>\n";
+},"5":function(container,depth0,helpers,partials,data) {
+    return "      <span class=\"tree-marker space\"></span>\n      <span class=\"tree-marker space\"></span>\n";
+},"7":function(container,depth0,helpers,partials,data) {
+    return "      <span class=\"tree-marker up-right\"></span>\n      <span class=\"tree-marker right\"></span>\n";
+},"9":function(container,depth0,helpers,partials,data) {
+    return "      <span class=\"tree-marker vert-right\"></span>\n      <span class=\"tree-marker right\"></span>\n";
+},"11":function(container,depth0,helpers,partials,data) {
+    return "      <span class=\"tree-marker horiz-down\"></span>\n";
+},"13":function(container,depth0,helpers,partials,data) {
+    return "      <span class=\"tree-marker right\"></span>\n";
+},"15":function(container,depth0,helpers,partials,data) {
+    var alias1=container.lambda, alias2=container.escapeExpression;
 
-return"        <span class=\"cnc-node__tree-file\">"+
-alias2(alias1(depth0!=null?depth0.file:depth0,depth0))+
-"</span>\n        <span class=\"cnc-node__tree-hostname\">("+
-alias2(alias1(depth0!=null?depth0.hostname:depth0,depth0))+
-")</span>\n";
-},"17":function(container,depth0,helpers,partials,data){
-var stack1,alias1=depth0!=null?depth0:{},alias2=helpers.helperMissing,alias3=container.escapeExpression;
+  return "        <span class=\"cnc-node__tree-file\">"
+    + alias2(alias1((depth0 != null ? depth0.file : depth0), depth0))
+    + "</span>\n        <span class=\"cnc-node__tree-hostname\">("
+    + alias2(alias1((depth0 != null ? depth0.hostname : depth0), depth0))
+    + ")</span>\n";
+},"17":function(container,depth0,helpers,partials,data) {
+    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3=container.escapeExpression;
 
-return"        - <span class=\"cnc-node__chain-duration\">"+
-alias3((helpers.chainDuration||depth0&&depth0.chainDuration||alias2).call(alias1,depth0!=null?depth0.startTime:depth0,(stack1=(stack1=depth0!=null?depth0.node:depth0)!=null?stack1.request:stack1)!=null?stack1.endTime:stack1,{"name":"chainDuration","hash":{},"data":data}))+
-"ms</span>,\n          <span class=\"cnc-node__chain-duration\">"+
-alias3((helpers.formatTransferSize||depth0&&depth0.formatTransferSize||alias2).call(alias1,depth0!=null?depth0.transferSize:depth0,{"name":"formatTransferSize","hash":{},"data":data}))+
-"KB</span>\n";
-},"19":function(container,depth0,helpers,partials,data,blockParams,depths){
-var stack1;
+  return "        - <span class=\"cnc-node__chain-duration\">"
+    + alias3((helpers.chainDuration || (depth0 && depth0.chainDuration) || alias2).call(alias1,(depth0 != null ? depth0.startTime : depth0),((stack1 = ((stack1 = (depth0 != null ? depth0.node : depth0)) != null ? stack1.request : stack1)) != null ? stack1.endTime : stack1),{"name":"chainDuration","hash":{},"data":data}))
+    + "ms</span>,\n          <span class=\"cnc-node__chain-duration\">"
+    + alias3((helpers.formatTransferSize || (depth0 && depth0.formatTransferSize) || alias2).call(alias1,(depth0 != null ? depth0.transferSize : depth0),{"name":"formatTransferSize","hash":{},"data":data}))
+    + "KB</span>\n";
+},"19":function(container,depth0,helpers,partials,data,blockParams,depths) {
+    var stack1;
 
-return(stack1=(helpers.createContextFor||depth0&&depth0.createContextFor||helpers.helperMissing).call(depth0!=null?depth0:{},(stack1=depths[1]!=null?depths[1].node:depths[1])!=null?stack1.children:stack1,data&&data.key,depths[1]!=null?depths[1].treeMarkers:depths[1],depths[1]!=null?depths[1].isLastChild:depths[1],depths[1]!=null?depths[1].startTime:depths[1],depths[1]!=null?depths[1].transferSize:depths[1],{"name":"createContextFor","hash":{},"fn":container.program(20,data,0,blockParams,depths),"inverse":container.noop,"data":data}))!=null?stack1:"";
-},"20":function(container,depth0,helpers,partials,data){
-var stack1;
+  return ((stack1 = (helpers.createContextFor || (depth0 && depth0.createContextFor) || helpers.helperMissing).call(depth0 != null ? depth0 : {},((stack1 = (depths[1] != null ? depths[1].node : depths[1])) != null ? stack1.children : stack1),(data && data.key),(depths[1] != null ? depths[1].treeMarkers : depths[1]),(depths[1] != null ? depths[1].isLastChild : depths[1]),(depths[1] != null ? depths[1].startTime : depths[1]),(depths[1] != null ? depths[1].transferSize : depths[1]),{"name":"createContextFor","hash":{},"fn":container.program(20, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+},"20":function(container,depth0,helpers,partials,data) {
+    var stack1;
 
-return(stack1=container.invokePartial(partials.writeNode,depth0,{"name":"writeNode","data":data,"indent":"      ","helpers":helpers,"partials":partials,"decorators":container.decorators}))!=null?stack1:"";
-},"22":function(container,depth0,helpers,partials,data,blockParams,depths){
-var stack1;
+  return ((stack1 = container.invokePartial(partials.writeNode,depth0,{"name":"writeNode","data":data,"indent":"      ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
+},"22":function(container,depth0,helpers,partials,data,blockParams,depths) {
+    var stack1;
 
-return(stack1=helpers.each.call(depth0!=null?depth0:{},depth0!=null?depth0.tree:depth0,{"name":"each","hash":{},"fn":container.program(23,data,0,blockParams,depths),"inverse":container.noop,"data":data}))!=null?stack1:"";
-},"23":function(container,depth0,helpers,partials,data,blockParams,depths){
-var stack1;
+  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.tree : depth0),{"name":"each","hash":{},"fn":container.program(23, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+},"23":function(container,depth0,helpers,partials,data,blockParams,depths) {
+    var stack1;
 
-return(stack1=(helpers.createContextFor||depth0&&depth0.createContextFor||helpers.helperMissing).call(depth0!=null?depth0:{},depths[1]!=null?depths[1].tree:depths[1],data&&data.key,undefined,undefined,depths[1]!=null?depths[1].startTime:depths[1],depths[1]!=null?depths[1].transferSize:depths[1],{"name":"createContextFor","hash":{},"fn":container.program(24,data,0,blockParams,depths),"inverse":container.noop,"data":data}))!=null?stack1:"";
-},"24":function(container,depth0,helpers,partials,data){
-var stack1;
+  return ((stack1 = (helpers.createContextFor || (depth0 && depth0.createContextFor) || helpers.helperMissing).call(depth0 != null ? depth0 : {},(depths[1] != null ? depths[1].tree : depths[1]),(data && data.key),undefined,undefined,(depths[1] != null ? depths[1].startTime : depths[1]),(depths[1] != null ? depths[1].transferSize : depths[1]),{"name":"createContextFor","hash":{},"fn":container.program(24, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+},"24":function(container,depth0,helpers,partials,data) {
+    var stack1;
 
-return(stack1=container.invokePartial(partials.writeNode,depth0,{"name":"writeNode","data":data,"indent":"            ","helpers":helpers,"partials":partials,"decorators":container.decorators}))!=null?stack1:"";
-},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths){
-var stack1,alias1=depth0!=null?depth0:{},alias2=helpers.helperMissing,alias3=container.escapeExpression;
+  return ((stack1 = container.invokePartial(partials.writeNode,depth0,{"name":"writeNode","data":data,"indent":"            ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
+},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {
+    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3=container.escapeExpression;
 
-return"\n<ul class=\"subitem__details\">\n  <li class=\"subitem__detail\">Longest chain: <strong>"+
-alias3((helpers.formatNumber||depth0&&depth0.formatNumber||alias2).call(alias1,(stack1=depth0!=null?depth0.longestChain:depth0)!=null?stack1.duration:stack1,{"name":"formatNumber","hash":{},"data":data,"blockParams":blockParams}))+
-"ms</strong>\n   over <strong>"+
-alias3(container.lambda((stack1=depth0!=null?depth0.longestChain:depth0)!=null?stack1.length:stack1,depth0))+
-"</strong> requests, totalling <strong>"+
-alias3((helpers.formatTransferSize||depth0&&depth0.formatTransferSize||alias2).call(alias1,(stack1=depth0!=null?depth0.longestChain:depth0)!=null?stack1.transferSize:stack1,{"name":"formatTransferSize","hash":{},"data":data,"blockParams":blockParams}))+
-"KB</strong></li>\n  <li class=\"subitem__detail\">\n    <details>\n      <summary>View critical network waterfall</summary>\n      <div class=\"initial-nav\">Initial navigation</div>\n"+(
-(stack1=(helpers.createTreeRenderContext||depth0&&depth0.createTreeRenderContext||alias2).call(alias1,depth0!=null?depth0.chains:depth0,{"name":"createTreeRenderContext","hash":{},"fn":container.program(22,data,0,blockParams,depths),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"    </details>\n  </li>\n</ul>\n";
-},"main_d":function(fn,props,container,depth0,data,blockParams,depths){
+  return "\n<ul class=\"subitem__details\">\n  <li class=\"subitem__detail\">Longest chain: <strong>"
+    + alias3((helpers.formatNumber || (depth0 && depth0.formatNumber) || alias2).call(alias1,((stack1 = (depth0 != null ? depth0.longestChain : depth0)) != null ? stack1.duration : stack1),{"name":"formatNumber","hash":{},"data":data,"blockParams":blockParams}))
+    + "ms</strong>\n   over <strong>"
+    + alias3(container.lambda(((stack1 = (depth0 != null ? depth0.longestChain : depth0)) != null ? stack1.length : stack1), depth0))
+    + "</strong> requests, totalling <strong>"
+    + alias3((helpers.formatTransferSize || (depth0 && depth0.formatTransferSize) || alias2).call(alias1,((stack1 = (depth0 != null ? depth0.longestChain : depth0)) != null ? stack1.transferSize : stack1),{"name":"formatTransferSize","hash":{},"data":data,"blockParams":blockParams}))
+    + "KB</strong></li>\n  <li class=\"subitem__detail\">\n    <details>\n      <summary>View critical network waterfall</summary>\n      <div class=\"initial-nav\">Initial navigation</div>\n"
+    + ((stack1 = (helpers.createTreeRenderContext || (depth0 && depth0.createTreeRenderContext) || alias2).call(alias1,(depth0 != null ? depth0.chains : depth0),{"name":"createTreeRenderContext","hash":{},"fn":container.program(22, data, 0, blockParams, depths),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "    </details>\n  </li>\n</ul>\n";
+},"main_d":  function(fn, props, container, depth0, data, blockParams, depths) {
 
-var decorators=container.decorators;
+  var decorators = container.decorators;
 
-fn=decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(1,data,0,blockParams,depths),"inverse":container.noop,"args":["writeNode"],"data":data,"blockParams":blockParams})||fn;
-return fn;
-},
+  fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.noop,"args":["writeNode"],"data":data,"blockParams":blockParams}) || fn;
+  return fn;
+  }
 
-"useDecorators":true,"usePartial":true,"useData":true,"useDepths":true,"useBlockParams":true};
-this["report"]["partials"]["null"]={"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data){
-return"";
+,"useDecorators":true,"usePartial":true,"useData":true,"useDepths":true,"useBlockParams":true};
+this["report"]["partials"]["null"] = {"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
+    return "";
 },"useData":true};
-this["report"]["partials"]["speedline"]={"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data){
-var stack1,alias1=container.lambda,alias2=container.escapeExpression;
+this["report"]["partials"]["speedline"] = {"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
+    var stack1, alias1=container.lambda, alias2=container.escapeExpression;
 
-return"<ul class=\"subitem__details\">\n  <li class=\"subitem__detail\">First Visual Change: <strong>"+
-alias2(alias1((stack1=depth0!=null?depth0.timings:depth0)!=null?stack1.firstVisualChange:stack1,depth0))+
-"ms</strong></li>\n  <li class=\"subitem__detail\">Last Visual Change: <strong>"+
-alias2(alias1((stack1=depth0!=null?depth0.timings:depth0)!=null?stack1.visuallyComplete:stack1,depth0))+
-"ms</strong></li>\n</ul>\n";
+  return "<ul class=\"subitem__details\">\n  <li class=\"subitem__detail\">First Visual Change: <strong>"
+    + alias2(alias1(((stack1 = (depth0 != null ? depth0.timings : depth0)) != null ? stack1.firstVisualChange : stack1), depth0))
+    + "ms</strong></li>\n  <li class=\"subitem__detail\">Last Visual Change: <strong>"
+    + alias2(alias1(((stack1 = (depth0 != null ? depth0.timings : depth0)) != null ? stack1.visuallyComplete : stack1), depth0))
+    + "ms</strong></li>\n</ul>\n";
 },"useData":true};
-this["report"]["partials"]["table"]={"1":function(container,depth0,helpers,partials,data,blockParams,depths){
-var stack1;
+this["report"]["partials"]["table"] = {"1":function(container,depth0,helpers,partials,data,blockParams,depths) {
+    var stack1;
 
-return"\n"+(
-(stack1=helpers["if"].call(depth0!=null?depth0:{},depth0!=null?depth0.rows:depth0,{"name":"if","hash":{},"fn":container.program(2,data,0,blockParams,depths),"inverse":container.noop,"data":data}))!=null?stack1:"")+
-"\n";
-},"2":function(container,depth0,helpers,partials,data,blockParams,depths){
-var stack1,alias1=depth0!=null?depth0:{};
+  return "\n"
+    + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.rows : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + "\n";
+},"2":function(container,depth0,helpers,partials,data,blockParams,depths) {
+    var stack1, alias1=depth0 != null ? depth0 : {};
 
-return"<details class=\"subitem__details\">\n  <summary class=\"subitem__detail\">View details</summary>\n  <table class=\"table_list "+(
-(stack1=(helpers.ifNotEq||depth0&&depth0.ifNotEq||helpers.helperMissing).call(alias1,(stack1=depth0!=null?depth0.headings:depth0)!=null?stack1.length:stack1,2,{"name":"ifNotEq","hash":{},"fn":container.program(3,data,0,blockParams,depths),"inverse":container.noop,"data":data}))!=null?stack1:"")+
-"\">\n    <thead>\n      <tr>\n"+(
-(stack1=helpers.each.call(alias1,depths[1]!=null?depths[1].tableHeadings:depths[1],{"name":"each","hash":{},"fn":container.program(5,data,0,blockParams,depths),"inverse":container.noop,"data":data}))!=null?stack1:"")+
-"      </tr>\n    </thead>\n    <tbody>\n"+(
-(stack1=helpers.each.call(alias1,depth0!=null?depth0.rows:depth0,{"name":"each","hash":{},"fn":container.program(7,data,0,blockParams,depths),"inverse":container.noop,"data":data}))!=null?stack1:"")+
-"    </tbody>\n  </table>\n</details>\n";
-},"3":function(container,depth0,helpers,partials,data){
-return"multicolumn";
-},"5":function(container,depth0,helpers,partials,data){
-var alias1=container.escapeExpression;
+  return "<details class=\"subitem__details\">\n  <summary class=\"subitem__detail\">View details</summary>\n  <table class=\"table_list "
+    + ((stack1 = (helpers.ifNotEq || (depth0 && depth0.ifNotEq) || helpers.helperMissing).call(alias1,((stack1 = (depth0 != null ? depth0.headings : depth0)) != null ? stack1.length : stack1),2,{"name":"ifNotEq","hash":{},"fn":container.program(3, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + "\">\n    <thead>\n      <tr>\n"
+    + ((stack1 = helpers.each.call(alias1,(depths[1] != null ? depths[1].tableHeadings : depths[1]),{"name":"each","hash":{},"fn":container.program(5, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + "      </tr>\n    </thead>\n    <tbody>\n"
+    + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.rows : depth0),{"name":"each","hash":{},"fn":container.program(7, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + "    </tbody>\n  </table>\n</details>\n";
+},"3":function(container,depth0,helpers,partials,data) {
+    return "multicolumn";
+},"5":function(container,depth0,helpers,partials,data) {
+    var alias1=container.escapeExpression;
 
-return"          <th class=\"table-column table-column-"+
-alias1((helpers.kebabCase||depth0&&depth0.kebabCase||helpers.helperMissing).call(depth0!=null?depth0:{},data&&data.key,{"name":"kebabCase","hash":{},"data":data}))+
-"\">"+
-alias1(container.lambda(depth0,depth0))+
-"</th>\n";
-},"7":function(container,depth0,helpers,partials,data,blockParams,depths){
-var stack1;
+  return "          <th class=\"table-column table-column-"
+    + alias1((helpers.kebabCase || (depth0 && depth0.kebabCase) || helpers.helperMissing).call(depth0 != null ? depth0 : {},(data && data.key),{"name":"kebabCase","hash":{},"data":data}))
+    + "\">"
+    + alias1(container.lambda(depth0, depth0))
+    + "</th>\n";
+},"7":function(container,depth0,helpers,partials,data,blockParams,depths) {
+    var stack1;
 
-return"        <tr>\n"+(
-(stack1=helpers.each.call(depth0!=null?depth0:{},depth0!=null?depth0.cols:depth0,{"name":"each","hash":{},"fn":container.program(8,data,0,blockParams,depths),"inverse":container.noop,"data":data}))!=null?stack1:"")+
-"        </tr>\n";
-},"8":function(container,depth0,helpers,partials,data,blockParams,depths){
-var alias1=depth0!=null?depth0:{},alias2=helpers.helperMissing,alias3=container.escapeExpression;
+  return "        <tr>\n"
+    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.cols : depth0),{"name":"each","hash":{},"fn":container.program(8, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + "        </tr>\n";
+},"8":function(container,depth0,helpers,partials,data,blockParams,depths) {
+    var alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3=container.escapeExpression;
 
-return"            <td class=\"table-column table-column-"+
-alias3((helpers.kebabCase||depth0&&depth0.kebabCase||alias2).call(alias1,helpers.lookup.call(alias1,depths[2]!=null?depths[2].headingKeys:depths[2],data&&data.index,{"name":"lookup","hash":{},"data":data}),{"name":"kebabCase","hash":{},"data":data}))+
-"\">"+
-alias3((helpers.sanitize||depth0&&depth0.sanitize||alias2).call(alias1,depth0,{"name":"sanitize","hash":{},"data":data}))+
-"</td>\n";
-},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths){
-var stack1;
+  return "            <td class=\"table-column table-column-"
+    + alias3((helpers.kebabCase || (depth0 && depth0.kebabCase) || alias2).call(alias1,helpers.lookup.call(alias1,(depths[2] != null ? depths[2].headingKeys : depths[2]),(data && data.index),{"name":"lookup","hash":{},"data":data}),{"name":"kebabCase","hash":{},"data":data}))
+    + "\">"
+    + alias3((helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,depth0,{"name":"sanitize","hash":{},"data":data}))
+    + "</td>\n";
+},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {
+    var stack1;
 
-return(stack1=(helpers.createTable||depth0&&depth0.createTable||helpers.helperMissing).call(depth0!=null?depth0:{},depth0!=null?depth0.tableHeadings:depth0,depth0!=null?depth0.results:depth0,{"name":"createTable","hash":{},"fn":container.program(1,data,0,blockParams,depths),"inverse":container.noop,"data":data}))!=null?stack1:"";
+  return ((stack1 = (helpers.createTable || (depth0 && depth0.createTable) || helpers.helperMissing).call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.tableHeadings : depth0),(depth0 != null ? depth0.results : depth0),{"name":"createTable","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
 },"useData":true,"useDepths":true};
-this["report"]["partials"]["url-list"]={"1":function(container,depth0,helpers,partials,data){
-var stack1,alias1=depth0!=null?depth0:{};
+this["report"]["partials"]["url-list"] = {"1":function(container,depth0,helpers,partials,data) {
+    var stack1, alias1=depth0 != null ? depth0 : {};
 
-return"    <li class=\"subitem__detail http-resource\">\n      <span class=\"http-resource__url\">"+
-container.escapeExpression(container.lambda(depth0!=null?depth0.url:depth0,depth0))+
-"</span>\n"+(
-(stack1=helpers["if"].call(alias1,depth0!=null?depth0.label:depth0,{"name":"if","hash":{},"fn":container.program(2,data,0),"inverse":container.noop,"data":data}))!=null?stack1:"")+(
-(stack1=helpers["if"].call(alias1,depth0!=null?depth0.code:depth0,{"name":"if","hash":{},"fn":container.program(4,data,0),"inverse":container.noop,"data":data}))!=null?stack1:"")+
-"    </li>\n";
-},"2":function(container,depth0,helpers,partials,data){
-return"        <span class=\"http-resource__protocol\">("+
-container.escapeExpression(container.lambda(depth0!=null?depth0.label:depth0,depth0))+
-")</span>\n";
-},"4":function(container,depth0,helpers,partials,data){
-return"        <pre class=\"http-resource__code\">"+
-container.escapeExpression(container.lambda(depth0!=null?depth0.code:depth0,depth0))+
-"</pre>\n";
-},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data){
-var stack1;
+  return "    <li class=\"subitem__detail http-resource\">\n      <span class=\"http-resource__url\">"
+    + container.escapeExpression(container.lambda((depth0 != null ? depth0.url : depth0), depth0))
+    + "</span>\n"
+    + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.label : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.code : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + "    </li>\n";
+},"2":function(container,depth0,helpers,partials,data) {
+    return "        <span class=\"http-resource__protocol\">("
+    + container.escapeExpression(container.lambda((depth0 != null ? depth0.label : depth0), depth0))
+    + ")</span>\n";
+},"4":function(container,depth0,helpers,partials,data) {
+    return "        <pre class=\"http-resource__code\">"
+    + container.escapeExpression(container.lambda((depth0 != null ? depth0.code : depth0), depth0))
+    + "</pre>\n";
+},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
+    var stack1;
 
-return"<details class=\"subitem__details\">\n  <summary class=\"subitem__detail\">URLs</summary>\n  <ul class=\"subitem__details\">\n"+(
-(stack1=helpers.each.call(depth0!=null?depth0:{},depth0,{"name":"each","hash":{},"fn":container.program(1,data,0),"inverse":container.noop,"data":data}))!=null?stack1:"")+
-"  </ul>\n</details>\n";
+  return "<details class=\"subitem__details\">\n  <summary class=\"subitem__detail\">URLs</summary>\n  <ul class=\"subitem__details\">\n"
+    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},depth0,{"name":"each","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + "  </ul>\n</details>\n";
 },"useData":true};
-this["report"]["partials"]["user-timings"]={"1":function(container,depth0,helpers,partials,data){
-var stack1;
+this["report"]["partials"]["user-timings"] = {"1":function(container,depth0,helpers,partials,data) {
+    var stack1;
 
-return"      <li>\n"+(
-(stack1=helpers["if"].call(depth0!=null?depth0:{},depth0!=null?depth0.isMark:depth0,{"name":"if","hash":{},"fn":container.program(2,data,0),"inverse":container.program(4,data,0),"data":data}))!=null?stack1:"")+
-"      </li>\n";
-},"2":function(container,depth0,helpers,partials,data){
-var alias1=container.escapeExpression;
+  return "      <li>\n"
+    + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.isMark : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.program(4, data, 0),"data":data})) != null ? stack1 : "")
+    + "      </li>\n";
+},"2":function(container,depth0,helpers,partials,data) {
+    var alias1=container.escapeExpression;
 
-return"          <strong class=\"ut-measure_listing-duration\">Mark: "+
-alias1((helpers.decimal||depth0&&depth0.decimal||helpers.helperMissing).call(depth0!=null?depth0:{},depth0!=null?depth0.startTime:depth0,{"name":"decimal","hash":{},"data":data}))+
-"ms</strong> - "+
-alias1(container.lambda(depth0!=null?depth0.name:depth0,depth0))+
-"\n";
-},"4":function(container,depth0,helpers,partials,data){
-var alias1=container.escapeExpression;
+  return "          <strong class=\"ut-measure_listing-duration\">Mark: "
+    + alias1((helpers.decimal || (depth0 && depth0.decimal) || helpers.helperMissing).call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.startTime : depth0),{"name":"decimal","hash":{},"data":data}))
+    + "ms</strong> - "
+    + alias1(container.lambda((depth0 != null ? depth0.name : depth0), depth0))
+    + "\n";
+},"4":function(container,depth0,helpers,partials,data) {
+    var alias1=container.escapeExpression;
 
-return"          <strong class=\"ut-measure_listing-duration\">Measure "+
-alias1((helpers.decimal||depth0&&depth0.decimal||helpers.helperMissing).call(depth0!=null?depth0:{},depth0!=null?depth0.duration:depth0,{"name":"decimal","hash":{},"data":data}))+
-"ms</strong> - "+
-alias1(container.lambda(depth0!=null?depth0.name:depth0,depth0))+
-"\n";
-},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data){
-var stack1;
+  return "          <strong class=\"ut-measure_listing-duration\">Measure "
+    + alias1((helpers.decimal || (depth0 && depth0.decimal) || helpers.helperMissing).call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.duration : depth0),{"name":"decimal","hash":{},"data":data}))
+    + "ms</strong> - "
+    + alias1(container.lambda((depth0 != null ? depth0.name : depth0), depth0))
+    + "\n";
+},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
+    var stack1;
 
-return"<details class=\"subitem__details\">\n  <summary class=\"subitem__detail\">View "+
-container.escapeExpression(container.lambda(depth0!=null?depth0.length:depth0,depth0))+
-" entries</summary>\n  <ul class=\"subitem__details\">\n"+(
-(stack1=helpers.each.call(depth0!=null?depth0:{},depth0,{"name":"each","hash":{},"fn":container.program(1,data,0),"inverse":container.noop,"data":data}))!=null?stack1:"")+
-"  </ul>\n</details>\n";
+  return "<details class=\"subitem__details\">\n  <summary class=\"subitem__detail\">View "
+    + container.escapeExpression(container.lambda((depth0 != null ? depth0.length : depth0), depth0))
+    + " entries</summary>\n  <ul class=\"subitem__details\">\n"
+    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},depth0,{"name":"each","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + "  </ul>\n</details>\n";
 },"useData":true};
 },{}],30:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
+/* global Intl */
 
+const Handlebars = require('handlebars/runtime');
+const handlebarHelpers = require('./handlebar-helpers');
+const reportTemplate = require('./templates/report-templates');
+const reportPartials = require('../report/partials/templates/report-partials');
 
-const Handlebars=require('handlebars/runtime');
-const handlebarHelpers=require('./handlebar-helpers');
-const reportTemplate=require('./templates/report-templates');
-const reportPartials=require('../report/partials/templates/report-partials');
+const path = require('path');
 
-const path=require('path');
+class ReportGenerator {
 
-class ReportGenerator{
+  constructor() {
+    Handlebars.registerHelper(handlebarHelpers);
+  }
 
-constructor(){
-Handlebars.registerHelper(handlebarHelpers);
+  /**
+   * Escape closing script tags.
+   * @param {string} jsonStr
+   * @return {string}
+   */
+  _escapeScriptTags(jsonStr) {
+    return jsonStr.replace(/<\/script>/g, '<\\/script>');
+  }
+
+  /**
+   * Gets the CSS for the report.
+   * @return {!Array<string>} an array of CSS
+   */
+  getReportCSS() {
+    // Cannot DRY this up and dynamically create paths because fs.readdirSync
+    // doesn't browserify well with a variable path. See https://github.com/substack/brfs/issues/36.
+    const partialStyles = [
+      ".cards__container {\n  --padding: 16px;\n  display: flex;\n  flex-wrap: wrap;\n}\n.scorecard {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex: 1 1 150px;\n  flex-direction: column;\n  padding: var(--padding);\n  padding-top: calc(33px + var(--padding));\n  border-radius: 3px;\n  margin-right: var(--padding);\n  position: relative;\n  color: var(--secondary-text-color);\n  line-height: inherit;\n  border: 1px solid #ebebeb;\n}\n.scorecard-title {\n  font-size: var(--subitem-font-size);\n  line-height: var(--heading-line-height);\n  background-color: #eee;\n  position: absolute;\n  top: 0;\n  right: 0;\n  left: 0;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  border-bottom: 1px solid #ebebeb;\n  color: #4f4f4f;\n}\n.scorecard-value {\n  font-size: 28px;\n}\n.scorecard-summary,\n.scorecard-target {\n  margin-top: var(--padding);\n}\n.scorecard-target {\n  font-size: 12px;\n  font-style: italic;\n}\n",
+      ".tree-marker {\n  width: 12px;\n  height: 26px;\n  display: block;\n  float: left;\n  background-position: top left;\n}\n\n.horiz-down {\n  background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjE2cHgiIGhlaWdodD0iMjZweCIgdmlld0JveD0iMCAwIDE2IDI2IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjcuMiAoMjgyNzYpIC0gaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoIC0tPgogICAgPHRpdGxlPmhvcml6LWRvd248L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz48L2RlZnM+CiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyBpZD0iaG9yaXotZG93biIgZmlsbD0iI0Q4RDhEOCI+CiAgICAgICAgICAgIDxyZWN0IGlkPSJSZWN0YW5nbGUtMTM4IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg3LjAwMDAwMCwgMTMuMDAwMDAwKSByb3RhdGUoLTI3MC4wMDAwMDApIHRyYW5zbGF0ZSgtNy4wMDAwMDAsIC0xMy4wMDAwMDApICIgeD0iNiIgeT0iNCIgd2lkdGg9IjIiIGhlaWdodD0iMTgiPjwvcmVjdD4KICAgICAgICAgICAgPHJlY3QgaWQ9IlJlY3RhbmdsZS0xMzkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDguMDAwMDAwLCAxOS4wMDAwMDApIHJvdGF0ZSgtMjcwLjAwMDAwMCkgdHJhbnNsYXRlKC04LjAwMDAwMCwgLTE5LjAwMDAwMCkgIiB4PSIxIiB5PSIxOCIgd2lkdGg9IjE0IiBoZWlnaHQ9IjIiPjwvcmVjdD4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==');\n}\n\n.right {\n  background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjE2cHgiIGhlaWdodD0iMjZweCIgdmlld0JveD0iMCAwIDE2IDI2IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjcuMiAoMjgyNzYpIC0gaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoIC0tPgogICAgPHRpdGxlPnJpZ2h0PC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+PC9kZWZzPgogICAgPGcgaWQ9IlBhZ2UtMSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9InJpZ2h0IiBmaWxsPSIjRDhEOEQ4Ij4KICAgICAgICAgICAgPHJlY3QgaWQ9IlJlY3RhbmdsZS0xMzgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDguMDAwMDAwLCAxMy4wMDAwMDApIHJvdGF0ZSgtMjcwLjAwMDAwMCkgdHJhbnNsYXRlKC04LjAwMDAwMCwgLTEzLjAwMDAwMCkgIiB4PSI3IiB5PSI1IiB3aWR0aD0iMiIgaGVpZ2h0PSIxNiI+PC9yZWN0PgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+');\n}\n\n.up-right {\n  background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjE2cHgiIGhlaWdodD0iMjZweCIgdmlld0JveD0iMCAwIDE2IDI2IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjcuMiAoMjgyNzYpIC0gaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoIC0tPgogICAgPHRpdGxlPnVwLXJpZ2h0PC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+PC9kZWZzPgogICAgPGcgaWQ9IlBhZ2UtMSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9InVwLXJpZ2h0IiBmaWxsPSIjRDhEOEQ4Ij4KICAgICAgICAgICAgPHJlY3QgaWQ9IlJlY3RhbmdsZS0xMzgiIHg9IjciIHk9IjAiIHdpZHRoPSIyIiBoZWlnaHQ9IjE0Ij48L3JlY3Q+CiAgICAgICAgICAgIDxyZWN0IGlkPSJSZWN0YW5nbGUtMTM5IiB4PSI5IiB5PSIxMiIgd2lkdGg9IjciIGhlaWdodD0iMiI+PC9yZWN0PgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+');\n}\n\n.vert-right {\n  background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjE2cHgiIGhlaWdodD0iMjZweCIgdmlld0JveD0iMCAwIDE2IDI2IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjcuMiAoMjgyNzYpIC0gaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoIC0tPgogICAgPHRpdGxlPnZlcnQtcmlnaHQ8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz48L2RlZnM+CiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyBpZD0idmVydC1yaWdodCIgZmlsbD0iI0Q4RDhEOCI+CiAgICAgICAgICAgIDxyZWN0IGlkPSJSZWN0YW5nbGUtMTM4IiB4PSI3IiB5PSIwIiB3aWR0aD0iMiIgaGVpZ2h0PSIyNyI+PC9yZWN0PgogICAgICAgICAgICA8cmVjdCBpZD0iUmVjdGFuZ2xlLTEzOSIgeD0iOSIgeT0iMTIiIHdpZHRoPSI3IiBoZWlnaHQ9IjIiPjwvcmVjdD4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==');\n}\n\n.vert {\n  background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjE2cHgiIGhlaWdodD0iMjZweCIgdmlld0JveD0iMCAwIDE2IDI2IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjcuMiAoMjgyNzYpIC0gaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoIC0tPgogICAgPHRpdGxlPnZlcnQ8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz48L2RlZnM+CiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyBpZD0idmVydCIgZmlsbD0iI0Q4RDhEOCI+CiAgICAgICAgICAgIDxyZWN0IGlkPSJSZWN0YW5nbGUtMTM4IiB4PSI3IiB5PSIwIiB3aWR0aD0iMiIgaGVpZ2h0PSIyNiI+PC9yZWN0PgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+');\n}\n\n.space {\n  background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCIgdmlld0JveD0iMCAwIDE2IDE2IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjcuMiAoMjgyNzYpIC0gaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoIC0tPgogICAgPHRpdGxlPmhvcml6LWRvd248L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz48L2RlZnM+CiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyBpZD0iaG9yaXotZG93biI+PC9nPgogICAgPC9nPgo8L3N2Zz4=');\n}\n\n.cnc-tree {\n  font-size: 14px;\n  width: 100%;\n  overflow-x: auto;\n}\n\n.cnc-node {\n  height: 26px;\n  line-height: 26px;\n  white-space: nowrap;\n}\n\n.cnc-node__tree-value {\n  margin-left: 10px;\n}\n\n.cnc-node__chain-duration {\n  font-weight: 700;\n}\n\n.cnc-node__tree-hostname {\n  color: #595959;\n}\n\n.initial-nav {\n  color: #595959;\n  margin: 10px 0 0;\n  font-style: italic;\n}\n",
+      ".table_list {\n  --image-preview: 24px;\n  margin-top: 8px;\n  border: 1px solid #ebebeb;\n  border-spacing: 0;\n  table-layout: fixed;\n}\n.table_list.multicolumn {\n  width: 100%;\n}\n.table_list th,\n.table_list td {\n  overflow: auto;\n}\n.table_list th {\n  background-color: #eee;\n  padding: 12px 10px;\n  line-height: 1.2;\n}\n.table_list td {\n  padding: 10px;\n}\n.table_list th:first-of-type,\n.table_list td:first-of-type {\n  white-space: nowrap;\n}\n.table_list tr:nth-child(even),\n.table_list tr:hover {\n  background-color: #fafafa;\n}\n.table_list code, .table_list pre {\n  white-space: pre;\n  font-family: monospace;\n  display: block;\n  margin: 0;\n  overflow-x: auto;\n}\n.table_list em + code, .table_list em + pre {\n  margin-top: 10px;\n}\n.table_list .table-column {\n  text-align: right;\n}\n.table_list .table-column.table-column-pre, .table_list .table-column.table-column-code {\n  text-align: left;\n}\n.table_list .table-column.table-column-pre pre {\n  max-height: 150px;\n}\n.table_list .table-column.table-column-url {\n  text-align: left;\n  width: 250px;\n  white-space: nowrap;\n}\n.table-column-potential-savings em, .table-column-webp-savings em, .table-column-jpeg-savings em {\n  color: #595959;\n  font-style: normal;\n  padding-left: 10px;\n}\n.table-column-preview img {\n  height: var(--image-preview);\n  width: var(--image-preview);\n  object-fit: contain;\n}\n.table-column-preview {\n  width: calc(var(--image-preview) * 2);\n}\n",
+      ".http-resource__protocol,\n.http-resource__code {\n  color: var(--secondary-text-color);\n}\n.http-resource__code {\n  text-overflow: ellipsis;\n  overflow: hidden;\n  white-space: pre-line;\n}\n",
+      ".ut-measure_listing-duration {\n  font-weight: 700;\n}\n"
+    ];
+
+    return [
+      "/**\n * Copyright 2016 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n* {\n  box-sizing: border-box;\n}\n\nspan, div, p, section, header, h1, h2, li, ul {\n  margin: 0;\n  padding: 0;\n  line-height: inherit;\n}\n\n:root {\n  --text-font-family: \"Roboto\", -apple-system, BlinkMacSystemFont,  \"Segoe UI\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", sans-serif;\n  --text-color: #222;\n  --secondary-text-color: #565656;\n  --accent-color: #3879d9;\n  --poor-color: #df332f;\n  --good-color: #2b882f;\n  --average-color: #ef6c00; /* md orange 800 */\n  --warning-color: #757575; /* md grey 600 */\n  --gutter-gap: 12px;\n  --gutter-width: 40px;\n  --body-font-size: 14px;\n  --body-line-height: 20px;\n  --subitem-font-size: 14px;\n  --subitem-line-height: 20px;\n  --subitem-overall-icon-size: 20px;\n  --subheading-font-size: 16px;\n  --subheading-line-height: 24px;\n  --subheading-color: inherit;\n  --heading-font-size: 24px;\n  --heading-line-height: 32px;\n  --subitem-indent: 16px;\n  --max-line-length: none;\n\n  --report-width: 1280px;\n  --report-menu-width: 280px;\n  --report-header-height: 58px;\n  --report-header-bg-color: #fafafa;\n  --report-border-color: #ebebeb;\n\n  --aggregation-padding: calc(var(--heading-line-height) + var(--gutter-width) + var(--gutter-gap));\n  --aggregation-icon-size: 24px;\n  --audit-icon-size: 19px;\n  --audit-icon-background-size: 17px;\n}\n\n:root[data-report-context=\"devtools\"] {\n  --text-font-family: '.SFNSDisplay-Regular', 'Helvetica Neue', 'Lucida Grande', sans-serif;\n  --body-font-size: 13px;\n  --body-line-height: 17px;\n  --subitem-font-size: 14px;\n  --subitem-line-height: 18px;\n  --subheading-font-size: 16px;\n  --subheading-line-height: 20px;\n  --report-header-height: 0;\n  --heading-font-size: 20px;\n  --heading-line-height: 24px;\n  --max-line-length: calc(60 * var(--body-font-size));\n}\n\nhtml {\n  font-family: var(--text-font-family);\n  font-size: var(--body-font-size);\n  line-height: 1;\n  margin: 0;\n  padding: 0;\n}\n\nhtml, body {\n  height: 100%;\n}\n\n/* When deep linking to a section, bump the heading down so it's not covered by the top nav. */\n:target.aggregations {\n  padding-top: calc(var(--report-header-height) + var(--heading-line-height)) !important;\n}\n\na {\n  color: #0c50c7;\n}\n\nbody {\n  display: flex;\n  flex-direction: column;\n  align-items: stretch;\n  margin: 0;\n  background-color: #f5f5f5;\n}\n\nsummary {\n  cursor: pointer;\n  display: block; /* Firefox compat */\n}\n\n.report-error {\n  font-family: consolas, monospace;\n}\n\n.error-stack {\n  white-space: pre-wrap;\n}\n\n.error-results {\n  background-color: #dedede;\n  max-height: 600px;\n  overflow: auto;\n  border-radius: 2px;\n}\n\n.report {\n  width: 100%;\n  margin: 0 auto;\n  max-width: var(--report-width);\n  background-color: #fff;\n}\n\n.report-body__icon {\n  width: 24px;\n  height: 24px;\n  border: none;\n  cursor: pointer;\n  flex: 0 0 auto;\n  background-repeat: no-repeat;\n  background-position: center center;\n  background-size: contain;\n  background-color: transparent;\n  margin-left: 8px;\n  opacity: 0.7;\n}\n\n.report-body__icon:hover {\n  opacity: 1;\n}\n\n.report__icon.share {\n  background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0z\"/><path d=\"M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z\"/></svg>');\n  display: none;\n  height: 20px;\n}\n\n.report__icon.print {\n  background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path d=\"M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z\"/><path fill=\"none\" d=\"M0 0h24v24H0z\"/></svg>');\n}\n\n.report__icon.copy {\n  background-image: url('data:image/svg+xml;utf8,<svg height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z\"/></svg>');\n}\n\n.report__icon.open {\n  background-image: url('data:image/svg+xml;utf8,<svg height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h4v-2H5V8h14v10h-4v2h4c1.1 0 2-.9 2-2V6c0-1.1-.89-2-2-2zm-7 6l-4 4h3v6h2v-6h3l-4-4z\"/></svg>');\n}\n\n.report__icon.download {\n  background-image: url('data:image/svg+xml;utf8,<svg height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z\"/><path d=\"M0 0h24v24H0z\" fill=\"none\"/></svg>');\n}\n\n#lhresults-dump {\n  display: none !important;\n}\n\n@keyframes rotate {\n  from {\n    transform: none;\n  }\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n.score-container__overall-score {\n  color: #fff;\n  font-size: 92px;\n  font-weight: 100;\n  position: relative;\n  display: inline-block;\n  text-align: center;\n  min-width: 70px;\n}\n\n.score-container__overall-score::after {\n  content: 'Your score';\n  position: absolute;\n  bottom: -4px;\n  font-size: 14px;\n  font-weight: 500;\n  text-align: center;\n  width: 100%;\n  left: 0;\n  opacity: 0.5;\n}\n\n.score-container__max-score {\n  color: #57a0a8;\n  font-size: 28px;\n  font-weight: 500;\n}\n\n.report-body {\n  position: relative;\n}\n\n.report-body__content {\n  margin-left: var(--report-menu-width);\n  position: relative;\n}\n\n.report-body__aggregations-container {\n  will-change: transform;\n  padding-top: var(--report-header-height);\n}\n\n.report-body__menu-container {\n  height: 100%;\n  width: 100%;\n  min-width: 230px;\n  max-width: var(--report-width);\n  position: fixed;\n  will-change: transform;\n  left: 50%;\n  transform: translateX(-50%);\n  top: 0;\n  pointer-events: none;\n}\n\n.menu {\n  width: var(--report-menu-width);\n  background-color: #fff;\n  height: 100%;\n  top: 0;\n  left: 0;\n  pointer-events: auto;\n  border-right: 1px solid #dfdfdf;\n}\n\n.menu__header {\n  background-color: #2238b3;\n  padding: 0 20px;\n  height: 115px;\n  line-height: 54px;\n  color: #fff;\n  font-family: var(--text-font-family);\n  font-size: 18px;\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  align-self: center;\n  justify-content: center;\n  background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZoAAADeCAYAAAAEuMatAABPH0lEQVR4Ae3dBZyk1ZX38d+59z5PSfv0uMPg7iQB4oRkIbrZbGSz7Js368mbrLtE1903nuzGVuIGgQgxCBbIAAPjru1d8sg9b091fxhBMsN0T1V13+/ncz5VNRaFP+ee89zi8QRBEARBEARBEARBEARBIARB0BTZuru7Be0HOoFYlcirAqgIiaoMGKtbAaWNBYFkD9/JzAuC4H3/57KeZKB0Wb/jea9YVrowFbNC0KVAj4Cr50olzTACCKCy11j/QLG/tlmEb5bXp18GdtFmgkDSy3lS7qxzLKsWzSfVcWCMIAiOzxfvuIZV3S+lp/hSUk5DYSzzCIcIkORKJcsQDqOg3gBgUj8qyP8YlU8DnyEI2oTsfc4ynkjvm65eaJ9z8cuMdQvwmoCpgAwiOlH2AERDCONABZUxICUIgob6R7/ynOz7D/+yjlRuEE8BryAccixBczQBUXCj+Xc73difhcAJ2oFUn1PmcV19dnfxZ1/yM2CXkucJIEeWGsCD1IAKSBUvIyCDGD0AphFEUz9fb9QcEATZvetPSd/7uT/2ef56CpFgDIecYNBMUSOAYES+YJ37XeA+WlQQyN6Xr+bxzP+XX3upJEPPIPdVnpwB5LBXEBQa5UHGgFGQUXIzBDIETFakg6B1IGMWCILq337+NfnajX8lxcISjDBl2oPmcCIMl7fXfh/4R1pQEMjwhX0cLfq5a1eUXn3lGxgZtSCeE2MeLcEAOZABOaIJyDAwAAxh4iFERqaCaQzrhwGlxQWBfv3+qP4/t/xZVo9+RZxhykkJGgCNDEZ4rxV5C1ChtQSho1nO0fr/6NWvMPN6Lif3dWaGABzVCZmpSoAaUMdSRRgGBkEGcbof0RGgAlIBMoKgSe5ev6KcRoVC18Z1C0758Hve7p39SZwBOMlBc4hFPmeM3AgM0iKCwEmqHK7nRef1miXzz6JST5k5etir50gCFIAiOX3AUkBAIUUQqaIyBhMlOlGMAIMoQzg9gGUMJQFSQDlOQbB7sLsDm5f/4+tXdW7b1rM0sqxQdInAotTbRV+9/8z+pEp3LnQV8aU/tz+39PSC70kkotly9MWo/9SY6XtNWIVuFYFsePHZHG78rTc+/fxz6jdQI6U1CWAOKwV8o+RgSRUYRhhBGUAZQnQIzBCqw+BrIYACgA994+m9Y2OF00yUnXLHnlWrvn3XmhVdsEpFVwBLKnWzMMuwIkxSEIFyQfEiGFL+2Pw6L+E/qVFGEYCmdjQABWrcaZ73wG8W/uPm3fPjnRbdirLJCI+IMAgoJ1EQyIpfyJnCaZ25fOqtf/36nrR6FpiE9iOPE0QeSBBJUFJEx0GGEBkE9qN+GBgDxhEZAZRgVvnSHWeVXSlddd8jq9d89vbzzi0V9FxVOXvTvu4VWUqXCOWCgUIBlENElMejQBXhzfLX/LK8m+qhkGmJoBGUAhU+ZH6Pf+U36aChKjAK7BR4QIW1ueUhYKsxbAH2MUOCQC55hTKFC/vW9r3/Tz76C4wXSoBn9pAnKIOQg1SBg1UBHQUZRBgAOYD3gwg1aFRK0NLW7Z7fsXt/39L33HTVeXGUP21guPviezb2L7OWJc7SFztAaYic8lRUEJ4nX+Ov5KcxeDIcQMsEDYAhx+L5Q/cRviYvpIjyBKoi7B4fZHNW415V+a6HtcBuYIBpEASy6gplCl/9t387/7SF219LEtWZO44MHhAmKQ2SIkxtwukoMAIyBAwjcgCfDwFZo0664LaHTu/64t1nnke9+MzP337uBanlTIFzvacIYA0UI0WZHhlCHwO817yMNTxEjRJAywUNQEyNHXIab7Zf4YD0EaE8EWMBYQqKss5Y1lq4R+EbKfJDYATwBMFxcvNW8ah0xK5iIcrcolMFkPMYKih9QD9gaNAcyFFyRGogI8AwRgbI/QDCKDCKyBAwzrQJ/ufWS1099pd86juXPX3z7r6nZXn89OFxWWYNrqOoOB5LmT4Z8Fp5L2ewlgqdtLKEIqv0QV6tf8/fyR8R8cR8zuEEOMvnnJXCjwO5ovtR7rQi30G402V8BxgjCI6BXH6DAvCMNevkz3//Yz8fj8kKkJTgyQjA46xoWwBEEqAO1EArIMPAIEYOkPv9wDhQmXr1BE/qextWnvuhW64+f/POeS9Yv2PeVRksLxcoOwMIGFFOhgThNDbwPvNiuhgmwwG0bEcDYMmoSwe/aG9ho5xKAWUaeOPZauCutCZf3rOOO1S4H1CC4HHIolUKwOtf84P+v/iNj7+RkVInkBNM53Hc4T+mIBXQcZAxiMeBySCSxkzoAFADkkbNQVv29/R84JZrTt+8e97131q79PkYLoosncZA7JRmUIQa8LvyB/y0/DPjdAK0fNCA0MEwn7Rv4S/MuyijTBcVEEAMVRV+oMLtqvIZ4F5gkCCYIhf8uALwyivvOeMPbvyv11MpekCZSYE5VCqAAh5BQXJgMnxERlAdBIanPg+hfgDVFPDMIg9tX1z87D1nPfPOh8684f7Ni5/rvZwLUC4orSBBWM5OPmReSB8HyHBtEjQQkTDAYt4U3cxOlhChzLBHVLnVeG4e7JObgRGCOc2NlGm48fnfWUg9ioAaMy3wUwUIj1IABOgCelA1gAFyIJ0skyCMggwh/gCqEyUjj86FoEKb+M7Dqwp/d8fVz9r24MIbSAo/tmsgXl0uYouRAkoryYBnyK0sZCdVOmgnGRGL2cIl+nU2y2uImHGni3A6lp/vqOpWb/nynsXyhTp8ExgimHPcvnk0dEWVhSCeoNl0qjyHSKNUC0AR6AFdgWJABEhAqqBVvKsiZnJF2+T7yQr70KgC1BBt+rU9P9i+oPjNe8666LaHlr30we2LXpqmnB1FIAK9nUorUoQIz/Pkc4DQbhQhw/Ec/2m+Yn8SxSAoM02BOGEl8HOnbNSf8ykbxg9wi4p8TOD7wDhzQuCWfRdWnlkR63QB4AlakR56fVyCahnoQFIBBAAPmBRE6iBjiB/BuxGIRoAhxA+QR/ungigDUmbIr9/03FOHHu77iVsfWPby2HIlQCGCUlFpdSmwWrazhnVkRLSjjIg1ej+djDBML5aTQ4WGXIAiazpXsgb05wTuxvNFKcgnpcBawDNrBXL5jcrPveBrpTdee/P/Y7wYFgFmJzlsJmSYlCPkqOSIVEGGER3EuwF8NAwMoYxQYBio8xR8/BsXlbfumn/DR799/ktG6/bFkdJdLihKe6kgvEBu5m/lddQoAgLQNjMaAINHEd7t3sPNcgNFlJYg1FX4qop8PEr57Oyc5wTOD0BhWHuBCFBmo0CBfLKERykCWFS7QHtQVkNisIkH6igJSp2IKsgAogOo3U9eGAYqCOPAKEf59HfPP+O9X73iVQ9unPdTKGd2laA7VgCU9mOg8dyMJQOEduQxdDDKGl3LFxtB0yKUgijXC3q9wmbj+S9V+ShwL7NG4Mgh7ZBefAiaNjQDt2jLoQCCMjllMuYBy0EEvEGq+VTQVIARjB1Mk6jy/YdPWfr7n7/2RYPbup9jnfbP68ypp5YkAxHBCIiAESaJIrQ2RXDASjbgsbQzwVPWYQytyRtW55bfENU3E3MzVt6rwteAUYK25rIesOQ9gANSggB0qiYJRxOMdwhd1Wph2cM7F527bseiC/YNdS99zXn34s8Xxusx47UCo9UiY7WDFTNaKTJUKTFWLZB7g5+ozINXEMAYpoJIaSUGWC5byLG0s5SYFWygm4wEh0VpNaIAFKnzYtCJ4h6FD8WpfBTYR9CWXDQEL7/qni6qBfukQRMEAhifgeZDo539D25dcvEjuxZfPBEgfaA46/EIAnQV6/SUapj+IUDJvWlU5i311DbCZqRSYvhg8NQKVA4G00SNVYtU6hGqNCiThEPdkIgy01KEjEkCdDLMyaAzfHzWowcQlCpgESLAoTwV3gtJDtZAZJVpJTQIXHywfKS/Jrl8RHM+DKwjaCtyyQuV7/7du14ex/kVQJ2jBYGgGJ+j6IGhrkVrty29aP2uhReN1wpdzipGPD+KAIgCIAKCYmSyEEgyO1GOWhJRT10jfIbGywxO1ESQUU0d9SSi2vh5g+pU6BxeAJx4ECUI8xnkeXyOpbINUF4qH6OL4RntanJVMq/MFEPOKPO41byCGmU2s4Zv6I8xQgcRyvHIvFBwGacu2sS+4QXsHemd+VsbBIADAh8zKv8KrCVoC/K6X97p3vuWD766GKVnAylBcDibZ6joroG+ZfdvXnbl5j3zz03SqGBNjjHK9DgUPiL66PsGARTqWcR4bbLrqUzUwSAaHi9PVImRarERTlluSHNLltMgAuY4u6AEYTWb+DPzc5zH9/E4QKhRQhFmmjBzFMHgialiUIScb3Mtv+3/hSH6j7mz8SqU4grv+Mm38dwLb2HrvlX84cf/mLs2nk/BKSfJuCqfVZW/B75H0NLkNW/a2fH+t3zotUVXXwmSEwQA1mcounugZ8n9m5dfuWnPgguT1EXOekSUk01EMVMloiiC9wfL4FWoJlFjHjRSKTJaK07OhyZq4nPjOC7NLKrgG3XUUZxRBFCEFOWP5Dd5lfwb4/Qx23UwxN/r2/lnfQsllGNRqQsvvuwW/vINb4U8gtIoX/r2T/LWD7yTcgFAOYkyVfkE8A/A7QQtyWW5iVHKIEoAhIBBNN83ETD3bFz19C17+8+vp67gjCdyOc2iKuQHiyMJYIzSWazTXaqxfP5kGOVeGt1NmrmJspMdUKV0MHgar9WDnVESUZnqkNIccqBb6pxu7ieVInAokWYrT8xF8j1U38KxEgOj1S7II3AJoAxXulGawono64BXCHxCkb8FfkDQUuR3fmv9oj964yd/puDSMqDMTYFRj/Hp0Ejn/B9sXHHlwzsXX1JLXCma6mDakYgicMRxXONVlCw31NKoETjVg4GTxAxVyoyOxfxK9Te5OPkmtawAaQZpDl7BCFjDbNLBKJ/hdfym/wfKKMdCAVXPG5/3YW648lNs2H42f/6ZX2fX4AKcVZpsTOAjxjQC52GCliB619+eyryR14I65p5AVLF5MjZe7vnBppVXrtu2+NLxetzlrMeIMlsdCqBDCwkopMScyoM8N/8spl4nq+ZQT9BaSj5Sw+8agiwHYQYICDPKyKH/XWPqjNHFm/3HuIOnUUCPq8OsZ1CMlCQTjNAImRYyCLzHO/kbYDdNFYhu/aOLyM0NgGOOCQHj06QeFddtW3reDzYvv3pwrGOBszlGlLlL8Ahnyf2ca+6lJFUQQUUoZGP4u9eh1QREmHbqwecgwkwQYLxeppoUEFF2sZz/0F/kq/pCIkBQjpeqtHTHK45NQzvkr8cH+AAwTlMEolvecTXePwewzClhk2z9jkVn3b1h9bP2DXetEFGs8QQAQoajSIUCdRTwavixp9/N/Pm7IQcQpp/C4B6ojoIYppUo2Ix//tKNfPS7z6cjVoboY5QCRQCU2cpYEOFuQd4BfJrjpB30qNIjUGRSAowCBwiOiejmt12HckUImjkyhxGf7RvsWXz3+tXXbNi94CJVcNYTPJbHoAgAuTe86nnfY9GqPaAWVJl2YiCrw96tkCUgMu1B8+7/eTP/dut1dBXAABZlLokyPm28vAu4kyfgu+jP+rjCC9dV53GONSxVZZ4IJSbVUYaBnSiPqHKLV24DdhI8LgdSBhWC2UsAmyfVaqF8z4aVz3pg67Kn1ZKo7GyOGJ5AYPAcouA9ZB48M8SDRFDug4HdIExv0KhifUYMRChzURLxMtDnCfKP3vCXwABTagu4uL6AnykrL1FhNYA9+iraQxYBZwDPFvhZa9iP50uqfBC4leBxg4bZLTwPs2H74jPvePjU5+8f6Vwa2ZzI5bSiQKHcA2NDkNRAhGB68xboAv0d43mJyeV3h5awTubzK7HwUx05HQoIx0mZj/B6EV4twmeBdwL3EjQ4lCIgBLNx2J8MT6wr377u1Gdt2LXgYo+Y2GW0sEAVrJ0Mm6QKCDMgUMBzbjqPTxfnMaDQjwflhEUKP64511W28Bfe86dAEoJGNEYJZhPjM/XG3Ld+9ZV3r1/17PFa3OOsx4knaJOwKXXAqIN82rfQAgUMZIuFvB9B6UeZPgpi6Ow6g7chXK7KzwK753jQMMuCJnQxBwa7F333oTXXbtk7/xwjvh2PyULQRAVwMeRVgukPmXQZ+D4gZ8b4FARu8I7Pbt3OK4GtzFEOJAYlaP9ZjM+NTHQxT7vnkVXPHU/izsjmBG0sLkK9AgjBNBHIlgq+F8hPUq55Ll9R5n+yUW4A9jAHORQHCG0tPHg5ONzV/50HTnvBxj0LznPG0+4hEwi4AiggBNPBQ7ZEyOcBOSeNerC9XObm8T6ElwEZc4wDNQTtyfgcFX1w87KLvrduzbXj1UJvGPbPEgJYByjBNPDgeyCfD3hOOvWNul7ht4B3Mcc4wBC0H5cnlWqh4ztrz3jBup2LLjNomMXMNiKNOkGBAhFkiwQE8DSNgd/0yufn2g3TDhFBlaBNiHpsnm7dvXDNtx847cf2j3QueZIbloMgUMjmgZaAnKZS6Eb4A+CVzCFhPtNOrM99buWuB0951l0bVj0nz20cu5xgFlLA+xOd0QQKWgDfJ+BpCQIvF8MzgW8yRzi8KiC0tsBlyeh4ufub9591/cbd88+LrMfZnGAWy1NOTCAKebegMa0UNCaHX8wM95cMw4CfC0dnHlVDSwvPxmzbveDU2x444/qBkY4lc6GLCRTSOgjBU6WgFnwXLUUB4/mJ2PNMEWoC6wVuQ7kF+C6zkAP1tKbAaK6ov/fh1U+fuKfsusybeI4M/AP1kNQAIXjqNAZfAjytxgJLMwWFU4EXAG+zwje85W+AzzGLOIQcpQWFBzBr9Tj+9trTrntw29IrrPU445kDAjFQG4P8hL4qINCpkLGAp2UJjzJeeY5mPAflw1h+HdjHLOBAUtBwsWYribL64GD3gq/df9ZLdg70rpljXUwgQHUMcg/G8AQCzyHCIXroVUtCuxEA4adRzspyXgVsoc050DrQRcsI85ituxas+fp9Z71spFrqD0/4zzEikNahMgJGCB6HBwxoGXwn+JKgEQ2iQB1MVbEjoBHtS7kisnwmHeF6YAdtzKEkBK0RMqLZ2k0rLv3W2tNfnHsTz8WQCQRGDkCeghiCo3jwXZD3C74TMBxBATrAi5AnoAJ42pfhQp/wntoOXgYk7dzRJDRXYHyuKnz3gdOfc+/Glc8VVKzxzDGBMVAZhfFhQDhMoIBAtkjIFwAG8I16QuoApa1pDoWFvKi0hJ8F/ok25RBTQb0CQnDy2TxLkjj6+n1n3vDwzsWXOBOe8p+TxEBah6E9oAoiBEeFzFIhnwco4PnRlFlBc8hzfgvhk8C+dn1gswIoQTNCJh2vljq/es85r9i6r/+MyOUISvMEXs0T/7gIGAGEaSUCSQ0GdkKegDVMOwGMoGLIgRzhaAYQlJajU7cu9wM5c9UK8bwC+DfakEO0gtIE4VLM/YM9C2/9wdmv3DvUtaz5ty4HRjwFk/B4cjFInkEtA2+YPgK1MRjci9brIMcwVJCncDONKNiMOB2nm1E6UQ5RFKFGGY9tzVuX+wHPnKbCq9s3aKwbJ8sITiKX1Xbt61/51XvPeeVIpbSg+evLQeojTu3cwrVLv0nuHQACIAKAihDtSknv2AtpBiJMH0VrCdkdD0Oag/Ckqt6TAsLxe3n9d3mReRuS8ShLTpUyb7cf4CE5nwilZRjI5wtICBrgfIFlwI42DJp0hEyU4OSIstq2ifXlm+8991XVetzd3PvKAhFFAGs8TnIilxIhAKgCuQfvIVd8LYPUQy4gTB9jIFG0kkGW/8gQ0zTDe0U4fmWp0SEKemTQVOjAkaAt2M34MuAJoFuFs9oyaP7s6y8e+ZUrv5LHJo0AZeYELq9v3Lb43FvuO+fHk8yWQsic1AfgAMXIZIkoAGnuqGeWSlZmODHQMUYuRUgydKLIPZr7Q4EjAobpZQSt1EEVjAHhyRnhqfJYHkvIcShCSxHIOwED5AQQAYtpQ+62HSuSt6jUgRKgBDMWMg9tWX7xN+4/4yW5N4Vwncz0E0DMkUHiveD1YBnSzDFSLTI6UePVmNFakZFKkbFagX3VLq5KDvATV27Gz18GWU6DMEVAhBkhgg6NQZaDs0wJDGhJwDMlUKVIG3IlmyagM3p7X3jaP68/sGnFZd/44ZkvQ8WGZ2ROIEhEpwoE5VECWW4Yqxao1GOqScxItcBwpcToeInhaolqPSLNLVluJkoeveHFCFQF8B72DcHC5WCEk0IEkhS/fxiMcJhAAMdhAoE6bcgZpA4yDgjB9BIU45O1G1de/s0fnvFSBGueNGQCEQA9LFR4tEPJvVBPo4ly1DPXCJShgyEyXn60M6klEbXUkUxUmoMRkEPVIICzekTzYBAwAjsH0NU1pFQA75lx1uB3DaDDVTCGKYGCGlDhkCATwx7akJNIEzSvAIZgehmf3rd+1dO//eDpP4aoNaIcEkwFCOZgGcWrkOV2srw5GBqNIBmplBrHXaO1AuMTVanFjNdjkswggB7R8RyqQqTHP5Sv1sk37sadtwoEUGaOEbSekm/YxWMF4kEAJUBAc4aTAzxAG3J33r9UudZVcLkhmM7LMev3r1/59G89ePr1ImrmYMgc6kamCkBVUAWvQiWJGKsWGas3AmSi4smjrkZ3UqSWOLwKuRe8BwWMHOpSIqtMO2PwW/fh+7swy/ohzZkxIuTrd6JDFXCGwwQCKJADjkBAPWtdDztoQ871wKc3Xjj66gtvU5ICJygQYGom860HTn+xiIoRneUzE4DJV+HQ5zS3jNViKvVCIzTGapMzk6GxcmMoX6nHjV+TZpYkE1TBmENBIgCiWHOwTmI6eiX/4VYoxpj+rukPGxFwQr5xD37THp7gP1zgQaqgRUDDkXJ9Fx+nTbn6Ltj5YHGIyzUlOeFONbB5/cGpwT+CGNFZtBoMcth6cJabRkjUs4gks42OZGi8fLAaQVKtxxMVUUliaqnF50fNSmSqgNgpLcPI5AOUd2/AXXQKZn4P5B5UOWFTKZpv2E3+4DYahODxKJhRxfdJyFzYVjX8D23KVQ2YNX6ElAxwIWhO7AvL1m1efuFEyLwUsEa0PWcmRhEUr2bq2MqQedvoQEarxcbcZOJ1alZSaAziD75muQCgOlkcdszljIKhfdjJeU1253rsmcuxKxeAtZDnPCUiYA1aT/HrtpNv2QcCiPAEAgN2DPIaaBHwzFmJ5y86lrOXNuU6lkOn+CGYCpqnLDzxv2nH4rO+PhEyCq4FQ+bILS4UAAUEIVeoJVEjMMarxcaR13C12JiXTA3jSb0hb6wGG3IPHD4zMYo1yqxiDGQ5+Q83o/uGsWuWIH2dYAAPqAfliclhSZvl+O37G52MDo+BtRyDIAV3QEmXC3ORCkSGr3QY3kMbc/McfHn9paOvPueu8c64WsIbguPksmT77vmn3nLv2a/Mc1O0xrfMarCgGFEUoZ5Z6knUCJTqRI1MBcnkcVeJetZYC278fJo/9u+VwiTTCBXmBpFG+d2D+P0jmAU9mGXzkJ4OpBhDZEAB1aN+D5Bk6HiC7h/B7ziAHxoD5XhCJrBgBsB2QN4H5MwdBiTlIfH8dAI12phLKvBIpS9PE7ePAguBnOMSrvrfvb9/2c33nPeqehp1nKxrZUSOnJmAkuWWqQcSG6ExVi0wWikx2JiZFKgmcWNuMvm8iUUfM9g/9HrUzCSwBrzH7xpolJQLSFcJKRehFCOxo0EVradotY6OTdRoBerpVGIbMARPgdulqBN8F5Az+1mQcYh2aJlElgF7aWNO1tNQNPk+wAIpwTGHzPBoZ9/BTqZSj3uczWfkKXgjCnJoNVhVSHLbWAkeO1jVIsOVIqPVwkQVD1YjVLyXRuUKqiACZqqcVYKnMmcRALSaoON1GgwgQoMCKPjD/gt3lhMQCJBBtFVJlwq+F9BGzT4GUDAD4HYrkrIS0f8GuRbY2L5B42lYu/b0PZc9424lNxyDwPqsXi8Uv3rv2T8+0TEsjI4zZARAjlwJFhQEBMi8aWxs1dJoagg/GSgjjWF8ufF5qoMhzQzZYU/BGwMCMDXYNwTTbSqxn3hNzxBMJwFyiLYr+biQ94MWOURpX8IkDzIG9gDYEaXBAHCqop/0yHXAAdqQy4WG/3rw9AOXPev2cepRDHieWGDU+8zYiZB5xc6B3tWxy580TI5+eNFMXaeSZo4ks40aqxcad3KN1A52JxOv40VqU9etVBtPwYPIVHHke2OUgiEIZjehwR5Q7DD4TvBdgi8CBlRAaDMeJAVTBRnVxiseMBxB4FKDfsyovAyo0GacUxpKqe73uR01sOBJgiYQnYD/+v1n3rBx98JzYpcBIKJHBIki5I3trIMlVJP40WtURiuNMGmsB1eSyZlJNXGgNCgcCieOuE4lCAID5GCGwAwrCKgFDO1FQXLAA8ok06jHJXCtyfWfXcYbAE8bcXFdAfjy7efVX3vdyr1nrN6+iCTiMQJB1OCy2n0Pn3rV+p2LrywXEqZQSx3j1UIjOMZrcePZkpFqibHGVfSFxtPxuZep4AFVMHLYMb5RjlEQBDJVUySjPcmhOhaZ48aJ2gC8gzbiakUeFTu/Gbhgzv9fVxFQAcyhH9c6pfruu3549iWfvf3C68brMaNTYTI8PvnAYpJaktyRpIbcHxkkIjSIKJGlBQUGD9SJKfB4shxUAWFGGQEB9InHgxOlyFN+KJcjWHIyHIKnbQlzgtDwNlFZD3yMNuGMClP4yNev2PIHr/9Uwuy+NFWYZB4twQAZUAcSIptgzSjKAdBBusoH6JAD7/i7Z57xpW+f87bhasEAiDz+avDUFfRBGxGgoh3s4HQqdPAYCj09OZEDVWaMAEkqZDlPqFY2pIBwfERgvOao1CKMKFMw5NQoUaMDIWgDoqLvSXLZCnybNiDn3KBM4fzVO6OP/+E/vpGx4nKElPZ2eJAI4B8tLznoGCoDGB0hZQjPCDCI6DAdpWEg5TB37Ty9+6ff+fJbNOOyMC+ZvQTlaKqQ5cJvvWGQs9ck1BNhpkRWueehIht2uCdZQReeCmeVBzYWeHBDTOR4HErQVjYoPBvYTotzh1+1/tC2JemB8d4t/XZ8Jd7S4g4dbSkCCJMEUESqwDDoOCmjwADIAIYBhnSIROuIJkDO0QarHO7OTSvNL3zs+vdIzmVxCJlZzWM4mgLWgnUCGBBhxhgFDNooZVoJGAMKqADa5qdPwRoR3q/Ii4E6LcwhwuE+e9OlP/yZF996pdStAErzCMBRQWIapQCSAOOI1ihQRWQYZB/4ARIGQSvAOGiVCE+D0tDPcfm/7/6pt2d59Kq5+7R86GjiWImsoqqgzBxVQJGpmk6C4owigKgSzALKtaL6V8CbaGFOvHK4j9125fb/88LbtiF6CirpSR7EW8CACkKGkjZeY6kBQygDCIP06jCiYyhDGB0FasyQt7//xT8+Xo1+p7usKHNRoAjOeVwjaGhbChijiBDMJsIvA3cD76dFOYQj7Bvt8A9tX37XWadsOYV6NF1dzZEdCQqgh90AmYKMgQ6BDONLI8DBGsDoAGUdATLAA5BwUrzjo88/7QP/e+k/9HaqUdpLkgkCRE45QYFC5MA52p41IKJMJ++FegJewVmIoxBmTfCXGLkXuJsW5DDC0f7iw1ev/dff3XxlZHQlXpLj3whXATFTr4KQA1NHWTKOxiMIA6gOYO0A3Z3DoAlQAzJawL++75LyP3/y0g8sXswSlLahCEkGK/r34L1lx8D8E3zYM1DAWW2UqtC2VLAWjIACwolLUqFYUC48c4zOzpRde4us21zGWbBGOTkCEfryTD+wa508Bxigxbgda3mMHWvPSn7r515w8xnLv/jTjJcdQgYIU47qTgyCAlNBIXXUjTVCRMwBvB8kYxiRCqpjwNgRWZLkUBmg1Xzq3vP+on8+V6NKO0kz5bVX/y8/+4J/w6vwL196E//zvRefwG3MgSrEUx2N0r4UsFYRAVVATjxkli+q80e/uJ5nXDIINmdsLOaTX1jKP3xsJV4FI8rMC1TBGC5Yfq7+A/A6Woxbfq7yeF79zms2/O/v3PXF1Wt2v4jUxkA+WeJBKogMgg7hoyHUjQIjiA6jdgCo8yhLu/n5v3rda9bt7PqleZ2KajuFjLC49wC/cO2/M3/eThDPm174L9x83/Op1EoYowTHTxUiB1Gbz2hQsAaMQK6cEK+Cs57ffeNGnnH5fsgsJI7Ocs4bfnIzewciPvDppZRLnFzBa9Nxvg38My3EpeM8oRf//ltv/9Nf+d+9jNpTERlD9AA+HkRdBUiBjFlmy76+0773yLK/6i0rqrQXgdw7kiwGkwNKksZ4b0A4AYFzOlG0PWMUkenoZuC8NVWedsEQeAteaMgEYuGFV+/nEzctIc/lZB+hhc4m5h1JVb4F3EeLcFkuPJlf/8sf3wRsYg5QT4zjX4pllghKu4mssm+kh7/5/Fv55Rf9UyNg/v6Lb2asVghHZyfc0SjOKr7dt86sRYQTHtKoh45yTqHgwXMUoaOUUyp4RsctJ1GgYCzzSt3670bluUCFFuA6ugim5J7fRvT5tLHYKV+4+3nc9tBVqAqj1UJTlwFUBQARbf+gcUo9EdqRGosKdGYDRMwjoYCgJ9DhwZZdRXbvK7B4aRUS4RDPI1vLDAxbirFy8gXqudKj7wbeSgtwXhUIRLka4bdpf41gqdSLyNT7Zq5YR/bQ+3buqiIH1tBmBDUWUOLxAboGNtAxvJWSXs8YBU5E5JSde2Pe8z/L+f1fegSJcyYpe3aXed//rsAaECFoEoU3Gy83AV+kyZx4Ya5TodOL/iNQYpawRpu+mPDMs2/nLTf8Lbka/v7zb+FbD13RnmEjUIg9SpsQwYtF1FMc20PngY2Uhndg8xqIwZkMPCesVFA+/uXFHBiOecXz9tDVnbJpa5kPf3YpD28pU4iVoKmMN/q3InInsJcmcmqZ88aL+q6OCheqMA0Cr0JXqcrvv/JdLFvyCAj89sv+gtf/wwcYrnTirNJOBCjGCtoOAeMwPqU8sp2u/esbQWN8hheLNxEGTyzptKw3i0DklC9/ex5fmag4gmodIkcImRahcHqk+hfAjU1eplHmMoe8IK/ySypMk0CByGV0FschiwDoKIwT2RSl/YhAIVaU1qQiYCwmS+gc3krnwEaKY3tBPdoIGAdTRJRIUqaLyGRnowqqQkdJCVqHACm83iBfAD5Jk7gcYa6q7qdHO/UvSiWmMW8DIzAw1sW/3/xGfunH/hFV4X23/h/2jvYRGdpSK/4TuopBxWKzKh0HNtF5YD1xdRDUo8aBGB5PbFJUpj+MRZSg9SgI6J/1WfkGsIcmcH2WOcsu5I9S5QJVgmm/JRg+/I3X8I0HngkKW/YvI7KKtHNHo7QEFYuKIUrG6BjcQsfAJqLa0KHhvxieiAAFk4ICAhB4FbwHY8CIMtsI4GH1/lz/FPg/NIHbnytz0f7N8rwFp+ibUGZAIALWwMa9yxAgsiAA7Ro0BW2JFWUQotownQObKQ9uJqqPosagxnFslILJmBTkXjAC/V1DDFV6yf3sfcBU4MahnfIZ4NOcZG54pzDXdPTTOREyfw5EzJhARCm42RGahQhQmmIqRIgrA435S3loKy6tomLwNuJ4CBwxowlLK2P81kv+mqed9W1uf/jp/PlnfoXhSs9sDRuZt1L/DOQbwCAnkZu3kjnH57wJ5RKC4JgoxYKiJ3uDzFhEtTHYn1xR3o7N6/ipAf9TFZGiBJU6/NTVn+cl13wU0iIvfuZ/sm7Hmfz7La+ho8Cs5DPOUOG3gd/iJHJ5ztxiOcegv6UEwY+mj3Y0Hj1J8xdvY0RzyiO76dr3CMWx3YetKDtOlCNlUhBFCQigFlAil6DKrCaqb/bCfwPf5yRxijJXjOzDdHbxp1KglyA4RgKNjgZlxqiZDJhiMkDf7g1UNhygo7bniBXl6aE4MiAoxfCZ79/AFWu+z0Vn3M4P7n0hn73reooRs5ooJYG/3LNIrgUSTgK3d6EwVxQjXhlV9cVKEBwjBZGZu8rH2wg1jrhygAWbv8Oy9V+gsm0F++rnoNaAGKaTAFYzILBGOTA6j//3gb+lv3sfB0bmU88cziqzmQoIPHPeADcC7zk5M5oB5oRinT4VfZtaguC4OAvOgSrTRPDWocZRHNnFwo3fYNGGWykObQeTomY1ahyQMt0UsJJjhGAqbNLcsvPAYowBZ5S5opDo74nwOWA3M8wVU2VOsLxVlbMIguOgCnGsWKOATEPARCDSeP5l4fpbWbDpNuLR3WAcuBhEmFmCJcOhKIKghAeMFWOZeyyr6qP8LvD/mGGuPs6sVx2Rs3qW6VtQguC4qApx5DEG9EQCxsWI+sb9Y4sfuZn5W76DrQyCdRCVOFkUsOKxeDIsc1egHlyZ/ytePgjczQxyUVGY7Uq9/F6W0kMQHCcF4miqo1GOi4ppBIzxGT277mPJRMDM2/Z9TG0EXAGiIiefYMmxkpOqRZjLAhHK4vhD4GXMICeO2U24Jkv11TwFQaAKhUgx5ngDpoBLK/RtuYOl675Ez64fIGl1KmBKNIsqOMlxkqEaEwSq+lLgBuDzzBCnqsxahkhy/hjBcYIUQEGEOSRQnepoLOixPAPjYqLaKAs33dY4Iuva8wD4DGzcCJhmUwRnPFY8KCAQBAp/LCq3AhVmgEOF2ao6yMvLffpcPE+ZKtQTwTmInFJPBfXh+zbm1tEZGDmWFeUB5j/y3YmA+Sod+x8B9eBiMI5W4iRvFEEwxSiX1or6U8C/MwNcvajMRuOdUuqp6++gJxYyWS68/Ln7eNWLdtE/r86mbR188H+X8937u8P3oc8FCoVDM5rHX1Ee3cPCTd9k0SO3UBzeCgjYCBBajQKWyaBRDgkCl/Mb1ZJ8Ehhimrk0FmajZQO8rlriIlWesloi3PiSXfzOLz5Cgworlla54txh/t+fns037+qlVFCC2b7e7LEWcn/kinJ5aBuL1t/C/E23URjZBcaBjQGhdQmWvFHKpCBQAZdyWlfK/wX+imnmukaYdayntyb66xiesjwXFs5L+ZmX7gAEEgNTip2TP37H/d14bzBGCWb31pkxkJoCeN+44LKxorz5W7jKAJipFeU2oIAjx5ATBIdTAUHfIiofAfYyjZww+9QtP2XhTE5AlsOKRTX6ulPIhSOosLLxczn7Bg3GEMxSquAKESZy9Gy9n4WPfJX5Ww+uKA+BmxrwtxXBkGPwIATBERRW5OjPA+9gGrkMZTYxnl5n+FUVTogxMDgSUU8MxY4ccg4RZWjMMV41GCGYzSwsGl3H2V/7AMUN9yFp5dCKctvyODIeTxAY4Ze95z3AbqaJQ5lVROQNKnoKJyhyyqadRb7y7QW86sXbwFjwAk4B4dO3LmJ4zFIuKsHspAhxDOft/SKlka+B6W0ETPsTXPg65ycWLDoYNsAfME2cEWYNL9Kj8ItME2fhbz6yEms91z9rH8VSxsBQzEc+s5z/umkxpQKzXTg2M9ARZ+BKoIbZIibjCQWB8EaQfwZ2MQ0cIswWBm4EPW36gkYZqzre9i+n8YFPL6e7K2XfQMyOPQWiSBEhmOWcKJ1RFVSYTSJJEeGJBMFihJ8F3j5NQcOs4A09NtdfUqaXs4pX2LSjiGoRYyCeEyETKOAkp2xq4IXZQoFIwtFZ8ORU9Y058i/APk6Qy5RZwRpepjlnMgOMTIbL3BNYySnNsqABIZYMEZ5QEAisMJbXAH/PCXLG0vb21Ijnj+mb4m5Qz7QIAgUMOUVqgDCbxCYlCJ6MKCj6i3sXynuBCifA7VlI2+sf4kVxlcvUM62CwOIpzLKgUaAgKUHwZFTA5py1bDs/AXyIE+CWbaetqSBi9Jc80y8IDDkFnX0dTWRShCD40bzTX1DkY0DCU+RyR3tTrhR4LtMsCBQQ9UR+tgWNEJERBMdEeRpWnw3cxFPkMEo7k1zeCDhmQBA4UqxPQZhVnKYEwbEynp8/oaAxnrY1ulNWdyzWV4ow7YJAgbJUEfGzbkZjJUNQguBYeOX6eUbOA37IU+B6RWhXbon+dAI9zIAgUKDLVTEos43B4yQnUxcCJ/iRBApDXt8A/CpPgRvySjvKPB3G8JPCzAgCVeiJK4goKLOCqEd8huCxeDKC4Ngo/DiedwCDHCennrZUynhuvcA5KDMiCBTojiogHlRmQcB48qhIre8UtsqZ1IcjBCUIjoUKKwuZ3AB8hOPk4kxoR2msbxRlxgSBBzpdFVBA2jZg0Jw87mS8dxXjfavJu/pIBgyqIEIQHBNRSGJ9AyL/ASjHwSUF2o7COaI8nxkUBKrQYaogSrsRnwNKVuhuhMvYvNVkhS5AcXlGJAYhIgiO09UevRL4HsfBeZR2EyfyijSmLMqMCoKSVAEPWFqfIt4DSlrqY2zeqYz3riCPO2BqNgOgBqwFhDYQqAoAIkqzCTjNedVxB43mtBeRUhbrT4oyo4JAgRLtcHSmiM9BDPWO+Yz1r6HSs4w8KjV+XHzG0Ywo1kDuQQhaVT0VjAHvQUSInTZ/Nd7wciPydmCIY+QiK7QTD89COY8ZFgQKFLUK+JYOGDWOWtcSRvtPo9azhNzEiOaYPOWJiIA1Sp4LCEELSjLhGWfexY3P+RC1pMQ/f+UXeGTXKURWaSphdZZyHfAJjpHLUtqKi/Q1npMjCCKtAdqCA36PGkeldxVj/adS61yEGjsZMD7lR5gKGoIWleXCgu5B/vAn3snKFWtBlN7SCG/8139GVZp6jGYE6jV97XEFTVpT2kWlLIvmwbWcDEEgEOU1UG2ZgBHNyV2RavcyxuafRq3cD2IOHZEdCwURMEYBIWg9uUJXcZwlvbugVgaXsWrBFiKXk+YOoXm8h2I31xRgNbCZY+B6umkfRZ7rKywRZlYQKIIViKUOSEsETBZ1UOld2ehgklLfoeG/ZhwPBYwBawkrzi0qsrDtwGL+5/ZX8MqrPkFWL/CRb76OauKIndJs6unLM7kO+DeOgcsToV10JPqaNAKUGRUECsQCJUlApWkryoKSxl2Mz1vdWFNOit0IiviMEyEyFTQErciIknvHn336N7jl/udRSwrcu/k8Iqu0iiTSVx9z0CSR0g6sl9UKzxBlVlIVVMEYJWg+VShaT9nVOal0MkTMRKWlHsbnncJ43yqyyRXlxo9PB0GxRglalzWKqvCtBy9FBAqR0kpEuRzkHOABfgQnKrSDzPI84+mfrYO/3EMhUqqJUIxaYmc+bJzZlLKtA4YZpx7yFGze2CA7sPAK0r7FZIetKE8nY8AaQAVQgtYkohRjWlWH8bwQeGDWfE2AoC9VZp/MC/O7Bnnri/+O5Qs3cfNdN/Cxb78SmrpZEqhCbNLJjsYLM0chq4MrMLz0Qg6cex2bRq9idE8nkWSYPGUmiIAxoDx1QaBWX6xe/hbwPAnnhZZnhGUqXI3Oxm4GfuY5H+alz/wPyGIuXXUfa7efxZ3rz6cY00xhRmMyimaGgkZzyBKISgysega7z7iWoaUXYYoF/P3SCBixyoxQEAPWKKBND/R6ImQ5GAPFOBwftxNVnqY5pwPr2v5mAF/gOnL6mIVEYGnfLvAOkhKURpnXMYQSND1oJKUgCSBMG59BnuALPQyccs1EwLyA4YVn402EzevYtIpoETAz/MVnYG1zj85yL3gPF505xtLFNUZGHfc+1M14zVCIlLYQFK3R639k0LT6QDDOReqi1ymzlMLHv/NKLjr1Hnq7DvD9e6/jro0XEzmCJlIgIqUgdVABmY6ASck6+tm36ir2nH4tY/PXAGDyFJvVTvrZvzWK0hzeC3Hk+fUbN/Gya/dQLKXgLXf8oJc//MfT2La7SBzCpi0oXBfJkx+fOSdCK6sYXWAyns0sVYiU7667jBv/4QMs6dvND7eeS6VewFklaC5HRsQJdjR5CupJuhaz99RnsWfNsxvPwogqJksApVmMBRGaolqHN7x8F69+yXbILSQODFxx8QF+942GN/3J2WiYU7YHy9N3bNCVwGaegNuxSWll3QvlilKPLvQ5zOaw2bp/KZv2LCWOCCHTAhSwpIgmIBwnfTRgaj0r2HPac9k7ETAHr4kRzRvdSytwTZrR5F7o6fQ8/8r9gEAuNHggt1xx/jCnr6zwwMaOMKdsA6p09S3l6U8aNH1LaWmurNf7nFkvsjpRBM2ioHrYew9RWod8CLQAGBALGECefEUZqCw4g12nP58Dq55GvTz/0PFYi9AmdjSqUIg9pWIOCEdQKERKuehRpU0ErsSLgY/xBJwr0bKMl06vejUzJAjUAwomAhuDLYEpCJGF8bifz5Vfy7xsiFXZVvrzfZT8IGBAYkBoUIW8DjZmdMn57DrzOg6suIIs7sLkSWPA33JUcFYx0pwHEQ8MOR7e0sGpp44Chkc5z/adJbbsLIY5ZTtRno5IHzDI43AgtKrhbs7sGuEMhGkXhIARIOqCeL4Q94DrBAyI0DDEIv5KfwUBYvWsTjdxcf1erqp9k/OT+8HXwAu4AgMrn8aeM17A4NKLyKMSJjsYMJXWPhq0IAIKCCePCIiBf//vFVxwxihLl40DAihp3fHPn1jJnoGIclFpE4GwMk64CPgaj8PFCS2rXOFpCDFtJvdCmoM1tNLdRMHhx2LdUFoO8TzBRICC6mGvgAARk3IMD8VruH+iPtXxEi6r3cWra//N6kUR209/IQMLz8Xb+NEV5XbgDJMUkJM/l3xoU5mff/u5vO76XaxeMc7wcMz/3rKI2+7qDSHTbhSTW572hEGTW1qWy/Q6lfa7TqYUJ5yxZBv7Ruazf7SHglOUoNnUgzFQPkUoLwOJQD1ozo8kQKwQA5lEfKP8NO6YqCsXJTx3YY0uWyfNarQNBWMUEVClKQqxsmlHiT/8p1MpxlBPwQgUC0r7CbzRFwB/wuNw3mirzmfmqeEStL06md6OYd792j/kmnNuY/2u0/id/3wXD2w/vQWu9g4hYwvQebpQWACaN+opMUBJIQe+sTlm+5DwE+emLOrISHKhHRx+dNZMkdOJAq9CqaCI0KYChQuNyjJgR9tcqpkZvUyURbSRegrPPe+bXHPBVyEtcdqp9/CKKz/N2m2/gSIIStCkkClC95kQzQPNmBYWsBY2DkV8+Ae9vP6CIRZ3tk/YWAMitAQjStsLenOjzwD+i6O43CgtyculiDraiAiMVLtALbgEUMaqXShB0yiYCLrOEKI+0IxpV7Cwa8zysR/2cONFQ/QVPKmn5VmryPQNaIJAQC5/3KABoQWJiD6DNlOI4Btrr+bfv/RLvOiyL/DDTRfxn996Nc4QupkmUYXOU4S4HzRjxhQsbBtxfOahbl53/hBGwCstSxWcDUdV0ysQ1SuACEg5jBNVWo0aehQuF23Db8VTx99+8Rf4l5t+niQXjEBkFeVkCzSHwgIoLQbNmXGxhR/ujbljR4lrVlWoZ0IrsyFoplkgcImKLDx6TuMQodWo5yKDLqQNGVEKDlSF2CoAStAMxkF5pYAAnhkngBG4bUsH5y6s013w5J6WJQLGMG2CQKEL1UseGzSqtJoCPD0BEdqXiBI0j+ZQXCJEXaCek8YZ2Fsx3LO7yAvXjJHkglfwKqiC0jrksK8LFmG6BMHVwOc4jKMFiXARylMTBArGQmERIJx0kYE7dpQpO09sob+c0V/K6Yg8CmReaAUi4CzTKghUuIijOBVai5euOnq28NQFgSkz2c3kzVkbHqwa/vfBbkTACI2gOXN+nYsX11jenTbCxitNZ4yiTJ8gEOV0PPOB/UxxktNajC4BziAIjpVyiDB1xYyABTxNYQQQUCZr77hlx1iZ7+8ocdXKKs9ePUZktHndjYaOZsYEq3CsOiJocLQUUTlPVQsEwZNQD3gQe7AO/Zjmk+U6QQyop6nksC7HArVc+PL6MttHHK88e4SuQt6UsFFABKxTQABlmgSBQeVC4C6mOFRoJap6OcGTCQEjU7cu9wuuA0x02LclVyAZVGwBUFqOFbBucg069T381PlDlCJP7oVmsAZUmV5BoHoZ8P6W3ToT5QIVguAxNAfbAR0rIZ4vGMckZZLQUFoqAGhOyyo6WLc/4qYNnbz8rBF8E3JRRDDWoEy7ILigZbfOjJcONXoqjyMIIRP3Q9fpgi3/iEsxDaC0vNjC7TtKnLewxhn9yUm8I01QY1HxdMkoTroAwzQKghWozAMGWu7oLDe6WmApRwtCyPRBz9mCONCMJ6e0BSNQz+FbWztYMy89aQGDeopje+k58DDl8Tob7fOoaBGDMk2CYKEYPR24HcCJUVpFlLIyjehGaQgC1anr/U8TJALNmVUiA1uGI/aMWRZ15swIEVQs4jNKwzvpPLCB0uhOnE9ItA8RBWU6BUExyzn10aDJclqGWDkdVYLgUR6KS8B1gubMOiIwlghbhmOWd1fImT4qBhWL8QkdQzvoPPAIhfF9GJ/jjWuU9YrBo0yvIBAjpzHFiRFaRS6cI8qkIFAwMRQXCeqZlQTIFfZXLF4B4cSJwRuDTauNgOkY2Eihsh9UUWMbAQOgCE4yLDkoIEybIDCiZzHFGVFahnI2QTBFPcTdgikwq1mBSmLIvOCsnlAHgxhsUqFraAudBzYS14ZQpmYzwmM0gkY8yjQLAuVsFRyQOaU1uFzKip7qDUETeS8T1TpXyLvyoQcvZzPlqVNjUQxRbZjOwc10TJSrjwHgjeOJKIIjx+CZAUGw0ov0AvudF6EVpJaVRukmaAqvQq0GnWWlsytj/1CEVyjGStMoiAMM4Jm1vEIpUqwoelwbZAaAuDpE54FNlIe24NIKKgY1lmNhJMdJxgwIgk6Tc2ojaExOSxDR1UAHJ13gPYDyhpfv5seevZeuUsb23UXe898ruWNtV1PDRhVQZjUR6C9lWAM5P5oaB+opjO2j6+AG2fAObF5DZWr+cpxiSZgBQVCY+vv6HU5EaQUCKxQsJ11QT4W3vHYrv/j6zUwSVq8a44IzR/nld57LnQ80L2x8Cujs7mY6ImV5T0rmBTH6I1aUc0oju+gcmAwYyTMwBm8inqqCpCjTLwgUVgO0zowmlZVprIhyEgVpJpyytMZrfmw3qEBqmGTo7k14/Yt3cPeDZ+IVjHByGchGFc0FDKDMOpmH1X0Zyzszcg/OPP6KsvUppeFtdB1YT2FsL+Iz1FjUOk6UI2O6BYEA3rMSwKmnJWSOlaKcZEGWw8oldXq7MsiFI8nEz9Xo6coZHXcYq5xMYiAbm6yoB1SZVRQwwFUrKjirJLkcETDeTq4ol4d3NI7I4vEDgJ8MGOOYLpGkTLcgUMAalgM4a2gB4lR0GcpJFlihMfiv1gylTg+ewygDQxGVqsUITaE5VHcqUY/Mutvs6xlcuqTOuQvqpF4A8DZqlEvG6TmwlY7BLcTVAfTR4b9jmp3kZYBwglBPQBWcg2KsiDBrKSxFKDkVmk6ML5PLYk66IIqUdZvLfP3OebzouTvBOfCAU/DCp29dRK0ulIpKM4iB+j6o90NxMfiMWaGew4runOvPGEWA3MSoGLoHN7Fo407qG8YoZSMogjeOmeQkZ+YF1bpw5uoq11wySKGY8+AjnXzr3j6YepxgllqoaIdTlGarDkix2MVSMZxkgchkvfu9p5Dm8OwrB+gqerbvjPnwp5bzpW/Np1hQmkYAhdENiikIUR9oDihtySskOazqyXj1+SN0dTqSXOjcv55F629h4ZavMbLvPHZkF854wAAIEEnGzApqdeG6Zwzwx7+0nr7+KgA+t/zXF5bwp+87FVUQYTZaVEil7Aqp0Gx5Kn0q2iMEzeCsMjQS8bt/dwanrajS1ZGxY0+BHftiSoUWaO0FfAIjDygda4TiQsCAekBpeQqoQuohtvC0FXWuOzOh23lKOx5iybovMW/7nZjaCDgldzGaGyBnmoX15ibIcmHZooTf/7kN9PXXIHEAGKf85Et2sHZjF5/48kLKRWUWKuYq/S5XodmiBSxTxRA0jXOKKjy8pYRXcJaW+j++GPAJjD6kJAegtERw3YCACCBMqyQFryCcGCOTVYqU8+alXLLSc3b3CL0772X+uluYt+MuSKtgY4hKIAknWyQpCDMkSFK49OxhFiyoQ2p5VCYQw9UXD/DpWxfgVTCizDZZzDKXxTSdKIsImk4ECrHSsgwNtT1Q36+4TiZKMDGIZVqoghG48oIaXR0e709s0aJgPf2dwpI+Yb4ZZuHOO5l3+0307F4LeQKu0AiYZookm8FLNQNVsJYnZI1iBJTZyaALnUFpNoWFBMExEgsopCOQDiso00YVIgfXvniMJfMzslxO6A6y3BZw4weYt/l7LHz4Zsr714PPwcbgirSCiJRg5hRiuOehLoYGY3r765AYGqwCyvfu76Val7Y9OvMqpBkgEFsQUQ6nqgucqtJsgixUjkMQCIgw7VTBRAoGqpkhzzluOvVdL8WRvSzcfBuLHrmF0uBmwICNwDlahU51NJEoiiBo697Fl4AIFCMQ0baagW7eUeQvPria3/+FDZQ6EiYJX7x1CZ+6ZRHFuH1DxpmcS864jyQp8sPtZ4OCCIeRBQ4kdDRBMAWFOAJrAeW4eBujYhoPWC7a8DUWTlQ8uhtEwBVpRYpgG0GTkmhMK8q9UIrrXHfhbVSTIt966Kq2m2cUC8r/3rKQrbtKPPfKA8RFz9p1nXzl2wtIMmmEUbtRBMj5rZf9Ba+6+uNkueNvPverfOjrr8VZEJQpCwwtwAvzaAFBoEAhVkSUYyIG7woTVaRzYCNrbn8PF375d1l+z38Sj+8DVwAb08ocOZFkKK3He8GahD985bv48zf8Cn//c2/iTS/8F9KMtlOMlbse6OLd71nN2/7xVP7nqwvJPW0ZMgB5Dsv79/LyKz4DanBRwque/t/0livknkkCqvQ6VZpOlE6EpgsCVaEQ+R89nBUhtwWMz+je+wBLHvoK87bdga0NgW0M+NvmP6+TqaBRQGgpaQ5nLNrNtRfcArlDTMZLLv8c//7V/0s9LSCibTav0YmaHbsXIjBaLbN/tJ8lS9aDydk1uIRa6jBMUUAoO4QmE2PQDqX5gkAV4lgx5skDxmZ1+rd9n8WP3Dy5opxUplaUy7QbQ44lR2k91sC+kXls3HsKZ53+PQAe2nEmlSTGGaVdCe3PGmVgrIff/eg7ef1zPkStXubfbv55amlMZJXDFB1NJlBSKNMigiCOwIg+ZoPM2xiXjLNg27dY/PBN9O66D/LkUAfTpiweR9a6DxOPd/JHn/wDXnfNR6kmZT74tRtRFUBpriCyyh3rL+LeTRfhFTIPBacohwiUndBcAkWFDlpAEKhCIVKMARTUOHIbE9cGGwP+JetuomP/w+CzyYBxRdqZIhjxWPEorSl2yg+3nslvfPhtKFBwEFtFCVrlfx+vgsjke+UxSo4mU7QAlFrlvFoEQAnmcNDECs6R2YjC6B7mb/oWi9Z/jfLABkDAxuAcs4WhETQt/zezyAkCQKuFTGBEeRKFVgiaSJC4Fb5l0hqmtkAEZxQlmItBExUs5epeFjz8NRZMBExhZCeIAVdkNjIohpxWJyhtKYgdTScRUKCJ0ly45uzvc8MVn2HX/uV88OuvZ6xWxhqCOSazcNaBW7ngy/+AHRoEE4ErMnsJIh5BUYJgRkSOJhOIgJgmSTJhzaJt/PXP/Bod3XvBZhSiOn/26bdgjRLMLWpgyeiDWLsbXC+znQIGj8UTBDMkNjRbk4Mm97Codw8dPXvx1R5IC5y9/CGMIZhjFCESiF0GJmKuEJTIJAhBMCMiQ/NZwNEkkYWHdpzO/euejimNkGcFPvv9G8g9wRwUCZRNDRDmCgVKUiMIZog1NJmCaf6efg9vfv/f8Jvv/1ve+I8f4LN3Xk/BKcEcXAQwSperg86loBFKUscQBDPCOppMwLTCQ2EHRnv57B3XYQRiByIEc4wCkcnocFVQw9whlEwVEYJgJhhH85lWeQLZWYK5HjSS02Frc6yjgYIkCMz0VwUEIWjCtT9B4CSnaOqQC8dFOUQf53OD8BjKkUQopoZC6ojE84QElEkqAPrYzwIKqChPToglQVAUYZoFgTiCIGhQwJFRliqoHBkEhz4fWQgIYISalamC1CmpUfKp8gdLFDUKoiQGcgEz9T4VsCi5y/lObZBNtW3E3gNgVbC5AcB4wXmDUcH6g2UO/djUZ+uFKDdEmcUdfM0doqACioKAF300hIxRSmSIF1QABESZLkHgaD5PEDSjiVYQBXJQD5qDoYDzOVVxJNaS2cmQGImVfSXYV1b2d+SMFXOqsSePcnKXkUQ5SexJo4xarNScklgltZ7MKrmdChvjwSgCgGJQhElGPE48t4449td3gXiekBfIDW6qotzi/KHPcWYppG6ibKNKqaNUjykljnI9oqtaaFR3NaazbqknVTSuQx4juWASAQU1gAGVqfcCoByHIPBy0Us9TXY5cAfTLQhUjggSD6iZLBHAebLOAbRnN653F65zP/0927h26acYLtXY36GMFjOSQoZzKc5mWJtOdSegoqiAoFgFg2JRppoWBBAVBEBpECbpobA7QiQ5Xxw+n021hSA5T0oO/0OPei+H/7wCwmFnaeAFUcGoNDqfqNJFPj4fV+0iGu8hGp2HrXQTjfcRjfbhKj1EI/3YpAAK4gEFODqIlCA4StoKQXMpcCdB8BSJCuoBD6qThUIeQVauYnp2E3Xtw3bvxS7YSLz0IQp92ykVR+gojBAVhzGFCiauYiQn80UiBTNVqIAeHg7CTIkl40vD57GpthgkY8Yc3ZmIB+NB/KH3KpAWkaSAyQrYtIAb7yUeWExxcDHx4BKikXlElV5cpQs31odNQAVo1KH3agCUYE6qt0LQXAzczY8UBALKZOVANvVSBDqHiUpDaP827Mr7KC9eR0fXHsqd+3A9uxvdSsHVKOIxKqgaUEER8FPvMYcCpUkKJuNLQ+excSpomk50KnwA/NRnPRRGWYSt9GCrnUQHQ2hwEcV9KylNVGFwSePHG1W3iIJaUAMqc+gYLqi2QtBcANwFOILgKOIFMvAecgsaK6Y4Sr70YeIV99HZu43SvK24JQ9T7N1BVzxKwSYgHgGMN6h34C2qgkdoYY2g+crQuayvLQHJaHmHh47kIBw6R0sj4pEFFBrdz2KK+1dQ2rOK4oEVuGoHkllMBmqYCiAlmJVGWyFozgLuJHz5WaACGaiCB0Qg7R6Fxesp9m+iOBEm8cp76Vn4CD3lfdjiGNYmRCqQOdQ7vBoUoV0VJOOmkXN4pLoEJKetiYLJD1UeIUkRk5Qp7l9KefepFPespjiwbOLzcqKxTlAmayp8VJS2Fww4mk1JEeohaOYgL6Cg+WRlRWDRdgrzdhCvvpPO075Lb/8GCr27iMqDlCTD5g4/FShkEaQxGS0pfO+KCuSuUQ2iqE3Jy0OMrz7A+Jp7AEEqXVPHbkvo2H4GHdvOojjxPh5eiK2aqW4HkDbteoKkFTqalcD3gCUEs5yABzLIPeRlj+3eg5x6Nx2n3E7vwocpLn2Acu8OOkyCqIC3jdKJ8gizXUEybhk5i4eqy0By5gSTH7aIoKBgR+dT3L+M0t5T6Nx6Dh07zsCN9+CqEQqoa6MFg2Cro9lEE1TqBLOTCqTgFdSBdowhp9xF54r76FjzXbpW3k1v+QDW1bCAZDE+dXgi5iIFDMqc4m2jDpeXhxk/ZT/jp/6A/Zd/HlProLTnFDq3nUV55xl0bD+LaLQbyQUEvANEaUlB6mg2lQSoE8wScsQDkHkBdPWDlFffQ/ncm5i/9Id09OxqrBTH3qBZAX/wtd5BTgAg4W+YR4WP4uMq46fcx/iauyEpNVapO3aeRtfGi+nYfnqj87F1QQ2TZQGUlhDUHU0nVdBx2legAgpkkCn4vmGi+ZspnPtV+k+/je5l99PRtZeCF/zUBpjWOsgQfrQQNIGACqQxEIN4su79DPfuYfi8b2LGeynuW9E4Yut+5DJKe1cRjXaCCGpb4IgtqDiaTJWawChCOwoBk0BmQDoqmDV3MO/8r9B12rfoXfQwHa6KKJDF+EawBNNwnXmgBvKD5QAa3U5l5QNUVq1l7zM+RXEiaDq2nU3PI5fSsfVcXKWMeEGjJm2xBVVHk4moIlRQ2kAgXvAZKJCX69izv0fvWd+k57yvMH/eBuLiKDaL8AfDJS8TnNyts3DMBrVFm6ktXc+BS25q3GLQs/5SujZdSOeW83DjRZCT+dxOIFBxQvPFhtFaDkLQumuqoBkkJcWdeg+dl36KBWd+na4F6ykXRjBpgdw7tNZJRvDUCQYFlOApyqLJEiXp38m+ieDZd8XnKexfTvdE6PSuu7KxUOCqFu9m+o62wAvjcuHLPE2n/JvAz9F6QsDUIY3ALNxG8cIv0H/el+k75Xa641HwDs0ivFqmRxBLzu3jp3DX2GoQzzQJxIPNwWaQxo2g6XnoSvoefAaFgcVIBj4KgTMTBD7ojNJ0CntoHUEm5DloZ43okptYfP5EuJzzVXo792BRNC2Q1ztpG+HoLFADmXm006mseIDKyrXsuep/6N54EX1rr6Z7/WW4aoQ3oE6ZHoHCPqe0At0DQhMFXtAMvIJfvIPOi77I/Cs+2lhHLkZVSErkB4tgJikgBDP/bFfh0UWCofO/ztDZ36G4dzXz7ns2vQ9dSXHfYhBQd+ILBIHucaA0ncouhGYJAZNAVlSiM+6m7+kfZdE5N9HTvwmXOfI8Jq91cvIERjwnSaAGkhKIUlu8kZ3L1rH3Gf9L9yNX0H/v8+ncdiYmOaGNtUDNbocamk2F3YJyEgVe8An4jhR74VdZds0HWXjm1+hwFchj/Jwb6oejs3Cha9SorGOYgUu/yMAFtza21frveR49D18xeawWh8A5XiLsdSI0XVHYU1e8giGYWbngM8g7a5Se/jn6n/FhFq2+nZKtomk5zF5C0AS5myxRRs+4g9E1dzeuvOm/+zp6H3wG0VgBHx3renSQ5exxWU7TeWHYGPYDCwlmjgc/byelSz7L4qf/J/1L7ydWJc+K5HkHrSEwBK3zQHIRUMZXPNCovU/7DP33PJ++tc/AjfeBEDy5cTE6+P8BW01E00mieO8AAAAASUVORK5CYII=) no-repeat 150% 100%;\n  background-color: #2238b3;\n  background-size: 205px;\n  background-blend-mode: luminosity;\n}\n\n.menu__header::after {\n  content: '';\n  display: block;\n  width: 90px;\n  height: 86px;\n  position: absolute;\n  bottom: 0;\n  right: 0;\n  background: url('data:image/svg+xml;utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"86\" height=\"86\" viewBox=\"0 0 86 86\"><title>Beta</title><defs><path id=\"b\" d=\"M-11.704 13.144H125.58v30H-11.703z\"/><filter x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\" filterUnits=\"objectBoundingBox\" id=\"a\"><feOffset dy=\"1\" in=\"SourceAlpha\" result=\"shadowOffsetOuter1\"/><feGaussianBlur stdDeviation=\"1\" in=\"shadowOffsetOuter1\" result=\"shadowBlurOuter1\"/><feColorMatrix values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0\" in=\"shadowBlurOuter1\"/></filter><path id=\"d\" d=\"M.4 16.972h119v28.4H.4z\"/><text id=\"f\" font-family=\"Arial-BoldMT, Arial\" font-size=\"13\" font-weight=\"700\" fill=\"#FFF\"><tspan x=\"37.556\" y=\"34.556\">BETA</tspan></text><filter x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\" filterUnits=\"objectBoundingBox\" id=\"e\"><feOffset dy=\"1\" in=\"SourceAlpha\" result=\"shadowOffsetOuter1\"/><feGaussianBlur stdDeviation=\".5\" in=\"shadowOffsetOuter1\" result=\"shadowBlurOuter1\"/><feColorMatrix values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.140964674 0\" in=\"shadowBlurOuter1\"/></filter></defs><g fill=\"none\" fill-rule=\"evenodd\"><g mask=\"url(#mask-2)\" transform=\"rotate(135 55.44 44.523)\"><use fill=\"#000\" filter=\"url(#a)\" xlink:href=\"#b\"/><use fill=\"#CF3A3C\" xlink:href=\"#b\"/></g><g mask=\"url(#mask-2)\" transform=\"rotate(-45 95 36)\" fill=\"white\" class=\"\"><use filter=\"url(#e)\" xlink:href=\"#f\"/><use xlink:href=\"#f\"/></g><path d=\"M8.5-.5l88.204 88.204M8.5-39.5l88.204 88.204\" stroke=\"#FFF\" stroke-linecap=\"square\" stroke-dasharray=\"1,2\" opacity=\".386\" mask=\"url(#mask-2)\" transform=\"translate(-3)\" style=\"&#10;    transform: rotate(90deg) translateY(81px);&#10;    transform-origin: bottom right;&#10;\"/></g></svg>') top right no-repeat;\n}\n\n.menu__header-title {\n  font-family: var(--text-font-family);\n  font-weight: 300;\n  color: #fff;\n  margin: 0;\n  padding: 0;\n  line-height: 1.5;\n}\n\n.menu__header-version {\n  color: #aab3ed;\n  font-family: var(--text-font-family);\n  font-size: 14px;\n  line-height: 1.5;\n}\n\n.menu__report-tab {\n  padding: 3px 13px;\n  color: #777;\n  border-top: 1px solid var(--report-border-color);\n  display: flex;\n  flex-direction: column;\n  cursor: pointer;\n  text-decoration: none;\n  height: 55px;\n}\n\n.menu__report-tab:hover {\n  background-color: #448aff;\n  color: #fff;\n}\n\n.menu__report-tab__url {\n  font-size: 15px;\n  font-weight: 700;\n  text-overflow: ellipsis;\n  line-height: 30px;\n  white-space: nowrap;\n  overflow: hidden;\n}\n\n.menu__report-tab__time {\n  font-size: 11px;\n  line-height: 11px;\n  padding-left: 2px;\n}\n\n.menu__nav {\n  list-style-type: none;\n  margin: 0;\n  padding: 0;\n}\n\n.menu__nav-item {\n  height: 40px;\n  line-height: 40px;\n  border-top: 1px solid var(--report-border-color);\n}\n\n.menu__link {\n  padding: 0 20px;\n  text-decoration: none;\n  color: #595959;\n  display: flex;\n}\n\n.menu__report-tab + .menu__nav .menu__link {\n  padding-left: 35px;\n}\n\n.menu__link:hover {\n  background-color: #448aff;\n  color: #fff;\n}\n\n.menu__link-label {\n  flex: 1;\n  color: #49525f;\n  font-weight: 500;\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n\n.menu__link-score {\n  padding-left: 20px;\n}\n\n.report-body__metadata {\n  flex: 1 1 0;\n  white-space: nowrap;\n  padding-right: 10px;\n  overflow: hidden;\n}\n\n.report-body__buttons {\n  display: flex;\n  align-items: center;\n  flex-shrink: 0;\n}\n\n.report-body__url {\n  font-family: var(--text-font-family);\n  white-space: nowrap;\n  font-size: 13px;\n  font-weight: 400;\n  color: var(--secondary-text-color);\n  line-height: 20px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.report-body__url a {\n  color: currentColor;\n}\n\n.report-body__breakdown {\n  flex: 1;\n  max-width: 100%;\n}\n\n.report-body__breakdown-item {\n  padding-bottom: 6px;\n}\n\n.report-body__breakdown-item:last-of-type {\n  border: none;\n}\n\n.report-body__header {\n  height: var(--report-header-height);\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n  justify-content: flex-start;\n  border-bottom: 1px solid var(--report-border-color);\n  padding-right: 14px;\n  position: fixed;\n  width: calc(100vw - var(--report-menu-width));\n  max-width: calc( var(--report-width) - var(--report-menu-width));\n  z-index: 1;\n  will-change: transform;\n  background-color: var(--report-header-bg-color);\n}\n\n.report-body__more-toggle {\n  background-image: url('data:image/svg+xml;utf8,<svg fill=\"black\" height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z\"/><path d=\"M0-.25h24v24H0z\" fill=\"none\"/></svg>');\n  width: 24px;\n  height: 24px;\n  border: none;\n  flex: 0 0 auto;\n  background-repeat: no-repeat;\n  background-position: center center;\n  background-size: contain;\n  background-color: transparent;\n  margin: 0 8px 0 8px;\n  cursor: pointer;\n  transition: transform 150ms ease-in-out;\n}\n\n.report-body__header-container.expanded .report-body__more-toggle,\n.aggregation details[open] .report-body__more-toggle {\n  transform: rotateZ(90deg);\n}\n\n.report-body__header-content {\n  padding: 10px 0 10px 42px;\n  top: 100%;\n  left: 0;\n  position: absolute;\n  width: 100%;\n  background-color: inherit;\n  clip: rect(0, calc( var(--report-width) - var(--report-menu-width)), 0, 0);\n  opacity: 0;\n  visibility: hidden;\n  transition: all 200ms cubic-bezier(0,0,0.2,1);\n  border-bottom: 1px solid var(--report-border-color);\n}\n\n.report-body__header-container.expanded .report-body__header-content {\n  clip: rect(0, calc( var(--report-width) - var(--report-menu-width)), 200px, 0);\n  opacity: 1;\n  visibility: visible;\n}\n\n.report-body__header-content .config-section {\n  padding: 15px 0 15px 0;\n}\n\n.config-section__title {\n  font-size: var(--subheading-font-size);\n  font-weight: 400;\n  line-height: var(--subheading-line-height);\n  color: var(--subheading-color);\n}\n\n.report-body__header-content .config-section__items {\n  margin-left: 30px;\n  margin-top: 15px;\n}\n\n.report-body__header-content .config-section__item {\n  margin-top: 5px;\n}\n\n.report-body__header-content .config-section__desc {\n  line-height: var(--subitem-line-height);\n  word-wrap: break-word;\n}\n\n.report-body__fixed-footer-container {\n  margin-left: var(--report-menu-width);\n  max-width: calc( var(--report-width) - var(--report-menu-width));\n  width: calc(100vw - var(--report-menu-width));\n  position: fixed;\n  bottom: 0;\n  z-index: 1;\n}\n\n.report-section__title {\n  -webkit-font-smoothing: antialiased;\n  font-family: var(--text-font-family);\n  font-size: 28px;\n  font-weight: 500;\n  color: #49525f;\n  display: flex;\n  margin: 0.4em 0 0.3em 0;\n}\n\n.report-section__title-main {\n  flex: 1;\n}\n\n.report-section__title-score-total {\n  font-weight: 500;\n}\n\n.report-section__title-score-max {\n  font-weight: 400;\n  font-size: 18px;\n  margin-left: -4px;\n}\n\n.report-section__subtitle {\n  -webkit-font-smoothing: antialiased;\n  font-family: var(--text-font-family);\n  font-size: 18px;\n  font-weight: 500;\n  color: #719ea8;\n  display: flex;\n  margin: 24px 0 16px 0;\n}\n\n.report-section__description {\n  color: #5f6875;\n  font-size: 16px;\n  margin: 0 0 1em 0;\n  line-height: 1.4;\n  max-width: 750px;\n}\n.report-section__description:empty {\n  margin: 0;\n}\n\n.report-section__aggregation-description {\n  font-style: italic;\n  color: #777;\n  font-size: 14px;\n  margin: 0.6em 0 0.8em 0;\n  line-height: 1.4;\n  max-width: 750px;\n}\n\n.report-section__label {\n  flex: 1;\n}\n\n.report-section__individual-results {\n  list-style-type: none;\n  padding: 0;\n  margin: 0;\n}\n\n.report-section__item {\n  padding-left: 32px;\n  background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNXB4IiBoZWlnaHQ9IjVweCIgdmlld0JveD0iMCAwIDUgNSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxjaXJjbGUgaWQ9Ik92YWwtNzAiIHN0cm9rZT0ibm9uZSIgZmlsbD0iIzY0NjQ2NCIgZmlsbC1ydWxlPSJldmVub2RkIiBjeD0iMi41IiBjeT0iMi41IiByPSIyLjUiPjwvY2lyY2xlPgo8L3N2Zz4K') 14px 8px no-repeat;\n  line-height: 24px;\n}\n\n.report-section__item-details {\n  display: flex;\n}\n\n.report-section__item-category {\n  font-weight: 700;\n}\n\n.report-section__item-extended-info {\n  font-size: 15px;\n  color: #555;\n  font-style: italic;\n  margin: 0 0 16px 24px;\n  max-width: 90%;\n}\n\n.report-section__item-extended-info:empty {\n  margin: 0;\n}\n\n.report-section__item-helptext {\n  font-size: 14px;\n  color: #999;\n  font-style: italic;\n  padding: 8px 0 16px 24px;\n  max-width: 90%;\n}\n\n.report-section__item-help-toggle {\n  color: currentColor;\n  border-radius: 50%;\n  width: 21px;\n  height: 21px;\n  display: inline-flex;\n  justify-content: center;\n  align-items: center;\n  cursor: pointer;\n  transition: all 0.2s cubic-bezier(0,0,0.3,1);\n  font-size: 90%;\n  font-weight: 600;\n  margin-left: 8px;\n  vertical-align: top;\n  opacity: 0.6;\n  box-shadow: 0 1px 2px rgba(0,0,0,0.5);\n}\n\n.report-section__item-help-toggle:hover {\n  opacity: 1;\n  box-shadow: 0 1px 2px rgba(0,0,0,0.7);\n}\n\n.report-section__item-raw-value {\n  color: #777;\n}\n\n.report-section__item-description {\n  flex: 1;\n}\n\n.footer {\n  margin-top: 40px;\n  margin-left: var(--report-menu-width);\n  font-size: 12px;\n  border-top: 1px solid var(--report-border-color);\n  color: #565656;\n  background-color: var(--report-header-bg-color);\n  border-top: 1px solid var(--report-border-color);\n}\n\n.footer__key-description {\n  padding-left: calc(var(--subitem-indent) / 2);\n}\n\n.footer__legend {\n  padding: var(--heading-line-height) var(--aggregation-padding);\n  padding-bottom: 0;\n}\n\n.footer__legend .config-section__title {\n  margin: 0 0 calc(var(--subitem-indent) / 2) 0;\n}\n\n.footer__legend h2.config-section__title {\n  text-transform: uppercase;\n}\n\n.legend_panel {\n  border: 1px solid var(--report-border-color);\n  border-radius: 2px;\n  padding: var(--subitem-indent);\n  background: #fff;\n}\n\n.legend_columns {\n  display: flex;\n  justify-content: space-between;\n}\n\n.legend_columns.single-line {\n  padding-bottom: var(--subitem-indent);\n}\n\n.legend_columns.single-line .legend_column {\n  display: flex;\n  justify-content: space-between;\n}\n\n.legend_categories {\n  width: 100%;\n}\n\n@media screen and (max-width: 900px) {\n  .legend_columns {\n    display: block;\n  }\n  .legend_columns.single-line .legend_column {\n    display: block;\n  }\n}\n\n.legend_column span[class*=\"subitem-result__\"] {\n  flex-shrink: 0;\n}\n\n.legend_categories .aggregation__header__score {\n  height: var(--aggregation-icon-size);\n  width: var(--aggregation-icon-size);\n  flex-shrink: 0;\n}\n\n.legend_categories .aggregation__header__score::after {\n  background-size: var(--subitem-overall-icon-size);\n}\n\n.footer__legend li {\n  padding: calc(var(--subitem-indent) / 2) 0;\n  display: flex;\n  align-items: center;\n}\n\n.footer__generated {\n  text-align: center;\n  line-height: 90px;\n}\n\n.devtabs {\n  flex: 0 1 auto;\n  background: right 0 / auto 27px no-repeat url(tabs_right.png),\n              0 0 / auto 27px no-repeat url(tabs_left.png),\n              0 0 / auto 27px repeat-x url(tabs_center.png);\n  height: 27px;\n}\n\n.aggregations__header {\n  position: relative;\n}\n\n.aggregations__header > h2 {\n  font-size: var(--heading-font-size);\n  font-weight: 400;\n  line-height: var(--heading-line-height);\n}\n\n.aggregations {\n  padding: var(--heading-line-height);\n  padding-left: var(--aggregation-padding);\n}\n\n.aggregations:not(:first-child) {\n  border-top: 1px solid #ccc;\n}\n\n.aggregations__desc {\n  font-size: var(--body-font-size);\n  line-height: var(--body-line-height);\n  margin-top: calc(var(--body-line-height) / 2);\n}\n\n.section-result {\n  position: absolute;\n  top: 0;\n  left: calc((var(--gutter-width) + var(--gutter-gap)) * -1);\n  width: var(--gutter-width);\n  display: flex;\n  flex-direction: column;\n  align-items: flex-end;\n}\n\n.section-result__score {\n  display: flex;\n  flex-direction: column;\n  align-items: stretch;\n  background-color: #000;\n  color: #fff;\n  text-align: center;\n  padding: 4px 8px;\n  border-radius: 2px;\n}\n\n.section-result__points {\n  font-size: var(--heading-font-size);\n}\n\n.section-result__divider {\n  display: none;\n}\n\n.section-result__total {\n  font-size: var(--body-font-size);\n  margin-top: 2px;\n  border-top: 1px solid #fff;\n  padding-top: 4px;\n}\n\n.aggregation__header {\n  position: relative;\n  max-width: var(--max-line-length);\n  display: flex;\n  align-items: center;\n  cursor: pointer;\n}\n\n.aggregation__header .aggregation__header__score {\n  margin-top: 0;\n  position: absolute;\n  left: calc(-1 * var(--aggregation-icon-size) - var(--subitem-indent) / 2);\n  width: var(--aggregation-icon-size);\n  height: var(--aggregation-icon-size);\n  top: 2px;\n}\n\n.aggregation__header .aggregation__header__score::after {\n  background-size: var(--subitem-overall-icon-size);\n}\n\n.aggregation__header > h2 {\n  font-size: var(--subheading-font-size);\n  font-weight: 400;\n  line-height: var(--subheading-line-height);\n  color: var(--subheading-color);\n}\n\n.aggregation {\n  margin-top: var(--subheading-line-height);\n  max-width: var(--max-line-length);\n  padding-left: calc(var(--aggregation-icon-size) + var(--subitem-indent) / 2);\n}\n\n.aggregation__details {\n  display: inline-block;\n}\n\n.aggregation__details > summary {\n  outline: none;\n}\n\n.aggregation__details > summary::-moz-list-bullet {\n  display: none;\n}\n\n.aggregation__details > summary::-webkit-details-marker {\n  display: none;\n}\n\n.aggregation__details > summary:focus header h2 {\n  outline: rgb(59, 153, 252) auto 5px;\n}\n\n.aggregation__desc {\n  font-size: var(--body-font-size);\n  color: var(--secondary-text-color);\n  line-height: var(--body-line-height);\n  margin-top: calc(var(--body-line-height) / 2);\n}\n\n.subitems {\n  list-style-type: none;\n  margin-top: var(--subitem-line-height);\n}\n\n.subitem {\n  position: relative;\n  font-size: var(--subitem-font-size);\n  padding-left: calc(var(--subitem-indent) * 3);\n  margin-top: calc(var(--subitem-line-height) / 2);\n}\n\n.subitem strong {\n  font-weight: 700;\n}\n\n.subitem small {\n  font-size: var(--body-font-size);\n}\n\n.subitem__desc {\n  line-height: var(--subitem-line-height);\n}\n\n.subitem-result {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: var(--gutter-width);\n  display: flex;\n  flex-direction: column;\n  align-items: flex-end;\n}\n\n.subitem-result__good, .subitem-result__average, .subitem-result__poor, .subitem-result__unknown, .subitem-result__warning, .subitem-result__info {\n  position: relative;\n  display: block;\n  overflow: hidden;\n  margin-top: calc((var(--subitem-line-height) - var(--audit-icon-size)) / 2);\n  width: var(--audit-icon-size);\n  height: var(--audit-icon-size);\n  border-radius: 50%;\n  color: transparent;\n  background-color: #000;\n}\n\n.subitem-result__good::after, .subitem-result__average:after, .subitem-result__poor::after, .subitem-result__unknown::after, .subitem-result__warning::after, .subitem-result__info::after {\n  content: '';\n  position: absolute;\n  left: 0;\n  right: 0;\n  top: 0;\n  bottom: 0;\n  background-color: #fff;\n  border-radius: inherit;\n}\n\n.subitem-result__good::after {\n  background: url('data:image/svg+xml;utf8,<svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" xmlns=\"http://www.w3.org/2000/svg\"><title>good</title><path d=\"M9.17 2.33L4.5 7 2.83 5.33 1.5 6.66l3 3 6-6z\" fill=\"white\" fill-rule=\"evenodd\"/></svg>') no-repeat 50% 50%;\n  background-size: var(--audit-icon-background-size);\n}\n\n.subitem-result__average::after {\n  background: none;\n  content: \"!\";\n  background-color: var(--average-color);\n  color: #fff;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  font-weight: 500;\n  font-size: 15px;\n}\n\n.subitem-result__poor::after {\n  background: url('data:image/svg+xml;utf8,<svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" xmlns=\"http://www.w3.org/2000/svg\"><title>poor</title><path d=\"M8.33 2.33l1.33 1.33-2.335 2.335L9.66 8.33 8.33 9.66 5.995 7.325 3.66 9.66 2.33 8.33l2.335-2.335L2.33 3.66l1.33-1.33 2.335 2.335z\" fill=\"white\"/></svg>') no-repeat 50% 50%;\n  background-size: var(--audit-icon-background-size);\n}\n.subitem-result__warning::after {\n  background: url('data:image/svg+xml;utf8,<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><title>warn</title><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\" fill=\"white\"/></svg>') no-repeat 50% 50%;\n  background-size: calc(var(--audit-icon-background-size) + -2px);\n  background-position: 50% 1px;\n}\n.subitem-result__info::after {\n  background: url('data:image/svg+xml;utf8,<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><title>info</title><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\" fill=\"white\"/></svg>') no-repeat 50% 50%;\n  background-size: calc(var(--audit-icon-background-size) + 2px);\n}\n.subitem-result__unknown::after {\n  background: url('data:image/svg+xml;utf8,<svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" xmlns=\"http://www.w3.org/2000/svg\"><title>neutral</title><path d=\"M2 5h8v2H2z\" fill=\"white\" fill-rule=\"evenodd\"/></svg>') no-repeat 50% 50%;\n  background-size: var(--audit-icon-background-size);\n}\n\n.subitem-result__points {\n  background-color: #000;\n  padding: 2px 4px;\n  color: #fff;\n  border-radius: 2px;\n  font-weight: 600;\n  font-size: 16px;\n}\n\n.subitem__details {\n  list-style-type: none;\n  margin: 0;\n  padding: 0;\n  margin-left: var(--subitem-indent);\n}\n\n.subitem__detail {\n  font-size: var(--body-font-size);\n  line-height: var(--body-line-height);\n  margin-top: calc(var(--body-line-height) / 2);\n}\n\n.subitem__help-toggle {\n  -webkit-appearance: none;\n  -moz-appearance: none;\n  width: 16px;\n  height: 16px;\n  border-radius: 50%;\n  border: 1px solid #ccc;\n  vertical-align: middle;\n  margin-left: 8px;\n  outline: none;\n  cursor: pointer;\n}\n\n.subitem__help-toggle + label {\n  position: relative;\n  display: inline-block;\n  height: 12px;\n  width: 12px;\n  left: -21px;\n  top: 2px;\n  pointer-events: none;\n}\n\n.subitem__help-toggle + label::after {\n  content: '';\n  position: absolute;\n  left: 0;\n  right: 0;\n  top: 0;\n  bottom: 0;\n  height: 12px;\n  width: 12px;\n  display: inline-block;\n  background: url('data:image/svg+xml;utf8,<svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" xmlns=\"http://www.w3.org/2000/svg\"><title>help</title><path d=\"M5.216 7.457c0-.237.011-.452.033-.645.021-.194.058-.372.11-.535a1.918 1.918 0 0 1 .55-.847 3.65 3.65 0 0 0 .545-.597c.133-.19.2-.398.2-.623 0-.28-.053-.485-.16-.616-.107-.13-.268-.196-.482-.196a.583.583 0 0 0-.457.207.834.834 0 0 0-.15.271c-.04.111-.062.244-.065.398H3.67c.003-.401.067-.745.19-1.032a1.96 1.96 0 0 1 .5-.707c.208-.185.455-.32.738-.406A3.13 3.13 0 0 1 6.012 2c.359 0 .682.046.968.137.287.091.53.227.729.406.2.18.352.401.457.667.105.265.158.571.158.919 0 .233-.03.44-.091.624-.061.182-.145.353-.252.51-.107.158-.233.311-.378.46-.145.149-.3.306-.465.47a2.084 2.084 0 0 0-.24.275c-.063.09-.115.183-.152.282a1.57 1.57 0 0 0-.084.323 2.966 2.966 0 0 0-.033.384H5.216zm-.202 1.634a.96.96 0 0 1 .067-.36.828.828 0 0 1 .19-.287.913.913 0 0 1 .291-.191.969.969 0 0 1 .376-.07c.138 0 .263.023.375.07.112.046.21.11.292.19.082.081.146.177.19.288a.96.96 0 0 1 .067.36.96.96 0 0 1-.067.36.828.828 0 0 1-.19.288.913.913 0 0 1-.292.191.969.969 0 0 1-.375.07.969.969 0 0 1-.376-.07.913.913 0 0 1-.291-.19.828.828 0 0 1-.19-.288.96.96 0 0 1-.067-.36z\" fill=\"rgb(117,117,117)\"/></svg>') no-repeat 50% 50%;\n}\n\n.subitem__help-toggle:hover {\n  border-color: var(--secondary-text-color);\n}\n\n.subitem__help-toggle:checked {\n  background-color: var(--accent-color);\n  border-color: var(--accent-color);\n}\n\n.subitem__help-toggle:checked + label::after {\n  content: '';\n  background: url('data:image/svg+xml;utf8,<svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" xmlns=\"http://www.w3.org/2000/svg\"><title>help</title><path d=\"M5.216 7.457c0-.237.011-.452.033-.645.021-.194.058-.372.11-.535a1.918 1.918 0 0 1 .55-.847 3.65 3.65 0 0 0 .545-.597c.133-.19.2-.398.2-.623 0-.28-.053-.485-.16-.616-.107-.13-.268-.196-.482-.196a.583.583 0 0 0-.457.207.834.834 0 0 0-.15.271c-.04.111-.062.244-.065.398H3.67c.003-.401.067-.745.19-1.032a1.96 1.96 0 0 1 .5-.707c.208-.185.455-.32.738-.406A3.13 3.13 0 0 1 6.012 2c.359 0 .682.046.968.137.287.091.53.227.729.406.2.18.352.401.457.667.105.265.158.571.158.919 0 .233-.03.44-.091.624-.061.182-.145.353-.252.51-.107.158-.233.311-.378.46-.145.149-.3.306-.465.47a2.084 2.084 0 0 0-.24.275c-.063.09-.115.183-.152.282a1.57 1.57 0 0 0-.084.323 2.966 2.966 0 0 0-.033.384H5.216zm-.202 1.634a.96.96 0 0 1 .067-.36.828.828 0 0 1 .19-.287.913.913 0 0 1 .291-.191.969.969 0 0 1 .376-.07c.138 0 .263.023.375.07.112.046.21.11.292.19.082.081.146.177.19.288a.96.96 0 0 1 .067.36.96.96 0 0 1-.067.36.828.828 0 0 1-.19.288.913.913 0 0 1-.292.191.969.969 0 0 1-.375.07.969.969 0 0 1-.376-.07.913.913 0 0 1-.291-.19.828.828 0 0 1-.19-.288.96.96 0 0 1-.067-.36z\" fill=\"white\"/></svg>') no-repeat 50% 50%;\n  background-size: contain;\n  border-radius: 50%;\n  background-color: var(--accent-color);\n}\n\n.subitem__help {\n  display: none;\n  font-size: var(--body-font-size);\n  line-height: var(--body-line-height);\n  margin-top: calc(var(--body-line-height) / 2);\n  margin-left: var(--subitem-indent);\n}\n\n.subitem__help-toggle:checked ~ .subitem__help {\n  display: block;\n}\n\n.subitem__debug {\n  font-size: var(--body-font-size);\n  line-height: var(--body-line-height);\n  margin-top: calc(var(--body-line-height) / 2);\n  margin-left: var(--subitem-indent);\n  color: var(--poor-color);\n}\n\n.score-good-bg {\n  background-color: var(--good-color);\n}\n.score-average-bg {\n  background-color: var(--average-color);\n}\n.score-poor-bg {\n  background-color: var(--poor-color);\n}\n.score-warning-bg {\n  background-color: var(--warning-color);\n}\n\n.export-section {\n  position: relative;\n}\n\n.export-button {\n  display: inline-flex;\n  background-color: #fff;\n  border: 1px solid #ccc;\n  box-sizing: border-box;\n  min-width: 5.14em;\n  padding: 0.7em 1.1em;\n  letter-spacing: 0.02em;\n  border-radius: 3px;\n  cursor: pointer;\n  color: var(--secondary-text-color);\n  outline: none;\n  font-weight: 500;\n}\n\n.export-dropdown {\n  position: absolute;\n  background-color: var(--report-header-bg-color);\n  border: 1px solid #ccc;\n  border-radius: 3px;\n  margin: 0;\n  padding: 8px 0;\n  cursor: pointer;\n  top: 36px;\n  right: 0;\n  z-index: 1;\n  box-shadow: 1px 1px 3px #ccc;\n  min-width: 125px;\n  list-style-type: none;\n  line-height: 1.5em;\n  clip: rect(0, 164px, 0, 0);\n  opacity: 0;\n  transition: all 200ms cubic-bezier(0,0,0.2,1);\n}\n\n.export-button:focus,\n.export-button.active {\n  box-shadow: 1px 1px 3px #ccc;\n}\n\n.export-button.active + .export-dropdown {\n  opacity: 1;\n  clip: rect(0, 164px, 200px, 0);\n}\n\n.export-dropdown a {\n  display: block;\n  color: currentColor;\n  text-decoration: none;\n  white-space: nowrap;\n  padding: 0 12px;\n  line-height: 1.8;\n}\n\n.export-dropdown a:hover,\n.export-dropdown a:focus {\n  background-color: #efefef;\n  outline: none;\n}\n\n.export-dropdown .report__icon {\n  background-repeat: no-repeat;\n  background-position: 8px 50%;\n  background-size: 18px;\n  text-indent: 18px;\n}\n\n/* copy icon needs slight adjustments to look great */\n.export-dropdown .report__icon.copy {\n  background-size: 16px;\n  background-position: 9px 50%;\n}\n\n.log-wrapper {\n  position: fixed;\n  top: 0;\n  display: flex;\n  align-content: center;\n  width: 100%;\n  pointer-events: none;\n}\n#log {\n  margin: 0 auto;\n  padding: 16px 32px;\n  border-bottom-left-radius: 5px;\n  border-bottom-right-radius: 5px;\n  background-color: rgba(0,0,0,0.6);\n  max-width: 500px;\n  line-height: 1.4;\n  color: #fff; /*#e53935;*/\n  font-size: 16px;\n  transition: transform 300ms ease-in-out;\n  transform: translateY(-100%);\n}\n#log.show {\n  transform: translateY(0);\n}\n\n@media print {\n  body {\n    -webkit-print-color-adjust: exact; /* print background colors */\n  }\n\n  .report {\n    box-shadow: none;\n  }\n\n  .report-body__header-container,\n  .report-body__menu-container {\n    display: none;\n  }\n\n  .report-body__content {\n    margin-left: 0;\n  }\n\n  .log-wrapper {\n    display: none;\n  }\n}\n\n@media screen and (max-width: 400px) {\n  .report-body__metadata {\n    margin-right: 8px;\n    max-width: 65%;\n  }\n}\n\n@media screen and (max-width: 767px) {\n  :root {\n    --subitem-indent: 8px;\n    --gutter-width: 16px;\n  }\n  .aggregations {\n    padding-right: 8px;\n  }\n  .report-body__menu-container {\n    display: none;\n  }\n  .report-body__content,\n  .report-body__fixed-footer-container {\n    margin-left: 0;\n  }\n  .report-body__header,\n  .report-body__fixed-footer-container {\n    width: 100%;\n  }\n  .report-body__header {\n    padding-right: 8px;\n  }\n  .export-dropdown {\n    right: 0;\n    left: initial;\n  }\n  .footer {\n    margin-top: 0;\n    margin-left: 0;\n    height: auto;\n  }\n}\n\n:root[data-report-context=\"devtools\"] .report {\n  margin: 10px 10px;\n  padding: 10px;\n  box-shadow: none;\n  max-width: none;\n  width: auto;\n}\n\n:root[data-report-context=\"devtools\"] .report-body__aggregations-container > section:first-child {\n  padding-top: calc(var(--heading-line-height) / 3);\n}\n:root[data-report-context=\"devtools\"] .report-body__menu-container {\n  display: none;\n}\n\n:root[data-report-context=\"devtools\"] .report-body__header-container {\n  display: none;\n}\n\n:root[data-report-context=\"devtools\"] .report-body__content {\n  margin-left: 0;\n}\n\n:root[data-report-context=\"devtools\"] .footer {\n  display: none;\n}\n\n:root[data-report-context=\"viewer\"] .share {\n  display: initial;\n}\n\n/* app z-indexes */\n.log-wrapper {\n  z-index: 3;\n}\n",
+      ...partialStyles
+    ];
+  }
+
+  /**
+   * Gets the script for the report UI
+   * @param {string} reportContext
+   * @return {!Array<string>} an array of scripts
+   */
+  getReportJS(reportContext) {
+    if (reportContext === 'devtools') {
+      return [];
+    } else {
+      return [
+        "/**\n * @license\n * Copyright 2016 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use strict';\n\n/* eslint-disable no-console */\n\n/**\n * Logs messages via a UI butter.\n * @class\n */\nclass Logger {\n  constructor(selector) {\n    this.el = document.querySelector(selector);\n  }\n\n  /**\n   * Shows a butter bar.\n   * @param {!string} msg The message to show.\n   * @param {boolean=} optAutoHide True to hide the message after a duration.\n   *     Default is true.\n   */\n  log(msg, optAutoHide) {\n    const autoHide = typeof optAutoHide === 'undefined' ? true : optAutoHide;\n\n    clearTimeout(this._id);\n\n    this.el.textContent = msg;\n    this.el.classList.add('show');\n    if (autoHide) {\n      this._id = setTimeout(_ => {\n        this.el.classList.remove('show');\n      }, 7000);\n    }\n  }\n\n  warn(msg) {\n    this.log('Warning: ' + msg);\n    console.warn(msg);\n  }\n\n  error(msg) {\n    this.log(msg);\n    console.error(msg);\n  }\n\n  /**\n   * Explicitly hides the butter bar.\n   */\n  hide() {\n    clearTimeout(this._id);\n    this.el.classList.remove('show');\n  }\n}\n\nif (typeof module !== 'undefined' && module.exports) {\n  module.exports = Logger;\n}\n",
+        "/**\n * @license\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use strict';\n\n/**\n * Generate a filenamePrefix of hostname_YYYY-MM-DD_HH-MM-SS\n * Date/time uses the local timezone, however Node has unreliable ICU\n * support, so we must construct a YYYY-MM-DD date format manually. :/\n * @param {!Object} results\n * @returns string\n */\nfunction getFilenamePrefix(results) {\n  // eslint-disable-next-line no-undef\n  const hostname = new (URLConstructor || URL)(results.url).hostname;\n  const date = (results.generatedTime && new Date(results.generatedTime)) || new Date();\n\n  const timeStr = date.toLocaleTimeString('en-US', {hour12: false});\n  const dateParts = date.toLocaleDateString('en-US', {\n    year: 'numeric', month: '2-digit', day: '2-digit'\n  }).split('/');\n  dateParts.unshift(dateParts.pop());\n  const dateStr = dateParts.join('-');\n\n  const filenamePrefix = `${hostname}_${dateStr}_${timeStr}`;\n  // replace characters that are unfriendly to filenames\n  return filenamePrefix.replace(/[\\/\\?<>\\\\:\\*\\|\":]/g, '-');\n}\n\nlet URLConstructor;\nif (typeof module !== 'undefined' && module.exports) {\n  URLConstructor = require('./url-shim');\n\n  module.exports = {\n    getFilenamePrefix\n  };\n}\n",
+        "/**\n * @license\n * Copyright 2016 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* global ga, logger */\n\n'use strict';\n\nclass LighthouseReport {\n\n  /**\n   * @param {Object=} lhresults Lighthouse JSON results.\n   */\n  constructor(lhresults) {\n    this.json = lhresults || null;\n    this._copyAttempt = false;\n\n    this.onCopy = this.onCopy.bind(this);\n    this.onExportButtonClick = this.onExportButtonClick.bind(this);\n    this.onExport = this.onExport.bind(this);\n    this.onKeyDown = this.onKeyDown.bind(this);\n    this.printShortCutDetect = this.printShortCutDetect.bind(this);\n\n    this._addEventListeners();\n  }\n\n  _addEventListeners() {\n    this._setUpCollaspeDetailsAfterPrinting();\n\n    const headerContainer = document.querySelector('.js-header-container');\n    if (headerContainer) {\n      const toggleButton = headerContainer.querySelector('.js-header-toggle');\n      toggleButton.addEventListener('click', () => headerContainer.classList.toggle('expanded'));\n    }\n\n    this.exportButton = document.querySelector('.js-export');\n    if (this.exportButton) {\n      this.exportButton.addEventListener('click', this.onExportButtonClick);\n      const dropdown = document.querySelector('.export-dropdown');\n      dropdown.addEventListener('click', this.onExport);\n\n      document.addEventListener('copy', this.onCopy);\n    }\n    document.addEventListener('keydown', this.printShortCutDetect);\n  }\n\n  /**\n   * Handler copy events.\n   */\n  onCopy(e) {\n    // Only handle copy button presses (e.g. ignore the user copying page text).\n    if (this._copyAttempt) {\n      // We want to write our own data to the clipboard, not the user's text selection.\n      e.preventDefault();\n      e.clipboardData.setData('text/plain', JSON.stringify(this.json, null, 2));\n      logger.log('Report JSON copied to clipboard');\n    }\n\n    this._copyAttempt = false;\n  }\n\n  /**\n   * Copies the report JSON to the clipboard (if supported by the browser).\n   */\n  onCopyButtonClick() {\n    if (window.ga) {\n      ga('send', 'event', 'report', 'copy');\n    }\n\n    try {\n      if (document.queryCommandSupported('copy')) {\n        this._copyAttempt = true;\n\n        // Note: In Safari 10.0.1, execCommand('copy') returns true if there's\n        // a valid text selection on the page. See http://caniuse.com/#feat=clipboard.\n        const successful = document.execCommand('copy');\n        if (!successful) {\n          this._copyAttempt = false; // Prevent event handler from seeing this as a copy attempt.\n          logger.warn('Your browser does not support copy to clipboard.');\n        }\n      }\n    } catch (err) {\n      this._copyAttempt = false;\n      logger.log(err.message);\n    }\n  }\n\n  closeExportDropdown() {\n    this.exportButton.classList.remove('active');\n  }\n\n  /**\n   * Click handler for export button.\n   */\n  onExportButtonClick(e) {\n    e.preventDefault();\n    e.target.classList.toggle('active');\n    document.addEventListener('keydown', this.onKeyDown);\n  }\n\n  /**\n   * Handler for \"export as\" button.\n   */\n  onExport(e) {\n    e.preventDefault();\n\n    if (!e.target.dataset.action) {\n      return;\n    }\n\n    switch (e.target.dataset.action) {\n      case 'copy':\n        this.onCopyButtonClick();\n        break;\n      case 'open-viewer':\n        this.sendJSONReport();\n        break;\n      case 'print':\n        this.expandDetailsWhenPrinting();\n        window.print();\n        break;\n      case 'save-json': {\n        const jsonStr = JSON.stringify(this.json, null, 2);\n        this._saveFile(new Blob([jsonStr], {type: 'application/json'}));\n        break;\n      }\n      case 'save-html': {\n        let htmlStr = '';\n\n        // Since Viewer generates its page HTML dynamically from report JSON,\n        // run the ReportGenerator. For everything else, the page's HTML is\n        // already the final product.\n        if (e.target.dataset.context !== 'viewer') {\n          htmlStr = document.documentElement.outerHTML;\n        } else {\n          const reportGenerator = new ReportGenerator();\n          htmlStr = reportGenerator.generateHTML(this.json, 'cli');\n        }\n\n        try {\n          this._saveFile(new Blob([htmlStr], {type: 'text/html'}));\n        } catch (err) {\n          logger.error('Could not export as HTML. ' + err.message);\n        }\n        break;\n      }\n    }\n\n    this.closeExportDropdown();\n    document.removeEventListener('keydown', this.onKeyDown);\n  }\n\n  /**\n   * Keydown handler for the document.\n   */\n  onKeyDown(e) {\n    if (e.keyCode === 27) { // ESC\n      this.closeExportDropdown();\n    }\n  }\n\n  /**\n   * Opens a new tab to the online viewer and sends the local page's JSON results\n   * to the online viewer using postMessage.\n   */\n  sendJSONReport() {\n    const VIEWER_ORIGIN = 'https://googlechrome.github.io';\n    const VIEWER_URL = `${VIEWER_ORIGIN}/lighthouse/viewer/`;\n\n    // Chrome doesn't allow us to immediately postMessage to a popup right\n    // after it's created. Normally, we could also listen for the popup window's\n    // load event, however it is cross-domain and won't fire. Instead, listen\n    // for a message from the target app saying \"I'm open\".\n    window.addEventListener('message', function msgHandler(e) {\n      if (e.origin !== VIEWER_ORIGIN) {\n        return;\n      }\n\n      if (e.data.opened) {\n        popup.postMessage({lhresults: this.json}, VIEWER_ORIGIN);\n        window.removeEventListener('message', msgHandler);\n      }\n    }.bind(this));\n\n    const popup = window.open(VIEWER_URL, '_blank');\n  }\n\n  /**\n   * Expands details while user using short cut to print report\n   */\n  printShortCutDetect(e) {\n    if ((e.ctrlKey || e.metaKey) && e.keyCode === 80) { // Ctrl+P\n      this.expandDetailsWhenPrinting();\n    }\n  }\n\n  /**\n   * Expands audit `<details>` when the user prints the page.\n   * Ideally, a print stylesheet could take care of this, but CSS has no way to\n   * open a `<details>` element.\n   */\n  expandDetailsWhenPrinting() {\n    const details = Array.from(document.querySelectorAll('details'));\n    details.map(detail => detail.open = true);\n  }\n\n  /**\n   * Sets up listeners to collapse audit `<details>` when the user closes the\n   * print dialog, all `<details>` are collapsed.\n   */\n  _setUpCollaspeDetailsAfterPrinting() {\n    const details = Array.from(document.querySelectorAll('details'));\n\n    // FF and IE implement these old events.\n    if ('onbeforeprint' in window) {\n      window.addEventListener('afterprint', _ => {\n        details.map(detail => detail.open = false);\n      });\n    } else {\n      // Note: while FF has media listeners, it doesn't fire when matching 'print'.\n      window.matchMedia('print').addListener(mql => {\n        if (!mql.matches) {\n          details.map(detail => detail.open = mql.matches);\n        }\n      });\n    }\n  }\n  /**\n   * Downloads a file (blob) using a[download].\n   * @param {Blob|File} blob The file to save.\n   */\n  _saveFile(blob) {\n    const filename = window.getFilenamePrefix({\n      url: this.json.url,\n      generatedTime: this.json.generatedTime\n    });\n\n    const ext = blob.type.match('json') ? '.json' : '.html';\n\n    const a = document.createElement('a');\n    a.download = `${filename}${ext}`;\n    a.href = URL.createObjectURL(blob);\n    document.body.appendChild(a); // Firefox requires anchor to be in the DOM.\n    a.click();\n\n    // cleanup.\n    document.body.removeChild(a);\n    setTimeout(_ => URL.revokeObjectURL(a.href), 500);\n  }\n}\n\n// Exports for Node usage (Viewer browserifies).\nlet ReportGenerator;\nif (typeof module !== 'undefined' && module.exports) {\n  module.exports = LighthouseReport;\n  ReportGenerator = require('../../../lighthouse-core/report/report-generator');\n  window.getFilenamePrefix = require('../../../lighthouse-core/lib/file-namer').getFilenamePrefix;\n}\n"
+      ];
+    }
+  }
+
+  /**
+   * Refactors the PWA audits into their respective tech categories, i.e. offline, manifest, etc
+   * because the report itself supports viewing them by user feature (default), or by category.
+   */
+  _createPWAAuditsByCategory(aggregations) {
+    const items = {};
+
+    aggregations.forEach(aggregation => {
+      // We only regroup the PWA aggregations so ignore any
+      // that don't match that name, i.e. Best Practices, metrics.
+      if (!aggregation.categorizable) {
+        return;
+      }
+
+      aggregation.score.forEach(score => {
+        score.subItems.forEach(subItem => {
+          // Create a space for the category.
+          if (!items[subItem.category]) {
+            items[subItem.category] = {};
+          }
+
+          // Then use the name to de-dupe the same audit from different aggregations.
+          if (!items[subItem.category][subItem.name]) {
+            items[subItem.category][subItem.name] = subItem;
+          }
+        });
+      });
+    });
+
+    return items;
+  }
+
+  /**
+   * Creates the page describing any error generated while running generateHTML()
+   * @param {!Error} err Exception thrown from generateHTML.
+   * @param {!Object} results Lighthouse results.
+   * @return {string} HTML of the exception page.
+   */
+  renderException(err, results) {
+    const template = reportTemplate.report.templates.exception;
+    return template({
+      errMessage: err.message,
+      errStack: err.stack,
+      css: this.getReportCSS(),
+      results: JSON.stringify(results, null, 2)
+    });
+  }
+
+  /**
+   * Register the partial used for each extendedInfo under the audit's name.
+   * @param {!Object} audits Lighthouse results.audits.
+   */
+  _registerPartials(audits) {
+    Object.keys(audits).forEach(auditName => {
+      const audit = audits[auditName];
+
+      if (!audit.extendedInfo) {
+        return;
+      }
+
+      const partialName = audit.extendedInfo.formatter;
+      const partial = reportPartials.report.partials[partialName];
+      if (!partial) {
+        throw new Error(`${auditName} requested unknown partial for formatting`);
+      }
+
+      Handlebars.registerPartial(audit.name, Handlebars.template(partial));
+    });
+  }
+
+  /**
+   * Generates the Lighthouse report HTML.
+   * @param {!Object} results Lighthouse results.
+   * @param {!string} reportContext What app is requesting the report (eg. devtools, extension)
+   * @param {?Object} reportsCatalog Basic info about all the reports to include in left nav bar
+   * @return {string} HTML of the report page.
+   */
+  generateHTML(results, reportContext = 'extension', reportsCatalog) {
+    this._registerPartials(results.audits);
+
+    results.aggregations.forEach(aggregation => {
+      aggregation.score.forEach(score => {
+        // Map subItem strings to auditResults from results.audits.
+        // Coming soon events are not in auditResults, but rather still in subItems.
+        score.subItems = score.subItems.map(subItem => results.audits[subItem] || subItem);
+      });
+    });
+
+    const template = Handlebars.template(reportTemplate.report.templates['report-template']);
+
+    return template({
+      url: results.url,
+      lighthouseVersion: results.lighthouseVersion,
+      generatedTime: results.generatedTime,
+      lhresults: this._escapeScriptTags(JSON.stringify(results, null, 2)),
+      stylesheets: this.getReportCSS(),
+      reportContext: reportContext,
+      scripts: this.getReportJS(reportContext),
+      aggregations: results.aggregations,
+      auditsByCategory: this._createPWAAuditsByCategory(results.aggregations),
+      runtimeConfig: results.runtimeConfig,
+      reportsCatalog
+    });
+  }
 }
 
+module.exports = ReportGenerator;
 
+},{"../report/partials/templates/report-partials":29,"./handlebar-helpers":28,"./templates/report-templates":31,"handlebars/runtime":293,"path":220}],31:[function(require,module,exports){
+this["report"] = this["report"] || {};
+this["report"]["templates"] = this["report"]["templates"] || {};
+this["report"]["templates"]["exception"] = {"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
+    var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function";
 
-
-
-
-_escapeScriptTags(jsonStr){
-return jsonStr.replace(/<\/script>/g,'<\\/script>');
-}
-
-
-
-
-
-getReportCSS(){
-
-
-const partialStyles=[
-".cards__container {\n  --padding: 16px;\n  display: flex;\n  flex-wrap: wrap;\n}\n.scorecard {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex: 1 1 150px;\n  flex-direction: column;\n  padding: var(--padding);\n  padding-top: calc(33px + var(--padding));\n  border-radius: 3px;\n  margin-right: var(--padding);\n  position: relative;\n  color: var(--secondary-text-color);\n  line-height: inherit;\n  border: 1px solid #ebebeb;\n}\n.scorecard-title {\n  font-size: var(--subitem-font-size);\n  line-height: var(--heading-line-height);\n  background-color: #eee;\n  position: absolute;\n  top: 0;\n  right: 0;\n  left: 0;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  border-bottom: 1px solid #ebebeb;\n}\n.scorecard-value {\n  font-size: 28px;\n}\n.scorecard-summary,\n.scorecard-target {\n  margin-top: var(--padding);\n}\n.scorecard-target {\n  font-size: 12px;\n  font-style: italic;\n}\n",
-".tree-marker {\n  width: 12px;\n  height: 26px;\n  display: block;\n  float: left;\n  background-position: top left;\n}\n\n.horiz-down {\n  background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjE2cHgiIGhlaWdodD0iMjZweCIgdmlld0JveD0iMCAwIDE2IDI2IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjcuMiAoMjgyNzYpIC0gaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoIC0tPgogICAgPHRpdGxlPmhvcml6LWRvd248L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz48L2RlZnM+CiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyBpZD0iaG9yaXotZG93biIgZmlsbD0iI0Q4RDhEOCI+CiAgICAgICAgICAgIDxyZWN0IGlkPSJSZWN0YW5nbGUtMTM4IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg3LjAwMDAwMCwgMTMuMDAwMDAwKSByb3RhdGUoLTI3MC4wMDAwMDApIHRyYW5zbGF0ZSgtNy4wMDAwMDAsIC0xMy4wMDAwMDApICIgeD0iNiIgeT0iNCIgd2lkdGg9IjIiIGhlaWdodD0iMTgiPjwvcmVjdD4KICAgICAgICAgICAgPHJlY3QgaWQ9IlJlY3RhbmdsZS0xMzkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDguMDAwMDAwLCAxOS4wMDAwMDApIHJvdGF0ZSgtMjcwLjAwMDAwMCkgdHJhbnNsYXRlKC04LjAwMDAwMCwgLTE5LjAwMDAwMCkgIiB4PSIxIiB5PSIxOCIgd2lkdGg9IjE0IiBoZWlnaHQ9IjIiPjwvcmVjdD4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==');\n}\n\n.right {\n  background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjE2cHgiIGhlaWdodD0iMjZweCIgdmlld0JveD0iMCAwIDE2IDI2IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjcuMiAoMjgyNzYpIC0gaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoIC0tPgogICAgPHRpdGxlPnJpZ2h0PC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+PC9kZWZzPgogICAgPGcgaWQ9IlBhZ2UtMSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9InJpZ2h0IiBmaWxsPSIjRDhEOEQ4Ij4KICAgICAgICAgICAgPHJlY3QgaWQ9IlJlY3RhbmdsZS0xMzgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDguMDAwMDAwLCAxMy4wMDAwMDApIHJvdGF0ZSgtMjcwLjAwMDAwMCkgdHJhbnNsYXRlKC04LjAwMDAwMCwgLTEzLjAwMDAwMCkgIiB4PSI3IiB5PSI1IiB3aWR0aD0iMiIgaGVpZ2h0PSIxNiI+PC9yZWN0PgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+');\n}\n\n.up-right {\n  background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjE2cHgiIGhlaWdodD0iMjZweCIgdmlld0JveD0iMCAwIDE2IDI2IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjcuMiAoMjgyNzYpIC0gaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoIC0tPgogICAgPHRpdGxlPnVwLXJpZ2h0PC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+PC9kZWZzPgogICAgPGcgaWQ9IlBhZ2UtMSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9InVwLXJpZ2h0IiBmaWxsPSIjRDhEOEQ4Ij4KICAgICAgICAgICAgPHJlY3QgaWQ9IlJlY3RhbmdsZS0xMzgiIHg9IjciIHk9IjAiIHdpZHRoPSIyIiBoZWlnaHQ9IjE0Ij48L3JlY3Q+CiAgICAgICAgICAgIDxyZWN0IGlkPSJSZWN0YW5nbGUtMTM5IiB4PSI5IiB5PSIxMiIgd2lkdGg9IjciIGhlaWdodD0iMiI+PC9yZWN0PgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+');\n}\n\n.vert-right {\n  background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjE2cHgiIGhlaWdodD0iMjZweCIgdmlld0JveD0iMCAwIDE2IDI2IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjcuMiAoMjgyNzYpIC0gaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoIC0tPgogICAgPHRpdGxlPnZlcnQtcmlnaHQ8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz48L2RlZnM+CiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyBpZD0idmVydC1yaWdodCIgZmlsbD0iI0Q4RDhEOCI+CiAgICAgICAgICAgIDxyZWN0IGlkPSJSZWN0YW5nbGUtMTM4IiB4PSI3IiB5PSIwIiB3aWR0aD0iMiIgaGVpZ2h0PSIyNyI+PC9yZWN0PgogICAgICAgICAgICA8cmVjdCBpZD0iUmVjdGFuZ2xlLTEzOSIgeD0iOSIgeT0iMTIiIHdpZHRoPSI3IiBoZWlnaHQ9IjIiPjwvcmVjdD4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==');\n}\n\n.vert {\n  background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjE2cHgiIGhlaWdodD0iMjZweCIgdmlld0JveD0iMCAwIDE2IDI2IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjcuMiAoMjgyNzYpIC0gaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoIC0tPgogICAgPHRpdGxlPnZlcnQ8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz48L2RlZnM+CiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyBpZD0idmVydCIgZmlsbD0iI0Q4RDhEOCI+CiAgICAgICAgICAgIDxyZWN0IGlkPSJSZWN0YW5nbGUtMTM4IiB4PSI3IiB5PSIwIiB3aWR0aD0iMiIgaGVpZ2h0PSIyNiI+PC9yZWN0PgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+');\n}\n\n.space {\n  background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCIgdmlld0JveD0iMCAwIDE2IDE2IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjcuMiAoMjgyNzYpIC0gaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoIC0tPgogICAgPHRpdGxlPmhvcml6LWRvd248L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz48L2RlZnM+CiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyBpZD0iaG9yaXotZG93biI+PC9nPgogICAgPC9nPgo8L3N2Zz4=');\n}\n\n.cnc-tree {\n  font-size: 14px;\n  width: 100%;\n  overflow-x: auto;\n}\n\n.cnc-node {\n  height: 26px;\n  line-height: 26px;\n  white-space: nowrap;\n}\n\n.cnc-node__tree-value {\n  margin-left: 10px;\n}\n\n.cnc-node__chain-duration {\n  font-weight: 700;\n}\n\n.cnc-node__tree-hostname {\n  color: #767676;\n}\n\n.initial-nav {\n  color: #767676;\n  margin: 10px 0 0;\n  font-style: italic;\n}\n",
-".table_list {\n  --image-preview: 24px;\n  margin-top: 8px;\n  border: 1px solid #ebebeb;\n  border-spacing: 0;\n  table-layout: fixed;\n}\n.table_list.multicolumn {\n  width: 100%;\n}\n.table_list th,\n.table_list td {\n  overflow: auto;\n}\n.table_list th {\n  background-color: #eee;\n  padding: 12px 10px;\n  line-height: 1.2;\n}\n.table_list td {\n  padding: 10px;\n}\n.table_list th:first-of-type,\n.table_list td:first-of-type {\n  white-space: nowrap;\n}\n.table_list tr:nth-child(even),\n.table_list tr:hover {\n  background-color: #fafafa;\n}\n.table_list code, .table_list pre {\n  white-space: pre;\n  font-family: monospace;\n  display: block;\n  margin: 0;\n  overflow-x: auto;\n}\n.table_list em + code, .table_list em + pre {\n  margin-top: 10px;\n}\n.table_list .table-column {\n  text-align: right;\n}\n.table_list .table-column.table-column-pre, .table_list .table-column.table-column-code {\n  text-align: left;\n}\n.table_list .table-column.table-column-pre pre {\n  max-height: 150px;\n}\n.table_list .table-column.table-column-url {\n  text-align: left;\n  width: 250px;\n  white-space: nowrap;\n}\n.table-column-potential-savings em, .table-column-webp-savings em, .table-column-jpeg-savings em {\n  color: #767676;\n  font-style: normal;\n  padding-left: 10px;\n}\n.table-column-preview img {\n  height: var(--image-preview);\n  width: var(--image-preview);\n  object-fit: contain;\n}\n.table-column-preview {\n  width: calc(var(--image-preview) * 2);\n}\n",
-".http-resource__protocol,\n.http-resource__code {\n  color: var(--secondary-text-color);\n}\n.http-resource__code {\n  text-overflow: ellipsis;\n  overflow: hidden;\n  white-space: pre-line;\n}\n",
-".ut-measure_listing-duration {\n  font-weight: 700;\n}\n"];
-
-
-return[
-"/**\n * Copyright 2016 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n* {\n  box-sizing: border-box;\n}\n\nspan, div, p, section, header, h1, h2, li, ul {\n  margin: 0;\n  padding: 0;\n  line-height: inherit;\n}\n\n:root {\n  --text-font-family: \"Roboto\", -apple-system, BlinkMacSystemFont,  \"Segoe UI\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", sans-serif;\n  --text-color: #222;\n  --secondary-text-color: #606060;\n  --accent-color: #3879d9;\n  --poor-color: #e53935; /* md red 600 */\n  --good-color: #43a047; /* md green 600 */\n  --average-color: #ef6c00; /* md orange 800 */\n  --warning-color: #757575; /* md grey 600 */\n  --gutter-gap: 12px;\n  --gutter-width: 40px;\n  --body-font-size: 14px;\n  --body-line-height: 20px;\n  --subitem-font-size: 14px;\n  --subitem-line-height: 20px;\n  --subitem-overall-icon-size: 14px;\n  --subheading-font-size: 16px;\n  --subheading-line-height: 24px;\n  --subheading-color: inherit;\n  --heading-font-size: 24px;\n  --heading-line-height: 32px;\n  --subitem-indent: 16px;\n  --max-line-length: none;\n\n  --report-width: 1280px;\n  --report-menu-width: 280px;\n  --report-header-height: 58px;\n  --report-header-bg-color: #fafafa;\n  --report-border-color: #ebebeb;\n\n  --aggregation-padding: calc(var(--heading-line-height) + var(--gutter-width) + var(--gutter-gap));\n  --aggregation-icon-size: 20px;\n}\n\n:root[data-report-context=\"devtools\"] {\n  --text-font-family: '.SFNSDisplay-Regular', 'Helvetica Neue', 'Lucida Grande', sans-serif;\n  --body-font-size: 13px;\n  --body-line-height: 17px;\n  --subitem-font-size: 14px;\n  --subitem-line-height: 18px;\n  --subheading-font-size: 16px;\n  --subheading-line-height: 20px;\n  --report-header-height: 0;\n  --heading-font-size: 20px;\n  --heading-line-height: 24px;\n  --max-line-length: calc(60 * var(--body-font-size));\n}\n\nhtml {\n  font-family: var(--text-font-family);\n  font-size: var(--body-font-size);\n  line-height: 1;\n  margin: 0;\n  padding: 0;\n}\n\nhtml, body {\n  height: 100%;\n}\n\n/* When deep linking to a section, bump the heading down so it's not covered by the top nav. */\n:target.aggregations {\n  padding-top: calc(var(--report-header-height) + var(--heading-line-height)) !important;\n}\n\na {\n  color: #15c;\n}\n\nbody {\n  display: flex;\n  flex-direction: column;\n  align-items: stretch;\n  margin: 0;\n  background-color: #f5f5f5;\n}\n\nsummary {\n  cursor: pointer;\n  display: block; /* Firefox compat */\n}\n\n.report-error {\n  font-family: consolas, monospace;\n}\n\n.error-stack {\n  white-space: pre-wrap;\n}\n\n.error-results {\n  background-color: #dedede;\n  max-height: 600px;\n  overflow: auto;\n  border-radius: 2px;\n}\n\n.report {\n  width: 100%;\n  margin: 0 auto;\n  max-width: var(--report-width);\n  background-color: #fff;\n}\n\n.report-body__icon {\n  width: 24px;\n  height: 24px;\n  border: none;\n  cursor: pointer;\n  flex: 0 0 auto;\n  background-repeat: no-repeat;\n  background-position: center center;\n  background-size: contain;\n  background-color: transparent;\n  margin-left: 8px;\n  opacity: 0.7;\n}\n\n.report-body__icon:hover {\n  opacity: 1;\n}\n\n.report__icon.share {\n  background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0z\"/><path d=\"M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z\"/></svg>');\n  display: none;\n  height: 20px;\n}\n\n.report__icon.print {\n  background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path d=\"M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z\"/><path fill=\"none\" d=\"M0 0h24v24H0z\"/></svg>');\n}\n\n.report__icon.copy {\n  background-image: url('data:image/svg+xml;utf8,<svg height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z\"/></svg>');\n}\n\n.report__icon.open {\n  background-image: url('data:image/svg+xml;utf8,<svg height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h4v-2H5V8h14v10h-4v2h4c1.1 0 2-.9 2-2V6c0-1.1-.89-2-2-2zm-7 6l-4 4h3v6h2v-6h3l-4-4z\"/></svg>');\n}\n\n.report__icon.download {\n  background-image: url('data:image/svg+xml;utf8,<svg height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z\"/><path d=\"M0 0h24v24H0z\" fill=\"none\"/></svg>');\n}\n\n#lhresults-dump {\n  display: none !important;\n}\n\n@keyframes rotate {\n  from {\n    transform: none;\n  }\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n.score-container__overall-score {\n  color: #fff;\n  font-size: 92px;\n  font-weight: 100;\n  position: relative;\n  display: inline-block;\n  text-align: center;\n  min-width: 70px;\n}\n\n.score-container__overall-score::after {\n  content: 'Your score';\n  position: absolute;\n  bottom: -4px;\n  font-size: 14px;\n  font-weight: 500;\n  text-align: center;\n  width: 100%;\n  left: 0;\n  opacity: 0.5;\n}\n\n.score-container__max-score {\n  color: #57a0a8;\n  font-size: 28px;\n  font-weight: 500;\n}\n\n.report-body {\n  position: relative;\n}\n\n.report-body__content {\n  margin-left: var(--report-menu-width);\n  position: relative;\n}\n\n.report-body__aggregations-container {\n  will-change: transform;\n  padding-top: var(--report-header-height);\n}\n\n.report-body__menu-container {\n  height: 100%;\n  width: 100%;\n  min-width: 230px;\n  max-width: var(--report-width);\n  position: fixed;\n  will-change: transform;\n  left: 50%;\n  transform: translateX(-50%);\n  top: 0;\n  pointer-events: none;\n}\n\n.menu {\n  width: var(--report-menu-width);\n  background-color: #fff;\n  height: 100%;\n  top: 0;\n  left: 0;\n  pointer-events: auto;\n  border-right: 1px solid #dfdfdf;\n}\n\n.menu__header {\n  background-color: #2238b3;\n  padding: 0 20px;\n  height: 115px;\n  line-height: 54px;\n  color: #fff;\n  font-family: var(--text-font-family);\n  font-size: 18px;\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  align-self: center;\n  justify-content: center;\n  background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZoAAADeCAYAAAAEuMatAABPH0lEQVR4Ae3dBZyk1ZX38d+59z5PSfv0uMPg7iQB4oRkIbrZbGSz7Js368mbrLtE1903nuzGVuIGgQgxCBbIAAPjru1d8sg9b091fxhBMsN0T1V13+/ncz5VNRaFP+ee89zi8QRBEARBEARBEARBEARBIARB0BTZuru7Be0HOoFYlcirAqgIiaoMGKtbAaWNBYFkD9/JzAuC4H3/57KeZKB0Wb/jea9YVrowFbNC0KVAj4Cr50olzTACCKCy11j/QLG/tlmEb5bXp18GdtFmgkDSy3lS7qxzLKsWzSfVcWCMIAiOzxfvuIZV3S+lp/hSUk5DYSzzCIcIkORKJcsQDqOg3gBgUj8qyP8YlU8DnyEI2oTsfc4ynkjvm65eaJ9z8cuMdQvwmoCpgAwiOlH2AERDCONABZUxICUIgob6R7/ynOz7D/+yjlRuEE8BryAccixBczQBUXCj+Xc73difhcAJ2oFUn1PmcV19dnfxZ1/yM2CXkucJIEeWGsCD1IAKSBUvIyCDGD0AphFEUz9fb9QcEATZvetPSd/7uT/2ef56CpFgDIecYNBMUSOAYES+YJ37XeA+WlQQyN6Xr+bxzP+XX3upJEPPIPdVnpwB5LBXEBQa5UHGgFGQUXIzBDIETFakg6B1IGMWCILq337+NfnajX8lxcISjDBl2oPmcCIMl7fXfh/4R1pQEMjwhX0cLfq5a1eUXn3lGxgZtSCeE2MeLcEAOZABOaIJyDAwAAxh4iFERqaCaQzrhwGlxQWBfv3+qP4/t/xZVo9+RZxhykkJGgCNDEZ4rxV5C1ChtQSho1nO0fr/6NWvMPN6Lif3dWaGABzVCZmpSoAaUMdSRRgGBkEGcbof0RGgAlIBMoKgSe5ev6KcRoVC18Z1C0758Hve7p39SZwBOMlBc4hFPmeM3AgM0iKCwEmqHK7nRef1miXzz6JST5k5etir50gCFIAiOX3AUkBAIUUQqaIyBhMlOlGMAIMoQzg9gGUMJQFSQDlOQbB7sLsDm5f/4+tXdW7b1rM0sqxQdInAotTbRV+9/8z+pEp3LnQV8aU/tz+39PSC70kkotly9MWo/9SY6XtNWIVuFYFsePHZHG78rTc+/fxz6jdQI6U1CWAOKwV8o+RgSRUYRhhBGUAZQnQIzBCqw+BrIYACgA994+m9Y2OF00yUnXLHnlWrvn3XmhVdsEpFVwBLKnWzMMuwIkxSEIFyQfEiGFL+2Pw6L+E/qVFGEYCmdjQABWrcaZ73wG8W/uPm3fPjnRbdirLJCI+IMAgoJ1EQyIpfyJnCaZ25fOqtf/36nrR6FpiE9iOPE0QeSBBJUFJEx0GGEBkE9qN+GBgDxhEZAZRgVvnSHWeVXSlddd8jq9d89vbzzi0V9FxVOXvTvu4VWUqXCOWCgUIBlENElMejQBXhzfLX/LK8m+qhkGmJoBGUAhU+ZH6Pf+U36aChKjAK7BR4QIW1ueUhYKsxbAH2MUOCQC55hTKFC/vW9r3/Tz76C4wXSoBn9pAnKIOQg1SBg1UBHQUZRBgAOYD3gwg1aFRK0NLW7Z7fsXt/39L33HTVeXGUP21guPviezb2L7OWJc7SFztAaYic8lRUEJ4nX+Ov5KcxeDIcQMsEDYAhx+L5Q/cRviYvpIjyBKoi7B4fZHNW415V+a6HtcBuYIBpEASy6gplCl/9t387/7SF219LEtWZO44MHhAmKQ2SIkxtwukoMAIyBAwjcgCfDwFZo0664LaHTu/64t1nnke9+MzP337uBanlTIFzvacIYA0UI0WZHhlCHwO817yMNTxEjRJAywUNQEyNHXIab7Zf4YD0EaE8EWMBYQqKss5Y1lq4R+EbKfJDYATwBMFxcvNW8ah0xK5iIcrcolMFkPMYKih9QD9gaNAcyFFyRGogI8AwRgbI/QDCKDCKyBAwzrQJ/ufWS1099pd86juXPX3z7r6nZXn89OFxWWYNrqOoOB5LmT4Z8Fp5L2ewlgqdtLKEIqv0QV6tf8/fyR8R8cR8zuEEOMvnnJXCjwO5ovtR7rQi30G402V8BxgjCI6BXH6DAvCMNevkz3//Yz8fj8kKkJTgyQjA46xoWwBEEqAO1EArIMPAIEYOkPv9wDhQmXr1BE/qextWnvuhW64+f/POeS9Yv2PeVRksLxcoOwMIGFFOhgThNDbwPvNiuhgmwwG0bEcDYMmoSwe/aG9ho5xKAWUaeOPZauCutCZf3rOOO1S4H1CC4HHIolUKwOtf84P+v/iNj7+RkVInkBNM53Hc4T+mIBXQcZAxiMeBySCSxkzoAFADkkbNQVv29/R84JZrTt+8e97131q79PkYLoosncZA7JRmUIQa8LvyB/y0/DPjdAK0fNCA0MEwn7Rv4S/MuyijTBcVEEAMVRV+oMLtqvIZ4F5gkCCYIhf8uALwyivvOeMPbvyv11MpekCZSYE5VCqAAh5BQXJgMnxERlAdBIanPg+hfgDVFPDMIg9tX1z87D1nPfPOh8684f7Ni5/rvZwLUC4orSBBWM5OPmReSB8HyHBtEjQQkTDAYt4U3cxOlhChzLBHVLnVeG4e7JObgRGCOc2NlGm48fnfWUg9ioAaMy3wUwUIj1IABOgCelA1gAFyIJ0skyCMggwh/gCqEyUjj86FoEKb+M7Dqwp/d8fVz9r24MIbSAo/tmsgXl0uYouRAkoryYBnyK0sZCdVOmgnGRGL2cIl+nU2y2uImHGni3A6lp/vqOpWb/nynsXyhTp8ExgimHPcvnk0dEWVhSCeoNl0qjyHSKNUC0AR6AFdgWJABEhAqqBVvKsiZnJF2+T7yQr70KgC1BBt+rU9P9i+oPjNe8666LaHlr30we2LXpqmnB1FIAK9nUorUoQIz/Pkc4DQbhQhw/Ec/2m+Yn8SxSAoM02BOGEl8HOnbNSf8ykbxg9wi4p8TOD7wDhzQuCWfRdWnlkR63QB4AlakR56fVyCahnoQFIBBAAPmBRE6iBjiB/BuxGIRoAhxA+QR/ungigDUmbIr9/03FOHHu77iVsfWPby2HIlQCGCUlFpdSmwWrazhnVkRLSjjIg1ej+djDBML5aTQ4WGXIAiazpXsgb05wTuxvNFKcgnpcBawDNrBXL5jcrPveBrpTdee/P/Y7wYFgFmJzlsJmSYlCPkqOSIVEGGER3EuwF8NAwMoYxQYBio8xR8/BsXlbfumn/DR799/ktG6/bFkdJdLihKe6kgvEBu5m/lddQoAgLQNjMaAINHEd7t3sPNcgNFlJYg1FX4qop8PEr57Oyc5wTOD0BhWHuBCFBmo0CBfLKERykCWFS7QHtQVkNisIkH6igJSp2IKsgAogOo3U9eGAYqCOPAKEf59HfPP+O9X73iVQ9unPdTKGd2laA7VgCU9mOg8dyMJQOEduQxdDDKGl3LFxtB0yKUgijXC3q9wmbj+S9V+ShwL7NG4Mgh7ZBefAiaNjQDt2jLoQCCMjllMuYBy0EEvEGq+VTQVIARjB1Mk6jy/YdPWfr7n7/2RYPbup9jnfbP68ypp5YkAxHBCIiAESaJIrQ2RXDASjbgsbQzwVPWYQytyRtW55bfENU3E3MzVt6rwteAUYK25rIesOQ9gANSggB0qiYJRxOMdwhd1Wph2cM7F527bseiC/YNdS99zXn34s8Xxusx47UCo9UiY7WDFTNaKTJUKTFWLZB7g5+ozINXEMAYpoJIaSUGWC5byLG0s5SYFWygm4wEh0VpNaIAFKnzYtCJ4h6FD8WpfBTYR9CWXDQEL7/qni6qBfukQRMEAhifgeZDo539D25dcvEjuxZfPBEgfaA46/EIAnQV6/SUapj+IUDJvWlU5i311DbCZqRSYvhg8NQKVA4G00SNVYtU6hGqNCiThEPdkIgy01KEjEkCdDLMyaAzfHzWowcQlCpgESLAoTwV3gtJDtZAZJVpJTQIXHywfKS/Jrl8RHM+DKwjaCtyyQuV7/7du14ex/kVQJ2jBYGgGJ+j6IGhrkVrty29aP2uhReN1wpdzipGPD+KAIgCIAKCYmSyEEgyO1GOWhJRT10jfIbGywxO1ESQUU0d9SSi2vh5g+pU6BxeAJx4ECUI8xnkeXyOpbINUF4qH6OL4RntanJVMq/MFEPOKPO41byCGmU2s4Zv6I8xQgcRyvHIvFBwGacu2sS+4QXsHemd+VsbBIADAh8zKv8KrCVoC/K6X97p3vuWD766GKVnAylBcDibZ6joroG+ZfdvXnbl5j3zz03SqGBNjjHK9DgUPiL66PsGARTqWcR4bbLrqUzUwSAaHi9PVImRarERTlluSHNLltMgAuY4u6AEYTWb+DPzc5zH9/E4QKhRQhFmmjBzFMHgialiUIScb3Mtv+3/hSH6j7mz8SqU4grv+Mm38dwLb2HrvlX84cf/mLs2nk/BKSfJuCqfVZW/B75H0NLkNW/a2fH+t3zotUVXXwmSEwQA1mcounugZ8n9m5dfuWnPgguT1EXOekSUk01EMVMloiiC9wfL4FWoJlFjHjRSKTJaK07OhyZq4nPjOC7NLKrgG3XUUZxRBFCEFOWP5Dd5lfwb4/Qx23UwxN/r2/lnfQsllGNRqQsvvuwW/vINb4U8gtIoX/r2T/LWD7yTcgFAOYkyVfkE8A/A7QQtyWW5iVHKIEoAhIBBNN83ETD3bFz19C17+8+vp67gjCdyOc2iKuQHiyMJYIzSWazTXaqxfP5kGOVeGt1NmrmJspMdUKV0MHgar9WDnVESUZnqkNIccqBb6pxu7ieVInAokWYrT8xF8j1U38KxEgOj1S7II3AJoAxXulGawono64BXCHxCkb8FfkDQUuR3fmv9oj964yd/puDSMqDMTYFRj/Hp0Ejn/B9sXHHlwzsXX1JLXCma6mDakYgicMRxXONVlCw31NKoETjVg4GTxAxVyoyOxfxK9Te5OPkmtawAaQZpDl7BCFjDbNLBKJ/hdfym/wfKKMdCAVXPG5/3YW648lNs2H42f/6ZX2fX4AKcVZpsTOAjxjQC52GCliB619+eyryR14I65p5AVLF5MjZe7vnBppVXrtu2+NLxetzlrMeIMlsdCqBDCwkopMScyoM8N/8spl4nq+ZQT9BaSj5Sw+8agiwHYQYICDPKyKH/XWPqjNHFm/3HuIOnUUCPq8OsZ1CMlCQTjNAImRYyCLzHO/kbYDdNFYhu/aOLyM0NgGOOCQHj06QeFddtW3reDzYvv3pwrGOBszlGlLlL8Ahnyf2ca+6lJFUQQUUoZGP4u9eh1QREmHbqwecgwkwQYLxeppoUEFF2sZz/0F/kq/pCIkBQjpeqtHTHK45NQzvkr8cH+AAwTlMEolvecTXePwewzClhk2z9jkVn3b1h9bP2DXetEFGs8QQAQoajSIUCdRTwavixp9/N/Pm7IQcQpp/C4B6ojoIYppUo2Ix//tKNfPS7z6cjVoboY5QCRQCU2cpYEOFuQd4BfJrjpB30qNIjUGRSAowCBwiOiejmt12HckUImjkyhxGf7RvsWXz3+tXXbNi94CJVcNYTPJbHoAgAuTe86nnfY9GqPaAWVJl2YiCrw96tkCUgMu1B8+7/eTP/dut1dBXAABZlLokyPm28vAu4kyfgu+jP+rjCC9dV53GONSxVZZ4IJSbVUYaBnSiPqHKLV24DdhI8LgdSBhWC2UsAmyfVaqF8z4aVz3pg67Kn1ZKo7GyOGJ5AYPAcouA9ZB48M8SDRFDug4HdIExv0KhifUYMRChzURLxMtDnCfKP3vCXwABTagu4uL6AnykrL1FhNYA9+iraQxYBZwDPFvhZa9iP50uqfBC4leBxg4bZLTwPs2H74jPvePjU5+8f6Vwa2ZzI5bSiQKHcA2NDkNRAhGB68xboAv0d43mJyeV3h5awTubzK7HwUx05HQoIx0mZj/B6EV4twmeBdwL3EjQ4lCIgBLNx2J8MT6wr377u1Gdt2LXgYo+Y2GW0sEAVrJ0Mm6QKCDMgUMBzbjqPTxfnMaDQjwflhEUKP64511W28Bfe86dAEoJGNEYJZhPjM/XG3Ld+9ZV3r1/17PFa3OOsx4knaJOwKXXAqIN82rfQAgUMZIuFvB9B6UeZPgpi6Ow6g7chXK7KzwK753jQMMuCJnQxBwa7F333oTXXbtk7/xwjvh2PyULQRAVwMeRVgukPmXQZ+D4gZ8b4FARu8I7Pbt3OK4GtzFEOJAYlaP9ZjM+NTHQxT7vnkVXPHU/izsjmBG0sLkK9AgjBNBHIlgq+F8hPUq55Ll9R5n+yUW4A9jAHORQHCG0tPHg5ONzV/50HTnvBxj0LznPG0+4hEwi4AiggBNPBQ7ZEyOcBOSeNerC9XObm8T6ElwEZc4wDNQTtyfgcFX1w87KLvrduzbXj1UJvGPbPEgJYByjBNPDgeyCfD3hOOvWNul7ht4B3Mcc4wBC0H5cnlWqh4ztrz3jBup2LLjNomMXMNiKNOkGBAhFkiwQE8DSNgd/0yufn2g3TDhFBlaBNiHpsnm7dvXDNtx847cf2j3QueZIbloMgUMjmgZaAnKZS6Eb4A+CVzCFhPtNOrM99buWuB0951l0bVj0nz20cu5xgFlLA+xOd0QQKWgDfJ+BpCQIvF8MzgW8yRzi8KiC0tsBlyeh4ufub9591/cbd88+LrMfZnGAWy1NOTCAKebegMa0UNCaHX8wM95cMw4CfC0dnHlVDSwvPxmzbveDU2x444/qBkY4lc6GLCRTSOgjBU6WgFnwXLUUB4/mJ2PNMEWoC6wVuQ7kF+C6zkAP1tKbAaK6ov/fh1U+fuKfsusybeI4M/AP1kNQAIXjqNAZfAjytxgJLMwWFU4EXAG+zwje85W+AzzGLOIQcpQWFBzBr9Tj+9trTrntw29IrrPU445kDAjFQG4P8hL4qINCpkLGAp2UJjzJeeY5mPAflw1h+HdjHLOBAUtBwsWYribL64GD3gq/df9ZLdg70rpljXUwgQHUMcg/G8AQCzyHCIXroVUtCuxEA4adRzspyXgVsoc050DrQRcsI85ituxas+fp9Z71spFrqD0/4zzEikNahMgJGCB6HBwxoGXwn+JKgEQ2iQB1MVbEjoBHtS7kisnwmHeF6YAdtzKEkBK0RMqLZ2k0rLv3W2tNfnHsTz8WQCQRGDkCeghiCo3jwXZD3C74TMBxBATrAi5AnoAJ42pfhQp/wntoOXgYk7dzRJDRXYHyuKnz3gdOfc+/Glc8VVKzxzDGBMVAZhfFhQDhMoIBAtkjIFwAG8I16QuoApa1pDoWFvKi0hJ8F/ok25RBTQb0CQnDy2TxLkjj6+n1n3vDwzsWXOBOe8p+TxEBah6E9oAoiBEeFzFIhnwco4PnRlFlBc8hzfgvhk8C+dn1gswIoQTNCJh2vljq/es85r9i6r/+MyOUISvMEXs0T/7gIGAGEaSUCSQ0GdkKegDVMOwGMoGLIgRzhaAYQlJajU7cu9wM5c9UK8bwC+DfakEO0gtIE4VLM/YM9C2/9wdmv3DvUtaz5ty4HRjwFk/B4cjFInkEtA2+YPgK1MRjci9brIMcwVJCncDONKNiMOB2nm1E6UQ5RFKFGGY9tzVuX+wHPnKbCq9s3aKwbJ8sITiKX1Xbt61/51XvPeeVIpbSg+evLQeojTu3cwrVLv0nuHQACIAKAihDtSknv2AtpBiJMH0VrCdkdD0Oag/Ckqt6TAsLxe3n9d3mReRuS8ShLTpUyb7cf4CE5nwilZRjI5wtICBrgfIFlwI42DJp0hEyU4OSIstq2ifXlm+8991XVetzd3PvKAhFFAGs8TnIilxIhAKgCuQfvIVd8LYPUQy4gTB9jIFG0kkGW/8gQ0zTDe0U4fmWp0SEKemTQVOjAkaAt2M34MuAJoFuFs9oyaP7s6y8e+ZUrv5LHJo0AZeYELq9v3Lb43FvuO+fHk8yWQsic1AfgAMXIZIkoAGnuqGeWSlZmODHQMUYuRUgydKLIPZr7Q4EjAobpZQSt1EEVjAHhyRnhqfJYHkvIcShCSxHIOwED5AQQAYtpQ+62HSuSt6jUgRKgBDMWMg9tWX7xN+4/4yW5N4Vwncz0E0DMkUHiveD1YBnSzDFSLTI6UePVmNFakZFKkbFagX3VLq5KDvATV27Gz18GWU6DMEVAhBkhgg6NQZaDs0wJDGhJwDMlUKVIG3IlmyagM3p7X3jaP68/sGnFZd/44ZkvQ8WGZ2ROIEhEpwoE5VECWW4Yqxao1GOqScxItcBwpcToeInhaolqPSLNLVluJkoeveHFCFQF8B72DcHC5WCEk0IEkhS/fxiMcJhAAMdhAoE6bcgZpA4yDgjB9BIU45O1G1de/s0fnvFSBGueNGQCEQA9LFR4tEPJvVBPo4ly1DPXCJShgyEyXn60M6klEbXUkUxUmoMRkEPVIICzekTzYBAwAjsH0NU1pFQA75lx1uB3DaDDVTCGKYGCGlDhkCATwx7akJNIEzSvAIZgehmf3rd+1dO//eDpP4aoNaIcEkwFCOZgGcWrkOV2srw5GBqNIBmplBrHXaO1AuMTVanFjNdjkswggB7R8RyqQqTHP5Sv1sk37sadtwoEUGaOEbSekm/YxWMF4kEAJUBAc4aTAzxAG3J33r9UudZVcLkhmM7LMev3r1/59G89ePr1ImrmYMgc6kamCkBVUAWvQiWJGKsWGas3AmSi4smjrkZ3UqSWOLwKuRe8BwWMHOpSIqtMO2PwW/fh+7swy/ohzZkxIuTrd6JDFXCGwwQCKJADjkBAPWtdDztoQ871wKc3Xjj66gtvU5ICJygQYGom860HTn+xiIoRneUzE4DJV+HQ5zS3jNViKvVCIzTGapMzk6GxcmMoX6nHjV+TZpYkE1TBmENBIgCiWHOwTmI6eiX/4VYoxpj+rukPGxFwQr5xD37THp7gP1zgQaqgRUDDkXJ9Fx+nTbn6Ltj5YHGIyzUlOeFONbB5/cGpwT+CGNFZtBoMcth6cJabRkjUs4gks42OZGi8fLAaQVKtxxMVUUliaqnF50fNSmSqgNgpLcPI5AOUd2/AXXQKZn4P5B5UOWFTKZpv2E3+4DYahODxKJhRxfdJyFzYVjX8D23KVQ2YNX6ElAxwIWhO7AvL1m1efuFEyLwUsEa0PWcmRhEUr2bq2MqQedvoQEarxcbcZOJ1alZSaAziD75muQCgOlkcdszljIKhfdjJeU1253rsmcuxKxeAtZDnPCUiYA1aT/HrtpNv2QcCiPAEAgN2DPIaaBHwzFmJ5y86lrOXNuU6lkOn+CGYCpqnLDzxv2nH4rO+PhEyCq4FQ+bILS4UAAUEIVeoJVEjMMarxcaR13C12JiXTA3jSb0hb6wGG3IPHD4zMYo1yqxiDGQ5+Q83o/uGsWuWIH2dYAAPqAfliclhSZvl+O37G52MDo+BtRyDIAV3QEmXC3ORCkSGr3QY3kMbc/McfHn9paOvPueu8c64WsIbguPksmT77vmn3nLv2a/Mc1O0xrfMarCgGFEUoZ5Z6knUCJTqRI1MBcnkcVeJetZYC278fJo/9u+VwiTTCBXmBpFG+d2D+P0jmAU9mGXzkJ4OpBhDZEAB1aN+D5Bk6HiC7h/B7ziAHxoD5XhCJrBgBsB2QN4H5MwdBiTlIfH8dAI12phLKvBIpS9PE7ePAguBnOMSrvrfvb9/2c33nPeqehp1nKxrZUSOnJmAkuWWqQcSG6ExVi0wWikx2JiZFKgmcWNuMvm8iUUfM9g/9HrUzCSwBrzH7xpolJQLSFcJKRehFCOxo0EVradotY6OTdRoBerpVGIbMARPgdulqBN8F5Az+1mQcYh2aJlElgF7aWNO1tNQNPk+wAIpwTGHzPBoZ9/BTqZSj3uczWfkKXgjCnJoNVhVSHLbWAkeO1jVIsOVIqPVwkQVD1YjVLyXRuUKqiACZqqcVYKnMmcRALSaoON1GgwgQoMCKPjD/gt3lhMQCJBBtFVJlwq+F9BGzT4GUDAD4HYrkrIS0f8GuRbY2L5B42lYu/b0PZc9424lNxyDwPqsXi8Uv3rv2T8+0TEsjI4zZARAjlwJFhQEBMi8aWxs1dJoagg/GSgjjWF8ufF5qoMhzQzZYU/BGwMCMDXYNwTTbSqxn3hNzxBMJwFyiLYr+biQ94MWOURpX8IkDzIG9gDYEaXBAHCqop/0yHXAAdqQy4WG/3rw9AOXPev2cepRDHieWGDU+8zYiZB5xc6B3tWxy580TI5+eNFMXaeSZo4ks40aqxcad3KN1A52JxOv40VqU9etVBtPwYPIVHHke2OUgiEIZjehwR5Q7DD4TvBdgi8CBlRAaDMeJAVTBRnVxiseMBxB4FKDfsyovAyo0GacUxpKqe73uR01sOBJgiYQnYD/+v1n3rBx98JzYpcBIKJHBIki5I3trIMlVJP40WtURiuNMGmsB1eSyZlJNXGgNCgcCieOuE4lCAID5GCGwAwrCKgFDO1FQXLAA8ok06jHJXCtyfWfXcYbAE8bcXFdAfjy7efVX3vdyr1nrN6+iCTiMQJB1OCy2n0Pn3rV+p2LrywXEqZQSx3j1UIjOMZrcePZkpFqibHGVfSFxtPxuZep4AFVMHLYMb5RjlEQBDJVUySjPcmhOhaZ48aJ2gC8gzbiakUeFTu/Gbhgzv9fVxFQAcyhH9c6pfruu3549iWfvf3C68brMaNTYTI8PvnAYpJaktyRpIbcHxkkIjSIKJGlBQUGD9SJKfB4shxUAWFGGQEB9InHgxOlyFN+KJcjWHIyHIKnbQlzgtDwNlFZD3yMNuGMClP4yNev2PIHr/9Uwuy+NFWYZB4twQAZUAcSIptgzSjKAdBBusoH6JAD7/i7Z57xpW+f87bhasEAiDz+avDUFfRBGxGgoh3s4HQqdPAYCj09OZEDVWaMAEkqZDlPqFY2pIBwfERgvOao1CKMKFMw5NQoUaMDIWgDoqLvSXLZCnybNiDn3KBM4fzVO6OP/+E/vpGx4nKElPZ2eJAI4B8tLznoGCoDGB0hZQjPCDCI6DAdpWEg5TB37Ty9+6ff+fJbNOOyMC+ZvQTlaKqQ5cJvvWGQs9ck1BNhpkRWueehIht2uCdZQReeCmeVBzYWeHBDTOR4HErQVjYoPBvYTotzh1+1/tC2JemB8d4t/XZ8Jd7S4g4dbSkCCJMEUESqwDDoOCmjwADIAIYBhnSIROuIJkDO0QarHO7OTSvNL3zs+vdIzmVxCJlZzWM4mgLWgnUCGBBhxhgFDNooZVoJGAMKqADa5qdPwRoR3q/Ii4E6LcwhwuE+e9OlP/yZF996pdStAErzCMBRQWIapQCSAOOI1ihQRWQYZB/4ARIGQSvAOGiVCE+D0tDPcfm/7/6pt2d59Kq5+7R86GjiWImsoqqgzBxVQJGpmk6C4owigKgSzALKtaL6V8CbaGFOvHK4j9125fb/88LbtiF6CirpSR7EW8CACkKGkjZeY6kBQygDCIP06jCiYyhDGB0FasyQt7//xT8+Xo1+p7usKHNRoAjOeVwjaGhbChijiBDMJsIvA3cD76dFOYQj7Bvt8A9tX37XWadsOYV6NF1dzZEdCQqgh90AmYKMgQ6BDONLI8DBGsDoAGUdATLAA5BwUrzjo88/7QP/e+k/9HaqUdpLkgkCRE45QYFC5MA52p41IKJMJ++FegJewVmIoxBmTfCXGLkXuJsW5DDC0f7iw1ev/dff3XxlZHQlXpLj3whXATFTr4KQA1NHWTKOxiMIA6gOYO0A3Z3DoAlQAzJawL++75LyP3/y0g8sXswSlLahCEkGK/r34L1lx8D8E3zYM1DAWW2UqtC2VLAWjIACwolLUqFYUC48c4zOzpRde4us21zGWbBGOTkCEfryTD+wa508Bxigxbgda3mMHWvPSn7r515w8xnLv/jTjJcdQgYIU47qTgyCAlNBIXXUjTVCRMwBvB8kYxiRCqpjwNgRWZLkUBmg1Xzq3vP+on8+V6NKO0kz5bVX/y8/+4J/w6vwL196E//zvRefwG3MgSrEUx2N0r4UsFYRAVVATjxkli+q80e/uJ5nXDIINmdsLOaTX1jKP3xsJV4FI8rMC1TBGC5Yfq7+A/A6Woxbfq7yeF79zms2/O/v3PXF1Wt2v4jUxkA+WeJBKogMgg7hoyHUjQIjiA6jdgCo8yhLu/n5v3rda9bt7PqleZ2KajuFjLC49wC/cO2/M3/eThDPm174L9x83/Op1EoYowTHTxUiB1Gbz2hQsAaMQK6cEK+Cs57ffeNGnnH5fsgsJI7Ocs4bfnIzewciPvDppZRLnFzBa9Nxvg38My3EpeM8oRf//ltv/9Nf+d+9jNpTERlD9AA+HkRdBUiBjFlmy76+0773yLK/6i0rqrQXgdw7kiwGkwNKksZ4b0A4AYFzOlG0PWMUkenoZuC8NVWedsEQeAteaMgEYuGFV+/nEzctIc/lZB+hhc4m5h1JVb4F3EeLcFkuPJlf/8sf3wRsYg5QT4zjX4pllghKu4mssm+kh7/5/Fv55Rf9UyNg/v6Lb2asVghHZyfc0SjOKr7dt86sRYQTHtKoh45yTqHgwXMUoaOUUyp4RsctJ1GgYCzzSt3670bluUCFFuA6ugim5J7fRvT5tLHYKV+4+3nc9tBVqAqj1UJTlwFUBQARbf+gcUo9EdqRGosKdGYDRMwjoYCgJ9DhwZZdRXbvK7B4aRUS4RDPI1vLDAxbirFy8gXqudKj7wbeSgtwXhUIRLka4bdpf41gqdSLyNT7Zq5YR/bQ+3buqiIH1tBmBDUWUOLxAboGNtAxvJWSXs8YBU5E5JSde2Pe8z/L+f1fegSJcyYpe3aXed//rsAaECFoEoU3Gy83AV+kyZx4Ya5TodOL/iNQYpawRpu+mPDMs2/nLTf8Lbka/v7zb+FbD13RnmEjUIg9SpsQwYtF1FMc20PngY2Uhndg8xqIwZkMPCesVFA+/uXFHBiOecXz9tDVnbJpa5kPf3YpD28pU4iVoKmMN/q3InInsJcmcmqZ88aL+q6OCheqMA0Cr0JXqcrvv/JdLFvyCAj89sv+gtf/wwcYrnTirNJOBCjGCtoOAeMwPqU8sp2u/esbQWN8hheLNxEGTyzptKw3i0DklC9/ex5fmag4gmodIkcImRahcHqk+hfAjU1eplHmMoe8IK/ySypMk0CByGV0FschiwDoKIwT2RSl/YhAIVaU1qQiYCwmS+gc3krnwEaKY3tBPdoIGAdTRJRIUqaLyGRnowqqQkdJCVqHACm83iBfAD5Jk7gcYa6q7qdHO/UvSiWmMW8DIzAw1sW/3/xGfunH/hFV4X23/h/2jvYRGdpSK/4TuopBxWKzKh0HNtF5YD1xdRDUo8aBGB5PbFJUpj+MRZSg9SgI6J/1WfkGsIcmcH2WOcsu5I9S5QJVgmm/JRg+/I3X8I0HngkKW/YvI7KKtHNHo7QEFYuKIUrG6BjcQsfAJqLa0KHhvxieiAAFk4ICAhB4FbwHY8CIMtsI4GH1/lz/FPg/NIHbnytz0f7N8rwFp+ibUGZAIALWwMa9yxAgsiAA7Ro0BW2JFWUQotownQObKQ9uJqqPosagxnFslILJmBTkXjAC/V1DDFV6yf3sfcBU4MahnfIZ4NOcZG54pzDXdPTTOREyfw5EzJhARCm42RGahQhQmmIqRIgrA435S3loKy6tomLwNuJ4CBwxowlLK2P81kv+mqed9W1uf/jp/PlnfoXhSs9sDRuZt1L/DOQbwCAnkZu3kjnH57wJ5RKC4JgoxYKiJ3uDzFhEtTHYn1xR3o7N6/ipAf9TFZGiBJU6/NTVn+cl13wU0iIvfuZ/sm7Hmfz7La+ho8Cs5DPOUOG3gd/iJHJ5ztxiOcegv6UEwY+mj3Y0Hj1J8xdvY0RzyiO76dr3CMWx3YetKDtOlCNlUhBFCQigFlAil6DKrCaqb/bCfwPf5yRxijJXjOzDdHbxp1KglyA4RgKNjgZlxqiZDJhiMkDf7g1UNhygo7bniBXl6aE4MiAoxfCZ79/AFWu+z0Vn3M4P7n0hn73reooRs5ooJYG/3LNIrgUSTgK3d6EwVxQjXhlV9cVKEBwjBZGZu8rH2wg1jrhygAWbv8Oy9V+gsm0F++rnoNaAGKaTAFYzILBGOTA6j//3gb+lv3sfB0bmU88cziqzmQoIPHPeADcC7zk5M5oB5oRinT4VfZtaguC4OAvOgSrTRPDWocZRHNnFwo3fYNGGWykObQeTomY1ahyQMt0UsJJjhGAqbNLcsvPAYowBZ5S5opDo74nwOWA3M8wVU2VOsLxVlbMIguOgCnGsWKOATEPARCDSeP5l4fpbWbDpNuLR3WAcuBhEmFmCJcOhKIKghAeMFWOZeyyr6qP8LvD/mGGuPs6sVx2Rs3qW6VtQguC4qApx5DEG9EQCxsWI+sb9Y4sfuZn5W76DrQyCdRCVOFkUsOKxeDIsc1egHlyZ/ytePgjczQxyUVGY7Uq9/F6W0kMQHCcF4miqo1GOi4ppBIzxGT277mPJRMDM2/Z9TG0EXAGiIiefYMmxkpOqRZjLAhHK4vhD4GXMICeO2U24Jkv11TwFQaAKhUgx5ngDpoBLK/RtuYOl675Ez64fIGl1KmBKNIsqOMlxkqEaEwSq+lLgBuDzzBCnqsxahkhy/hjBcYIUQEGEOSRQnepoLOixPAPjYqLaKAs33dY4Iuva8wD4DGzcCJhmUwRnPFY8KCAQBAp/LCq3AhVmgEOF2ao6yMvLffpcPE+ZKtQTwTmInFJPBfXh+zbm1tEZGDmWFeUB5j/y3YmA+Sod+x8B9eBiMI5W4iRvFEEwxSiX1or6U8C/MwNcvajMRuOdUuqp6++gJxYyWS68/Ln7eNWLdtE/r86mbR188H+X8937u8P3oc8FCoVDM5rHX1Ee3cPCTd9k0SO3UBzeCgjYCBBajQKWyaBRDgkCl/Mb1ZJ8Ehhimrk0FmajZQO8rlriIlWesloi3PiSXfzOLz5Cgworlla54txh/t+fns037+qlVFCC2b7e7LEWcn/kinJ5aBuL1t/C/E23URjZBcaBjQGhdQmWvFHKpCBQAZdyWlfK/wX+imnmukaYdayntyb66xiesjwXFs5L+ZmX7gAEEgNTip2TP37H/d14bzBGCWb31pkxkJoCeN+44LKxorz5W7jKAJipFeU2oIAjx5ATBIdTAUHfIiofAfYyjZww+9QtP2XhTE5AlsOKRTX6ulPIhSOosLLxczn7Bg3GEMxSquAKESZy9Gy9n4WPfJX5Ww+uKA+BmxrwtxXBkGPwIATBERRW5OjPA+9gGrkMZTYxnl5n+FUVTogxMDgSUU8MxY4ccg4RZWjMMV41GCGYzSwsGl3H2V/7AMUN9yFp5dCKctvyODIeTxAY4Ze95z3AbqaJQ5lVROQNKnoKJyhyyqadRb7y7QW86sXbwFjwAk4B4dO3LmJ4zFIuKsHspAhxDOft/SKlka+B6W0ETPsTXPg65ycWLDoYNsAfME2cEWYNL9Kj8ItME2fhbz6yEms91z9rH8VSxsBQzEc+s5z/umkxpQKzXTg2M9ARZ+BKoIbZIibjCQWB8EaQfwZ2MQ0cIswWBm4EPW36gkYZqzre9i+n8YFPL6e7K2XfQMyOPQWiSBEhmOWcKJ1RFVSYTSJJEeGJBMFihJ8F3j5NQcOs4A09NtdfUqaXs4pX2LSjiGoRYyCeEyETKOAkp2xq4IXZQoFIwtFZ8ORU9Y058i/APk6Qy5RZwRpepjlnMgOMTIbL3BNYySnNsqABIZYMEZ5QEAisMJbXAH/PCXLG0vb21Ijnj+mb4m5Qz7QIAgUMOUVqgDCbxCYlCJ6MKCj6i3sXynuBCifA7VlI2+sf4kVxlcvUM62CwOIpzLKgUaAgKUHwZFTA5py1bDs/AXyIE+CWbaetqSBi9Jc80y8IDDkFnX0dTWRShCD40bzTX1DkY0DCU+RyR3tTrhR4LtMsCBQQ9UR+tgWNEJERBMdEeRpWnw3cxFPkMEo7k1zeCDhmQBA4UqxPQZhVnKYEwbEynp8/oaAxnrY1ulNWdyzWV4ow7YJAgbJUEfGzbkZjJUNQguBYeOX6eUbOA37IU+B6RWhXbon+dAI9zIAgUKDLVTEos43B4yQnUxcCJ/iRBApDXt8A/CpPgRvySjvKPB3G8JPCzAgCVeiJK4goKLOCqEd8huCxeDKC4Ngo/DiedwCDHCennrZUynhuvcA5KDMiCBTojiogHlRmQcB48qhIre8UtsqZ1IcjBCUIjoUKKwuZ3AB8hOPk4kxoR2msbxRlxgSBBzpdFVBA2jZg0Jw87mS8dxXjfavJu/pIBgyqIEIQHBNRSGJ9AyL/ASjHwSUF2o7COaI8nxkUBKrQYaogSrsRnwNKVuhuhMvYvNVkhS5AcXlGJAYhIgiO09UevRL4HsfBeZR2EyfyijSmLMqMCoKSVAEPWFqfIt4DSlrqY2zeqYz3riCPO2BqNgOgBqwFhDYQqAoAIkqzCTjNedVxB43mtBeRUhbrT4oyo4JAgRLtcHSmiM9BDPWO+Yz1r6HSs4w8KjV+XHzG0Ywo1kDuQQhaVT0VjAHvQUSInTZ/Nd7wciPydmCIY+QiK7QTD89COY8ZFgQKFLUK+JYOGDWOWtcSRvtPo9azhNzEiOaYPOWJiIA1Sp4LCEELSjLhGWfexY3P+RC1pMQ/f+UXeGTXKURWaSphdZZyHfAJjpHLUtqKi/Q1npMjCCKtAdqCA36PGkeldxVj/adS61yEGjsZMD7lR5gKGoIWleXCgu5B/vAn3snKFWtBlN7SCG/8139GVZp6jGYE6jV97XEFTVpT2kWlLIvmwbWcDEEgEOU1UG2ZgBHNyV2RavcyxuafRq3cD2IOHZEdCwURMEYBIWg9uUJXcZwlvbugVgaXsWrBFiKXk+YOoXm8h2I31xRgNbCZY+B6umkfRZ7rKywRZlYQKIIViKUOSEsETBZ1UOld2ehgklLfoeG/ZhwPBYwBawkrzi0qsrDtwGL+5/ZX8MqrPkFWL/CRb76OauKIndJs6unLM7kO+DeOgcsToV10JPqaNAKUGRUECsQCJUlApWkryoKSxl2Mz1vdWFNOit0IiviMEyEyFTQErciIknvHn336N7jl/udRSwrcu/k8Iqu0iiTSVx9z0CSR0g6sl9UKzxBlVlIVVMEYJWg+VShaT9nVOal0MkTMRKWlHsbnncJ43yqyyRXlxo9PB0GxRglalzWKqvCtBy9FBAqR0kpEuRzkHOABfgQnKrSDzPI84+mfrYO/3EMhUqqJUIxaYmc+bJzZlLKtA4YZpx7yFGze2CA7sPAK0r7FZIetKE8nY8AaQAVQgtYkohRjWlWH8bwQeGDWfE2AoC9VZp/MC/O7Bnnri/+O5Qs3cfNdN/Cxb78SmrpZEqhCbNLJjsYLM0chq4MrMLz0Qg6cex2bRq9idE8nkWSYPGUmiIAxoDx1QaBWX6xe/hbwPAnnhZZnhGUqXI3Oxm4GfuY5H+alz/wPyGIuXXUfa7efxZ3rz6cY00xhRmMyimaGgkZzyBKISgysega7z7iWoaUXYYoF/P3SCBixyoxQEAPWKKBND/R6ImQ5GAPFOBwftxNVnqY5pwPr2v5mAF/gOnL6mIVEYGnfLvAOkhKURpnXMYQSND1oJKUgCSBMG59BnuALPQyccs1EwLyA4YVn402EzevYtIpoETAz/MVnYG1zj85yL3gPF505xtLFNUZGHfc+1M14zVCIlLYQFK3R639k0LT6QDDOReqi1ymzlMLHv/NKLjr1Hnq7DvD9e6/jro0XEzmCJlIgIqUgdVABmY6ASck6+tm36ir2nH4tY/PXAGDyFJvVTvrZvzWK0hzeC3Hk+fUbN/Gya/dQLKXgLXf8oJc//MfT2La7SBzCpi0oXBfJkx+fOSdCK6sYXWAyns0sVYiU7667jBv/4QMs6dvND7eeS6VewFklaC5HRsQJdjR5CupJuhaz99RnsWfNsxvPwogqJksApVmMBRGaolqHN7x8F69+yXbILSQODFxx8QF+942GN/3J2WiYU7YHy9N3bNCVwGaegNuxSWll3QvlilKPLvQ5zOaw2bp/KZv2LCWOCCHTAhSwpIgmIBwnfTRgaj0r2HPac9k7ETAHr4kRzRvdSytwTZrR5F7o6fQ8/8r9gEAuNHggt1xx/jCnr6zwwMaOMKdsA6p09S3l6U8aNH1LaWmurNf7nFkvsjpRBM2ioHrYew9RWod8CLQAGBALGECefEUZqCw4g12nP58Dq55GvTz/0PFYi9AmdjSqUIg9pWIOCEdQKERKuehRpU0ErsSLgY/xBJwr0bKMl06vejUzJAjUAwomAhuDLYEpCJGF8bifz5Vfy7xsiFXZVvrzfZT8IGBAYkBoUIW8DjZmdMn57DrzOg6suIIs7sLkSWPA33JUcFYx0pwHEQ8MOR7e0sGpp44Chkc5z/adJbbsLIY5ZTtRno5IHzDI43AgtKrhbs7sGuEMhGkXhIARIOqCeL4Q94DrBAyI0DDEIv5KfwUBYvWsTjdxcf1erqp9k/OT+8HXwAu4AgMrn8aeM17A4NKLyKMSJjsYMJXWPhq0IAIKCCePCIiBf//vFVxwxihLl40DAihp3fHPn1jJnoGIclFpE4GwMk64CPgaj8PFCS2rXOFpCDFtJvdCmoM1tNLdRMHhx2LdUFoO8TzBRICC6mGvgAARk3IMD8VruH+iPtXxEi6r3cWra//N6kUR209/IQMLz8Xb+NEV5XbgDJMUkJM/l3xoU5mff/u5vO76XaxeMc7wcMz/3rKI2+7qDSHTbhSTW572hEGTW1qWy/Q6lfa7TqYUJ5yxZBv7Ruazf7SHglOUoNnUgzFQPkUoLwOJQD1ozo8kQKwQA5lEfKP8NO6YqCsXJTx3YY0uWyfNarQNBWMUEVClKQqxsmlHiT/8p1MpxlBPwQgUC0r7CbzRFwB/wuNw3mirzmfmqeEStL06md6OYd792j/kmnNuY/2u0/id/3wXD2w/vQWu9g4hYwvQebpQWACaN+opMUBJIQe+sTlm+5DwE+emLOrISHKhHRx+dNZMkdOJAq9CqaCI0KYChQuNyjJgR9tcqpkZvUyURbSRegrPPe+bXHPBVyEtcdqp9/CKKz/N2m2/gSIIStCkkClC95kQzQPNmBYWsBY2DkV8+Ae9vP6CIRZ3tk/YWAMitAQjStsLenOjzwD+i6O43CgtyculiDraiAiMVLtALbgEUMaqXShB0yiYCLrOEKI+0IxpV7Cwa8zysR/2cONFQ/QVPKmn5VmryPQNaIJAQC5/3KABoQWJiD6DNlOI4Btrr+bfv/RLvOiyL/DDTRfxn996Nc4QupkmUYXOU4S4HzRjxhQsbBtxfOahbl53/hBGwCstSxWcDUdV0ysQ1SuACEg5jBNVWo0aehQuF23Db8VTx99+8Rf4l5t+niQXjEBkFeVkCzSHwgIoLQbNmXGxhR/ujbljR4lrVlWoZ0IrsyFoplkgcImKLDx6TuMQodWo5yKDLqQNGVEKDlSF2CoAStAMxkF5pYAAnhkngBG4bUsH5y6s013w5J6WJQLGMG2CQKEL1UseGzSqtJoCPD0BEdqXiBI0j+ZQXCJEXaCek8YZ2Fsx3LO7yAvXjJHkglfwKqiC0jrksK8LFmG6BMHVwOc4jKMFiXARylMTBArGQmERIJx0kYE7dpQpO09sob+c0V/K6Yg8CmReaAUi4CzTKghUuIijOBVai5euOnq28NQFgSkz2c3kzVkbHqwa/vfBbkTACI2gOXN+nYsX11jenTbCxitNZ4yiTJ8gEOV0PPOB/UxxktNajC4BziAIjpVyiDB1xYyABTxNYQQQUCZr77hlx1iZ7+8ocdXKKs9ePUZktHndjYaOZsYEq3CsOiJocLQUUTlPVQsEwZNQD3gQe7AO/Zjmk+U6QQyop6nksC7HArVc+PL6MttHHK88e4SuQt6UsFFABKxTQABlmgSBQeVC4C6mOFRoJap6OcGTCQEjU7cu9wuuA0x02LclVyAZVGwBUFqOFbBucg069T381PlDlCJP7oVmsAZUmV5BoHoZ8P6W3ToT5QIVguAxNAfbAR0rIZ4vGMckZZLQUFoqAGhOyyo6WLc/4qYNnbz8rBF8E3JRRDDWoEy7ILigZbfOjJcONXoqjyMIIRP3Q9fpgi3/iEsxDaC0vNjC7TtKnLewxhn9yUm8I01QY1HxdMkoTroAwzQKghWozAMGWu7oLDe6WmApRwtCyPRBz9mCONCMJ6e0BSNQz+FbWztYMy89aQGDeopje+k58DDl8Tob7fOoaBGDMk2CYKEYPR24HcCJUVpFlLIyjehGaQgC1anr/U8TJALNmVUiA1uGI/aMWRZ15swIEVQs4jNKwzvpPLCB0uhOnE9ItA8RBWU6BUExyzn10aDJclqGWDkdVYLgUR6KS8B1gubMOiIwlghbhmOWd1fImT4qBhWL8QkdQzvoPPAIhfF9GJ/jjWuU9YrBo0yvIBAjpzHFiRFaRS6cI8qkIFAwMRQXCeqZlQTIFfZXLF4B4cSJwRuDTauNgOkY2Eihsh9UUWMbAQOgCE4yLDkoIEybIDCiZzHFGVFahnI2QTBFPcTdgikwq1mBSmLIvOCsnlAHgxhsUqFraAudBzYS14ZQpmYzwmM0gkY8yjQLAuVsFRyQOaU1uFzKip7qDUETeS8T1TpXyLvyoQcvZzPlqVNjUQxRbZjOwc10TJSrjwHgjeOJKIIjx+CZAUGw0ov0AvudF6EVpJaVRukmaAqvQq0GnWWlsytj/1CEVyjGStMoiAMM4Jm1vEIpUqwoelwbZAaAuDpE54FNlIe24NIKKgY1lmNhJMdJxgwIgk6Tc2ojaExOSxDR1UAHJ13gPYDyhpfv5seevZeuUsb23UXe898ruWNtV1PDRhVQZjUR6C9lWAM5P5oaB+opjO2j6+AG2fAObF5DZWr+cpxiSZgBQVCY+vv6HU5EaQUCKxQsJ11QT4W3vHYrv/j6zUwSVq8a44IzR/nld57LnQ80L2x8Cujs7mY6ImV5T0rmBTH6I1aUc0oju+gcmAwYyTMwBm8inqqCpCjTLwgUVgO0zowmlZVprIhyEgVpJpyytMZrfmw3qEBqmGTo7k14/Yt3cPeDZ+IVjHByGchGFc0FDKDMOpmH1X0Zyzszcg/OPP6KsvUppeFtdB1YT2FsL+Iz1FjUOk6UI2O6BYEA3rMSwKmnJWSOlaKcZEGWw8oldXq7MsiFI8nEz9Xo6coZHXcYq5xMYiAbm6yoB1SZVRQwwFUrKjirJLkcETDeTq4ol4d3NI7I4vEDgJ8MGOOYLpGkTLcgUMAalgM4a2gB4lR0GcpJFlihMfiv1gylTg+ewygDQxGVqsUITaE5VHcqUY/Mutvs6xlcuqTOuQvqpF4A8DZqlEvG6TmwlY7BLcTVAfTR4b9jmp3kZYBwglBPQBWcg2KsiDBrKSxFKDkVmk6ML5PLYk66IIqUdZvLfP3OebzouTvBOfCAU/DCp29dRK0ulIpKM4iB+j6o90NxMfiMWaGew4runOvPGEWA3MSoGLoHN7Fo407qG8YoZSMogjeOmeQkZ+YF1bpw5uoq11wySKGY8+AjnXzr3j6YepxgllqoaIdTlGarDkix2MVSMZxkgchkvfu9p5Dm8OwrB+gqerbvjPnwp5bzpW/Np1hQmkYAhdENiikIUR9oDihtySskOazqyXj1+SN0dTqSXOjcv55F629h4ZavMbLvPHZkF854wAAIEEnGzApqdeG6Zwzwx7+0nr7+KgA+t/zXF5bwp+87FVUQYTZaVEil7Aqp0Gx5Kn0q2iMEzeCsMjQS8bt/dwanrajS1ZGxY0+BHftiSoUWaO0FfAIjDygda4TiQsCAekBpeQqoQuohtvC0FXWuOzOh23lKOx5iybovMW/7nZjaCDgldzGaGyBnmoX15ibIcmHZooTf/7kN9PXXIHEAGKf85Et2sHZjF5/48kLKRWUWKuYq/S5XodmiBSxTxRA0jXOKKjy8pYRXcJaW+j++GPAJjD6kJAegtERw3YCACCBMqyQFryCcGCOTVYqU8+alXLLSc3b3CL0772X+uluYt+MuSKtgY4hKIAknWyQpCDMkSFK49OxhFiyoQ2p5VCYQw9UXD/DpWxfgVTCizDZZzDKXxTSdKIsImk4ECrHSsgwNtT1Q36+4TiZKMDGIZVqoghG48oIaXR0e709s0aJgPf2dwpI+Yb4ZZuHOO5l3+0307F4LeQKu0AiYZookm8FLNQNVsJYnZI1iBJTZyaALnUFpNoWFBMExEgsopCOQDiso00YVIgfXvniMJfMzslxO6A6y3BZw4weYt/l7LHz4Zsr714PPwcbgirSCiJRg5hRiuOehLoYGY3r765AYGqwCyvfu76Val7Y9OvMqpBkgEFsQUQ6nqgucqtJsgixUjkMQCIgw7VTBRAoGqpkhzzluOvVdL8WRvSzcfBuLHrmF0uBmwICNwDlahU51NJEoiiBo697Fl4AIFCMQ0baagW7eUeQvPria3/+FDZQ6EiYJX7x1CZ+6ZRHFuH1DxpmcS864jyQp8sPtZ4OCCIeRBQ4kdDRBMAWFOAJrAeW4eBujYhoPWC7a8DUWTlQ8uhtEwBVpRYpgG0GTkmhMK8q9UIrrXHfhbVSTIt966Kq2m2cUC8r/3rKQrbtKPPfKA8RFz9p1nXzl2wtIMmmEUbtRBMj5rZf9Ba+6+uNkueNvPverfOjrr8VZEJQpCwwtwAvzaAFBoEAhVkSUYyIG7woTVaRzYCNrbn8PF375d1l+z38Sj+8DVwAb08ocOZFkKK3He8GahD985bv48zf8Cn//c2/iTS/8F9KMtlOMlbse6OLd71nN2/7xVP7nqwvJPW0ZMgB5Dsv79/LyKz4DanBRwque/t/0livknkkCqvQ6VZpOlE6EpgsCVaEQ+R89nBUhtwWMz+je+wBLHvoK87bdga0NgW0M+NvmP6+TqaBRQGgpaQ5nLNrNtRfcArlDTMZLLv8c//7V/0s9LSCibTav0YmaHbsXIjBaLbN/tJ8lS9aDydk1uIRa6jBMUUAoO4QmE2PQDqX5gkAV4lgx5skDxmZ1+rd9n8WP3Dy5opxUplaUy7QbQ44lR2k91sC+kXls3HsKZ53+PQAe2nEmlSTGGaVdCe3PGmVgrIff/eg7ef1zPkStXubfbv55amlMZJXDFB1NJlBSKNMigiCOwIg+ZoPM2xiXjLNg27dY/PBN9O66D/LkUAfTpiweR9a6DxOPd/JHn/wDXnfNR6kmZT74tRtRFUBpriCyyh3rL+LeTRfhFTIPBacohwiUndBcAkWFDlpAEKhCIVKMARTUOHIbE9cGGwP+JetuomP/w+CzyYBxRdqZIhjxWPEorSl2yg+3nslvfPhtKFBwEFtFCVrlfx+vgsjke+UxSo4mU7QAlFrlvFoEQAnmcNDECs6R2YjC6B7mb/oWi9Z/jfLABkDAxuAcs4WhETQt/zezyAkCQKuFTGBEeRKFVgiaSJC4Fb5l0hqmtkAEZxQlmItBExUs5epeFjz8NRZMBExhZCeIAVdkNjIohpxWJyhtKYgdTScRUKCJ0ly45uzvc8MVn2HX/uV88OuvZ6xWxhqCOSazcNaBW7ngy/+AHRoEE4ErMnsJIh5BUYJgRkSOJhOIgJgmSTJhzaJt/PXP/Bod3XvBZhSiOn/26bdgjRLMLWpgyeiDWLsbXC+znQIGj8UTBDMkNjRbk4Mm97Codw8dPXvx1R5IC5y9/CGMIZhjFCESiF0GJmKuEJTIJAhBMCMiQ/NZwNEkkYWHdpzO/euejimNkGcFPvv9G8g9wRwUCZRNDRDmCgVKUiMIZog1NJmCaf6efg9vfv/f8Jvv/1ve+I8f4LN3Xk/BKcEcXAQwSperg86loBFKUscQBDPCOppMwLTCQ2EHRnv57B3XYQRiByIEc4wCkcnocFVQw9whlEwVEYJgJhhH85lWeQLZWYK5HjSS02Frc6yjgYIkCMz0VwUEIWjCtT9B4CSnaOqQC8dFOUQf53OD8BjKkUQopoZC6ojE84QElEkqAPrYzwIKqChPToglQVAUYZoFgTiCIGhQwJFRliqoHBkEhz4fWQgIYISalamC1CmpUfKp8gdLFDUKoiQGcgEz9T4VsCi5y/lObZBNtW3E3gNgVbC5AcB4wXmDUcH6g2UO/djUZ+uFKDdEmcUdfM0doqACioKAF300hIxRSmSIF1QABESZLkHgaD5PEDSjiVYQBXJQD5qDoYDzOVVxJNaS2cmQGImVfSXYV1b2d+SMFXOqsSePcnKXkUQ5SexJo4xarNScklgltZ7MKrmdChvjwSgCgGJQhElGPE48t4449td3gXiekBfIDW6qotzi/KHPcWYppG6ibKNKqaNUjykljnI9oqtaaFR3NaazbqknVTSuQx4juWASAQU1gAGVqfcCoByHIPBy0Us9TXY5cAfTLQhUjggSD6iZLBHAebLOAbRnN653F65zP/0927h26acYLtXY36GMFjOSQoZzKc5mWJtOdSegoqiAoFgFg2JRppoWBBAVBEBpECbpobA7QiQ5Xxw+n021hSA5T0oO/0OPei+H/7wCwmFnaeAFUcGoNDqfqNJFPj4fV+0iGu8hGp2HrXQTjfcRjfbhKj1EI/3YpAAK4gEFODqIlCA4StoKQXMpcCdB8BSJCuoBD6qThUIeQVauYnp2E3Xtw3bvxS7YSLz0IQp92ykVR+gojBAVhzGFCiauYiQn80UiBTNVqIAeHg7CTIkl40vD57GpthgkY8Yc3ZmIB+NB/KH3KpAWkaSAyQrYtIAb7yUeWExxcDHx4BKikXlElV5cpQs31odNQAVo1KH3agCUYE6qt0LQXAzczY8UBALKZOVANvVSBDqHiUpDaP827Mr7KC9eR0fXHsqd+3A9uxvdSsHVKOIxKqgaUEER8FPvMYcCpUkKJuNLQ+excSpomk50KnwA/NRnPRRGWYSt9GCrnUQHQ2hwEcV9KylNVGFwSePHG1W3iIJaUAMqc+gYLqi2QtBcANwFOILgKOIFMvAecgsaK6Y4Sr70YeIV99HZu43SvK24JQ9T7N1BVzxKwSYgHgGMN6h34C2qgkdoYY2g+crQuayvLQHJaHmHh47kIBw6R0sj4pEFFBrdz2KK+1dQ2rOK4oEVuGoHkllMBmqYCiAlmJVGWyFozgLuJHz5WaACGaiCB0Qg7R6Fxesp9m+iOBEm8cp76Vn4CD3lfdjiGNYmRCqQOdQ7vBoUoV0VJOOmkXN4pLoEJKetiYLJD1UeIUkRk5Qp7l9KefepFPespjiwbOLzcqKxTlAmayp8VJS2Fww4mk1JEeohaOYgL6Cg+WRlRWDRdgrzdhCvvpPO075Lb/8GCr27iMqDlCTD5g4/FShkEaQxGS0pfO+KCuSuUQ2iqE3Jy0OMrz7A+Jp7AEEqXVPHbkvo2H4GHdvOojjxPh5eiK2aqW4HkDbteoKkFTqalcD3gCUEs5yABzLIPeRlj+3eg5x6Nx2n3E7vwocpLn2Acu8OOkyCqIC3jdKJ8gizXUEybhk5i4eqy0By5gSTH7aIoKBgR+dT3L+M0t5T6Nx6Dh07zsCN9+CqEQqoa6MFg2Cro9lEE1TqBLOTCqTgFdSBdowhp9xF54r76FjzXbpW3k1v+QDW1bCAZDE+dXgi5iIFDMqc4m2jDpeXhxk/ZT/jp/6A/Zd/HlProLTnFDq3nUV55xl0bD+LaLQbyQUEvANEaUlB6mg2lQSoE8wScsQDkHkBdPWDlFffQ/ncm5i/9Id09OxqrBTH3qBZAX/wtd5BTgAg4W+YR4WP4uMq46fcx/iauyEpNVapO3aeRtfGi+nYfnqj87F1QQ2TZQGUlhDUHU0nVdBx2legAgpkkCn4vmGi+ZspnPtV+k+/je5l99PRtZeCF/zUBpjWOsgQfrQQNIGACqQxEIN4su79DPfuYfi8b2LGeynuW9E4Yut+5DJKe1cRjXaCCGpb4IgtqDiaTJWawChCOwoBk0BmQDoqmDV3MO/8r9B12rfoXfQwHa6KKJDF+EawBNNwnXmgBvKD5QAa3U5l5QNUVq1l7zM+RXEiaDq2nU3PI5fSsfVcXKWMeEGjJm2xBVVHk4moIlRQ2kAgXvAZKJCX69izv0fvWd+k57yvMH/eBuLiKDaL8AfDJS8TnNyts3DMBrVFm6ktXc+BS25q3GLQs/5SujZdSOeW83DjRZCT+dxOIFBxQvPFhtFaDkLQumuqoBkkJcWdeg+dl36KBWd+na4F6ykXRjBpgdw7tNZJRvDUCQYFlOApyqLJEiXp38m+ieDZd8XnKexfTvdE6PSuu7KxUOCqFu9m+o62wAvjcuHLPE2n/JvAz9F6QsDUIY3ALNxG8cIv0H/el+k75Xa641HwDs0ivFqmRxBLzu3jp3DX2GoQzzQJxIPNwWaQxo2g6XnoSvoefAaFgcVIBj4KgTMTBD7ojNJ0CntoHUEm5DloZ43okptYfP5EuJzzVXo792BRNC2Q1ztpG+HoLFADmXm006mseIDKyrXsuep/6N54EX1rr6Z7/WW4aoQ3oE6ZHoHCPqe0At0DQhMFXtAMvIJfvIPOi77I/Cs+2lhHLkZVSErkB4tgJikgBDP/bFfh0UWCofO/ztDZ36G4dzXz7ns2vQ9dSXHfYhBQd+ILBIHucaA0ncouhGYJAZNAVlSiM+6m7+kfZdE5N9HTvwmXOfI8Jq91cvIERjwnSaAGkhKIUlu8kZ3L1rH3Gf9L9yNX0H/v8+ncdiYmOaGNtUDNbocamk2F3YJyEgVe8An4jhR74VdZds0HWXjm1+hwFchj/Jwb6oejs3Cha9SorGOYgUu/yMAFtza21frveR49D18xeawWh8A5XiLsdSI0XVHYU1e8giGYWbngM8g7a5Se/jn6n/FhFq2+nZKtomk5zF5C0AS5myxRRs+4g9E1dzeuvOm/+zp6H3wG0VgBHx3renSQ5exxWU7TeWHYGPYDCwlmjgc/byelSz7L4qf/J/1L7ydWJc+K5HkHrSEwBK3zQHIRUMZXPNCovU/7DP33PJ++tc/AjfeBEDy5cTE6+P8BW01E00mieO8AAAAASUVORK5CYII=) no-repeat 150% 100%;\n  background-color: #2238b3;\n  background-size: 205px;\n  background-blend-mode: luminosity;\n}\n\n.menu__header::after {\n  content: '';\n  display: block;\n  width: 90px;\n  height: 86px;\n  position: absolute;\n  bottom: 0;\n  right: 0;\n  background: url('data:image/svg+xml;utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"86\" height=\"86\" viewBox=\"0 0 86 86\"><title>Beta</title><defs><path id=\"b\" d=\"M-11.704 13.144H125.58v30H-11.703z\"/><filter x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\" filterUnits=\"objectBoundingBox\" id=\"a\"><feOffset dy=\"1\" in=\"SourceAlpha\" result=\"shadowOffsetOuter1\"/><feGaussianBlur stdDeviation=\"1\" in=\"shadowOffsetOuter1\" result=\"shadowBlurOuter1\"/><feColorMatrix values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0\" in=\"shadowBlurOuter1\"/></filter><path id=\"d\" d=\"M.4 16.972h119v28.4H.4z\"/><text id=\"f\" font-family=\"Arial-BoldMT, Arial\" font-size=\"13\" font-weight=\"700\" fill=\"#FFF\"><tspan x=\"37.556\" y=\"34.556\">BETA</tspan></text><filter x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\" filterUnits=\"objectBoundingBox\" id=\"e\"><feOffset dy=\"1\" in=\"SourceAlpha\" result=\"shadowOffsetOuter1\"/><feGaussianBlur stdDeviation=\".5\" in=\"shadowOffsetOuter1\" result=\"shadowBlurOuter1\"/><feColorMatrix values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.140964674 0\" in=\"shadowBlurOuter1\"/></filter></defs><g fill=\"none\" fill-rule=\"evenodd\"><g mask=\"url(#mask-2)\" transform=\"rotate(135 55.44 44.523)\"><use fill=\"#000\" filter=\"url(#a)\" xlink:href=\"#b\"/><use fill=\"#CF3A3C\" xlink:href=\"#b\"/></g><g mask=\"url(#mask-2)\" transform=\"rotate(-45 95 36)\" fill=\"white\" class=\"\"><use filter=\"url(#e)\" xlink:href=\"#f\"/><use xlink:href=\"#f\"/></g><path d=\"M8.5-.5l88.204 88.204M8.5-39.5l88.204 88.204\" stroke=\"#FFF\" stroke-linecap=\"square\" stroke-dasharray=\"1,2\" opacity=\".386\" mask=\"url(#mask-2)\" transform=\"translate(-3)\" style=\"&#10;    transform: rotate(90deg) translateY(81px);&#10;    transform-origin: bottom right;&#10;\"/></g></svg>') top right no-repeat;\n}\n\n.menu__header-title {\n  font-family: var(--text-font-family);\n  font-weight: 300;\n  color: #fff;\n  margin: 0;\n  padding: 0;\n  line-height: 1.5;\n}\n\n.menu__header-version {\n  color: #aab3ed;\n  font-family: var(--text-font-family);\n  font-size: 14px;\n  line-height: 1.5;\n}\n\n.menu__report-tab {\n  padding: 3px 13px;\n  color: #777;\n  border-top: 1px solid var(--report-border-color);\n  display: flex;\n  flex-direction: column;\n  cursor: pointer;\n  text-decoration: none;\n  height: 55px;\n}\n\n.menu__report-tab:hover {\n  background-color: #448aff;\n  color: #fff;\n}\n\n.menu__report-tab__url {\n  font-size: 15px;\n  font-weight: 700;\n  text-overflow: ellipsis;\n  line-height: 30px;\n  white-space: nowrap;\n  overflow: hidden;\n}\n\n.menu__report-tab__time {\n  font-size: 11px;\n  line-height: 11px;\n  padding-left: 2px;\n}\n\n.menu__nav {\n  list-style-type: none;\n  margin: 0;\n  padding: 0;\n}\n\n.menu__nav-item {\n  height: 40px;\n  line-height: 40px;\n  border-top: 1px solid var(--report-border-color);\n}\n\n.menu__link {\n  padding: 0 20px;\n  text-decoration: none;\n  color: #767676;\n  display: flex;\n}\n\n.menu__report-tab + .menu__nav .menu__link {\n  padding-left: 35px;\n}\n\n.menu__link:hover {\n  background-color: #448aff;\n  color: #fff;\n}\n\n.menu__link-label {\n  flex: 1;\n  color: #49525f;\n  font-weight: 500;\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n\n.menu__link-score {\n  padding-left: 20px;\n}\n\n.report-body__metadata {\n  flex: 1 1 0;\n  white-space: nowrap;\n  padding-right: 10px;\n  overflow: hidden;\n}\n\n.report-body__buttons {\n  display: flex;\n  align-items: center;\n  flex-shrink: 0;\n}\n\n.report-body__url {\n  font-family: var(--text-font-family);\n  white-space: nowrap;\n  font-size: 13px;\n  font-weight: 400;\n  color: var(--secondary-text-color);\n  line-height: 20px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.report-body__url a {\n  color: currentColor;\n}\n\n.report-body__breakdown {\n  flex: 1;\n  max-width: 100%;\n}\n\n.report-body__breakdown-item {\n  padding-bottom: 6px;\n}\n\n.report-body__breakdown-item:last-of-type {\n  border: none;\n}\n\n.report-body__header {\n  height: var(--report-header-height);\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n  justify-content: flex-start;\n  border-bottom: 1px solid var(--report-border-color);\n  padding-right: 14px;\n  position: fixed;\n  width: calc(100vw - var(--report-menu-width));\n  max-width: calc( var(--report-width) - var(--report-menu-width));\n  z-index: 1;\n  will-change: transform;\n  background-color: var(--report-header-bg-color);\n}\n\n.report-body__more-toggle {\n  background-image: url('data:image/svg+xml;utf8,<svg fill=\"black\" height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z\"/><path d=\"M0-.25h24v24H0z\" fill=\"none\"/></svg>');\n  width: 24px;\n  height: 24px;\n  border: none;\n  flex: 0 0 auto;\n  background-repeat: no-repeat;\n  background-position: center center;\n  background-size: contain;\n  background-color: transparent;\n  margin: 0 8px 0 8px;\n  cursor: pointer;\n  transition: transform 150ms ease-in-out;\n}\n\n.report-body__header-container.expanded .report-body__more-toggle,\n.aggregation details[open] .report-body__more-toggle {\n  transform: rotateZ(90deg);\n}\n\n.report-body__header-content {\n  padding: 10px 0 10px 42px;\n  top: 100%;\n  left: 0;\n  position: absolute;\n  width: 100%;\n  background-color: inherit;\n  clip: rect(0, calc( var(--report-width) - var(--report-menu-width)), 0, 0);\n  opacity: 0;\n  visibility: hidden;\n  transition: all 200ms cubic-bezier(0,0,0.2,1);\n  border-bottom: 1px solid var(--report-border-color);\n}\n\n.report-body__header-container.expanded .report-body__header-content {\n  clip: rect(0, calc( var(--report-width) - var(--report-menu-width)), 200px, 0);\n  opacity: 1;\n  visibility: visible;\n}\n\n.report-body__header-content .config-section {\n  padding: 15px 0 15px 0;\n}\n\n.config-section__title {\n  font-size: var(--subheading-font-size);\n  font-weight: 400;\n  line-height: var(--subheading-line-height);\n  color: var(--subheading-color);\n}\n\n.report-body__header-content .config-section__items {\n  margin-left: 30px;\n  margin-top: 15px;\n}\n\n.report-body__header-content .config-section__item {\n  margin-top: 5px;\n}\n\n.report-body__header-content .config-section__desc {\n  line-height: var(--subitem-line-height);\n  word-wrap: break-word;\n}\n\n.report-body__fixed-footer-container {\n  margin-left: var(--report-menu-width);\n  max-width: calc( var(--report-width) - var(--report-menu-width));\n  width: calc(100vw - var(--report-menu-width));\n  position: fixed;\n  bottom: 0;\n  z-index: 1;\n}\n\n.report-section__title {\n  -webkit-font-smoothing: antialiased;\n  font-family: var(--text-font-family);\n  font-size: 28px;\n  font-weight: 500;\n  color: #49525f;\n  display: flex;\n  margin: 0.4em 0 0.3em 0;\n}\n\n.report-section__title-main {\n  flex: 1;\n}\n\n.report-section__title-score-total {\n  font-weight: 500;\n}\n\n.report-section__title-score-max {\n  font-weight: 400;\n  font-size: 18px;\n  margin-left: -4px;\n}\n\n.report-section__subtitle {\n  -webkit-font-smoothing: antialiased;\n  font-family: var(--text-font-family);\n  font-size: 18px;\n  font-weight: 500;\n  color: #719ea8;\n  display: flex;\n  margin: 24px 0 16px 0;\n}\n\n.report-section__description {\n  color: #5f6875;\n  font-size: 16px;\n  margin: 0 0 1em 0;\n  line-height: 1.4;\n  max-width: 750px;\n}\n.report-section__description:empty {\n  margin: 0;\n}\n\n.report-section__aggregation-description {\n  font-style: italic;\n  color: #777;\n  font-size: 14px;\n  margin: 0.6em 0 0.8em 0;\n  line-height: 1.4;\n  max-width: 750px;\n}\n\n.report-section__label {\n  flex: 1;\n}\n\n.report-section__individual-results {\n  list-style-type: none;\n  padding: 0;\n  margin: 0;\n}\n\n.report-section__item {\n  padding-left: 32px;\n  background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNXB4IiBoZWlnaHQ9IjVweCIgdmlld0JveD0iMCAwIDUgNSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxjaXJjbGUgaWQ9Ik92YWwtNzAiIHN0cm9rZT0ibm9uZSIgZmlsbD0iIzY0NjQ2NCIgZmlsbC1ydWxlPSJldmVub2RkIiBjeD0iMi41IiBjeT0iMi41IiByPSIyLjUiPjwvY2lyY2xlPgo8L3N2Zz4K') 14px 8px no-repeat;\n  line-height: 24px;\n}\n\n.report-section__item-details {\n  display: flex;\n}\n\n.report-section__item-category {\n  font-weight: 700;\n}\n\n.report-section__item-extended-info {\n  font-size: 15px;\n  color: #555;\n  font-style: italic;\n  margin: 0 0 16px 24px;\n  max-width: 90%;\n}\n\n.report-section__item-extended-info:empty {\n  margin: 0;\n}\n\n.report-section__item-helptext {\n  font-size: 14px;\n  color: #999;\n  font-style: italic;\n  padding: 8px 0 16px 24px;\n  max-width: 90%;\n}\n\n.report-section__item-help-toggle {\n  color: currentColor;\n  border-radius: 50%;\n  width: 21px;\n  height: 21px;\n  display: inline-flex;\n  justify-content: center;\n  align-items: center;\n  cursor: pointer;\n  transition: all 0.2s cubic-bezier(0,0,0.3,1);\n  font-size: 90%;\n  font-weight: 600;\n  margin-left: 8px;\n  vertical-align: top;\n  opacity: 0.6;\n  box-shadow: 0 1px 2px rgba(0,0,0,0.5);\n}\n\n.report-section__item-help-toggle:hover {\n  opacity: 1;\n  box-shadow: 0 1px 2px rgba(0,0,0,0.7);\n}\n\n.report-section__item-raw-value {\n  color: #777;\n}\n\n.report-section__item-description {\n  flex: 1;\n}\n\n.footer {\n  margin-top: 40px;\n  margin-left: var(--report-menu-width);\n  font-size: 12px;\n  border-top: 1px solid var(--report-border-color);\n  color: #767676;\n  background-color: var(--report-header-bg-color);\n  border-top: 1px solid var(--report-border-color);\n}\n\n.footer__key-description {\n  padding-left: calc(var(--subitem-indent) / 2);\n}\n\n.footer__legend {\n  padding: var(--heading-line-height) var(--aggregation-padding);\n  padding-bottom: 0;\n}\n\n.footer__legend .config-section__title {\n  margin: 0 0 calc(var(--subitem-indent) / 2) 0;\n}\n\n.footer__legend h2.config-section__title {\n  text-transform: uppercase;\n}\n\n.legend_panel {\n  border: 1px solid var(--report-border-color);\n  border-radius: 2px;\n  padding: var(--subitem-indent);\n  background: #fff;\n}\n\n.legend_columns {\n  display: flex;\n  justify-content: space-between;\n}\n\n.legend_columns.single-line {\n  padding-bottom: var(--subitem-indent);\n}\n\n.legend_columns.single-line .legend_column {\n  display: flex;\n  justify-content: space-between;\n}\n\n.legend_categories {\n  width: 100%;\n}\n\n@media screen and (max-width: 900px) {\n  .legend_columns {\n    display: block;\n  }\n  .legend_columns.single-line .legend_column {\n    display: block;\n  }\n}\n\n.legend_column span[class*=\"subitem-result__\"] {\n  flex-shrink: 0;\n}\n\n.legend_categories .aggregation__header__score {\n  height: var(--aggregation-icon-size);\n  width: var(--aggregation-icon-size);\n  flex-shrink: 0;\n}\n\n.legend_categories .aggregation__header__score::after {\n  background-size: var(--subitem-overall-icon-size);\n}\n\n.footer__legend li {\n  padding: calc(var(--subitem-indent) / 2) 0;\n  display: flex;\n  align-items: center;\n}\n\n.footer__generated {\n  text-align: center;\n  line-height: 90px;\n}\n\n.devtabs {\n  flex: 0 1 auto;\n  background: right 0 / auto 27px no-repeat url(tabs_right.png),\n              0 0 / auto 27px no-repeat url(tabs_left.png),\n              0 0 / auto 27px repeat-x url(tabs_center.png);\n  height: 27px;\n}\n\n.aggregations__header {\n  position: relative;\n}\n\n.aggregations__header > h2 {\n  font-size: var(--heading-font-size);\n  font-weight: 400;\n  line-height: var(--heading-line-height);\n}\n\n.aggregations {\n  padding: var(--heading-line-height);\n  padding-left: var(--aggregation-padding);\n}\n\n.aggregations:not(:first-child) {\n  border-top: 1px solid #ccc;\n}\n\n.aggregations__desc {\n  font-size: var(--body-font-size);\n  line-height: var(--body-line-height);\n  margin-top: calc(var(--body-line-height) / 2);\n}\n\n.section-result {\n  position: absolute;\n  top: 0;\n  left: calc((var(--gutter-width) + var(--gutter-gap)) * -1);\n  width: var(--gutter-width);\n  display: flex;\n  flex-direction: column;\n  align-items: flex-end;\n}\n\n.section-result__score {\n  display: flex;\n  flex-direction: column;\n  align-items: stretch;\n  background-color: #000;\n  color: #fff;\n  text-align: center;\n  padding: 4px 8px;\n  border-radius: 2px;\n}\n\n.section-result__points {\n  font-size: var(--heading-font-size);\n}\n\n.section-result__divider {\n  display: none;\n}\n\n.section-result__total {\n  font-size: var(--body-font-size);\n  margin-top: 2px;\n  border-top: 1px solid #fff;\n  padding-top: 4px;\n}\n\n.aggregation__header {\n  position: relative;\n  max-width: var(--max-line-length);\n  display: flex;\n  align-items: center;\n  cursor: pointer;\n}\n\n.aggregation__header .aggregation__header__score {\n  margin-top: 0;\n  position: absolute;\n  left: calc(-1 * var(--aggregation-icon-size) - var(--subitem-indent) / 2);\n  width: var(--aggregation-icon-size);\n  height: var(--aggregation-icon-size);\n  top: 2px;\n}\n\n.aggregation__header .aggregation__header__score::after {\n  background-size: var(--subitem-overall-icon-size);\n}\n\n.aggregation__header > h2 {\n  font-size: var(--subheading-font-size);\n  font-weight: 400;\n  line-height: var(--subheading-line-height);\n  color: var(--subheading-color);\n}\n\n.aggregation {\n  margin-top: var(--subheading-line-height);\n  max-width: var(--max-line-length);\n  padding-left: calc(var(--aggregation-icon-size) + var(--subitem-indent) / 2);\n}\n\n.aggregation__details {\n  display: inline-block;\n}\n\n.aggregation__details > summary {\n  outline: none;\n}\n\n.aggregation__details > summary::-moz-list-bullet {\n  display: none;\n}\n\n.aggregation__details > summary::-webkit-details-marker {\n  display: none;\n}\n\n.aggregation__details > summary:focus header h2 {\n  outline: rgb(59, 153, 252) auto 5px;\n}\n\n.aggregation__desc {\n  font-size: var(--body-font-size);\n  color: var(--secondary-text-color);\n  line-height: var(--body-line-height);\n  margin-top: calc(var(--body-line-height) / 2);\n}\n\n.subitems {\n  list-style-type: none;\n  margin-top: var(--subitem-line-height);\n}\n\n.subitem {\n  position: relative;\n  font-size: var(--subitem-font-size);\n  padding-left: calc(var(--subitem-indent) * 3);\n  margin-top: calc(var(--subitem-line-height) / 2);\n}\n\n.subitem strong {\n  font-weight: 700;\n}\n\n.subitem small {\n  font-size: var(--body-font-size);\n}\n\n.subitem__desc {\n  line-height: var(--subitem-line-height);\n}\n\n.subitem-result {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: var(--gutter-width);\n  display: flex;\n  flex-direction: column;\n  align-items: flex-end;\n}\n\n.subitem-result__good, .subitem-result__average, .subitem-result__poor, .subitem-result__unknown, .subitem-result__warning, .subitem-result__info {\n  position: relative;\n  display: block;\n  overflow: hidden;\n  margin-top: calc((var(--subitem-line-height) - 16px) / 2);\n  width: 16px;\n  height: 16px;\n  border-radius: 50%;\n  color: transparent;\n  background-color: #000;\n}\n\n.subitem-result__good::after, .subitem-result__average:after, .subitem-result__poor::after, .subitem-result__unknown::after, .subitem-result__warning::after, .subitem-result__info::after {\n  content: '';\n  position: absolute;\n  left: 0;\n  right: 0;\n  top: 0;\n  bottom: 0;\n  background-color: #fff;\n  border-radius: inherit;\n}\n\n.subitem-result__good::after {\n  background: url('data:image/svg+xml;utf8,<svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" xmlns=\"http://www.w3.org/2000/svg\"><title>good</title><path d=\"M9.17 2.33L4.5 7 2.83 5.33 1.5 6.66l3 3 6-6z\" fill=\"white\" fill-rule=\"evenodd\"/></svg>') no-repeat 50% 50%;\n  background-size: 12px;\n}\n\n.subitem-result__average::after {\n  background: none;\n  content: \"!\";\n  background-color: var(--average-color);\n  color: #fff;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  font-weight: 500;\n  font-size: 15px;\n}\n\n.subitem-result__poor::after {\n  background: url('data:image/svg+xml;utf8,<svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" xmlns=\"http://www.w3.org/2000/svg\"><title>poor</title><path d=\"M8.33 2.33l1.33 1.33-2.335 2.335L9.66 8.33 8.33 9.66 5.995 7.325 3.66 9.66 2.33 8.33l2.335-2.335L2.33 3.66l1.33-1.33 2.335 2.335z\" fill=\"white\"/></svg>') no-repeat 50% 50%;\n  background-size: 12px;\n}\n.subitem-result__warning::after {\n  background: url('data:image/svg+xml;utf8,<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><title>warn</title><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\" fill=\"white\"/></svg>') no-repeat 50% 50%;\n  background-size: 12px;\n}\n.subitem-result__info::after {\n  background: url('data:image/svg+xml;utf8,<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><title>info</title><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\" fill=\"white\"/></svg>') no-repeat 50% 50%;\n  background-size: 12px;\n}\n.subitem-result__unknown::after {\n  background: url('data:image/svg+xml;utf8,<svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" xmlns=\"http://www.w3.org/2000/svg\"><title>neutral</title><path d=\"M2 5h8v2H2z\" fill=\"white\" fill-rule=\"evenodd\"/></svg>') no-repeat 50% 50%;\n  background-size: 12px;\n}\n\n.subitem-result__points {\n  margin-top: calc((var(--subitem-line-height) - var(--subitem-font-size) - 4px) / 2);\n  background-color: #000;\n  padding: 2px 4px;\n  color: #fff;\n  border-radius: 2px;\n}\n\n.subitem__details {\n  list-style-type: none;\n  margin: 0;\n  padding: 0;\n  margin-left: var(--subitem-indent);\n}\n\n.subitem__detail {\n  font-size: var(--body-font-size);\n  line-height: var(--body-line-height);\n  margin-top: calc(var(--body-line-height) / 2);\n}\n\n.subitem__help-toggle {\n  -webkit-appearance: none;\n  -moz-appearance: none;\n  width: 16px;\n  height: 16px;\n  border-radius: 50%;\n  border: 1px solid #ccc;\n  vertical-align: middle;\n  margin-left: 8px;\n  outline: none;\n  cursor: pointer;\n}\n\n.subitem__help-toggle + label {\n  position: relative;\n  display: inline-block;\n  height: 12px;\n  width: 12px;\n  left: -21px;\n  top: 2px;\n  pointer-events: none;\n}\n\n.subitem__help-toggle + label::after {\n  content: '';\n  position: absolute;\n  left: 0;\n  right: 0;\n  top: 0;\n  bottom: 0;\n  height: 12px;\n  width: 12px;\n  display: inline-block;\n  background: url('data:image/svg+xml;utf8,<svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" xmlns=\"http://www.w3.org/2000/svg\"><title>help</title><path d=\"M5.216 7.457c0-.237.011-.452.033-.645.021-.194.058-.372.11-.535a1.918 1.918 0 0 1 .55-.847 3.65 3.65 0 0 0 .545-.597c.133-.19.2-.398.2-.623 0-.28-.053-.485-.16-.616-.107-.13-.268-.196-.482-.196a.583.583 0 0 0-.457.207.834.834 0 0 0-.15.271c-.04.111-.062.244-.065.398H3.67c.003-.401.067-.745.19-1.032a1.96 1.96 0 0 1 .5-.707c.208-.185.455-.32.738-.406A3.13 3.13 0 0 1 6.012 2c.359 0 .682.046.968.137.287.091.53.227.729.406.2.18.352.401.457.667.105.265.158.571.158.919 0 .233-.03.44-.091.624-.061.182-.145.353-.252.51-.107.158-.233.311-.378.46-.145.149-.3.306-.465.47a2.084 2.084 0 0 0-.24.275c-.063.09-.115.183-.152.282a1.57 1.57 0 0 0-.084.323 2.966 2.966 0 0 0-.033.384H5.216zm-.202 1.634a.96.96 0 0 1 .067-.36.828.828 0 0 1 .19-.287.913.913 0 0 1 .291-.191.969.969 0 0 1 .376-.07c.138 0 .263.023.375.07.112.046.21.11.292.19.082.081.146.177.19.288a.96.96 0 0 1 .067.36.96.96 0 0 1-.067.36.828.828 0 0 1-.19.288.913.913 0 0 1-.292.191.969.969 0 0 1-.375.07.969.969 0 0 1-.376-.07.913.913 0 0 1-.291-.19.828.828 0 0 1-.19-.288.96.96 0 0 1-.067-.36z\" fill=\"rgb(117,117,117)\"/></svg>') no-repeat 50% 50%;\n}\n\n.subitem__help-toggle:hover {\n  border-color: var(--secondary-text-color);\n}\n\n.subitem__help-toggle:checked {\n  background-color: var(--accent-color);\n  border-color: var(--accent-color);\n}\n\n.subitem__help-toggle:checked + label::after {\n  content: '';\n  background: url('data:image/svg+xml;utf8,<svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" xmlns=\"http://www.w3.org/2000/svg\"><title>help</title><path d=\"M5.216 7.457c0-.237.011-.452.033-.645.021-.194.058-.372.11-.535a1.918 1.918 0 0 1 .55-.847 3.65 3.65 0 0 0 .545-.597c.133-.19.2-.398.2-.623 0-.28-.053-.485-.16-.616-.107-.13-.268-.196-.482-.196a.583.583 0 0 0-.457.207.834.834 0 0 0-.15.271c-.04.111-.062.244-.065.398H3.67c.003-.401.067-.745.19-1.032a1.96 1.96 0 0 1 .5-.707c.208-.185.455-.32.738-.406A3.13 3.13 0 0 1 6.012 2c.359 0 .682.046.968.137.287.091.53.227.729.406.2.18.352.401.457.667.105.265.158.571.158.919 0 .233-.03.44-.091.624-.061.182-.145.353-.252.51-.107.158-.233.311-.378.46-.145.149-.3.306-.465.47a2.084 2.084 0 0 0-.24.275c-.063.09-.115.183-.152.282a1.57 1.57 0 0 0-.084.323 2.966 2.966 0 0 0-.033.384H5.216zm-.202 1.634a.96.96 0 0 1 .067-.36.828.828 0 0 1 .19-.287.913.913 0 0 1 .291-.191.969.969 0 0 1 .376-.07c.138 0 .263.023.375.07.112.046.21.11.292.19.082.081.146.177.19.288a.96.96 0 0 1 .067.36.96.96 0 0 1-.067.36.828.828 0 0 1-.19.288.913.913 0 0 1-.292.191.969.969 0 0 1-.375.07.969.969 0 0 1-.376-.07.913.913 0 0 1-.291-.19.828.828 0 0 1-.19-.288.96.96 0 0 1-.067-.36z\" fill=\"white\"/></svg>') no-repeat 50% 50%;\n  background-size: contain;\n  border-radius: 50%;\n  background-color: var(--accent-color);\n}\n\n.subitem__help {\n  display: none;\n  font-size: var(--body-font-size);\n  line-height: var(--body-line-height);\n  margin-top: calc(var(--body-line-height) / 2);\n  margin-left: var(--subitem-indent);\n}\n\n.subitem__help-toggle:checked ~ .subitem__help {\n  display: block;\n}\n\n.subitem__debug {\n  font-size: var(--body-font-size);\n  line-height: var(--body-line-height);\n  margin-top: calc(var(--body-line-height) / 2);\n  margin-left: var(--subitem-indent);\n  color: var(--poor-color);\n}\n\n.score-good-bg {\n  background-color: var(--good-color);\n}\n.score-average-bg {\n  background-color: var(--average-color);\n}\n.score-poor-bg {\n  background-color: var(--poor-color);\n}\n.score-warning-bg {\n  background-color: var(--warning-color);\n}\n\n.export-section {\n  position: relative;\n}\n\n.export-button {\n  display: inline-flex;\n  background-color: #fff;\n  border: 1px solid #ccc;\n  box-sizing: border-box;\n  min-width: 5.14em;\n  padding: 0.7em 1.1em;\n  letter-spacing: 0.02em;\n  border-radius: 3px;\n  cursor: pointer;\n  color: var(--secondary-text-color);\n  outline: none;\n  font-weight: 500;\n}\n\n.export-dropdown {\n  position: absolute;\n  background-color: var(--report-header-bg-color);\n  border: 1px solid #ccc;\n  border-radius: 3px;\n  margin: 0;\n  padding: 8px 0;\n  cursor: pointer;\n  top: 36px;\n  right: 0;\n  z-index: 1;\n  box-shadow: 1px 1px 3px #ccc;\n  min-width: 125px;\n  list-style-type: none;\n  line-height: 1.5em;\n  clip: rect(0, 164px, 0, 0);\n  opacity: 0;\n  transition: all 200ms cubic-bezier(0,0,0.2,1);\n}\n\n.export-button:focus,\n.export-button.active {\n  box-shadow: 1px 1px 3px #ccc;\n}\n\n.export-button.active + .export-dropdown {\n  opacity: 1;\n  clip: rect(0, 164px, 200px, 0);\n}\n\n.export-dropdown a {\n  display: block;\n  color: currentColor;\n  text-decoration: none;\n  white-space: nowrap;\n  padding: 0 12px;\n  line-height: 1.8;\n}\n\n.export-dropdown a:hover,\n.export-dropdown a:focus {\n  background-color: #efefef;\n  outline: none;\n}\n\n.export-dropdown .report__icon {\n  background-repeat: no-repeat;\n  background-position: 8px 50%;\n  background-size: 18px;\n  text-indent: 18px;\n}\n\n/* copy icon needs slight adjustments to look great */\n.export-dropdown .report__icon.copy {\n  background-size: 16px;\n  background-position: 9px 50%;\n}\n\n.log-wrapper {\n  position: fixed;\n  top: 0;\n  display: flex;\n  align-content: center;\n  width: 100%;\n  pointer-events: none;\n}\n#log {\n  margin: 0 auto;\n  padding: 16px 32px;\n  border-bottom-left-radius: 5px;\n  border-bottom-right-radius: 5px;\n  background-color: rgba(0,0,0,0.6);\n  max-width: 500px;\n  line-height: 1.4;\n  color: #fff; /*#e53935;*/\n  font-size: 16px;\n  transition: transform 300ms ease-in-out;\n  transform: translateY(-100%);\n}\n#log.show {\n  transform: translateY(0);\n}\n\n@media print {\n  body {\n    -webkit-print-color-adjust: exact; /* print background colors */\n  }\n\n  .report {\n    box-shadow: none;\n  }\n\n  .report-body__header-container,\n  .report-body__menu-container {\n    display: none;\n  }\n\n  .report-body__content {\n    margin-left: 0;\n  }\n\n  .log-wrapper {\n    display: none;\n  }\n}\n\n@media screen and (max-width: 400px) {\n  .report-body__metadata {\n    margin-right: 8px;\n    max-width: 65%;\n  }\n}\n\n@media screen and (max-width: 767px) {\n  :root {\n    --subitem-indent: 8px;\n    --gutter-width: 16px;\n  }\n  .aggregations {\n    padding-right: 8px;\n  }\n  .report-body__menu-container {\n    display: none;\n  }\n  .report-body__content,\n  .report-body__fixed-footer-container {\n    margin-left: 0;\n  }\n  .report-body__header,\n  .report-body__fixed-footer-container {\n    width: 100%;\n  }\n  .report-body__header {\n    padding-right: 8px;\n  }\n  .export-dropdown {\n    right: 0;\n    left: initial;\n  }\n  .footer {\n    margin-top: 0;\n    margin-left: 0;\n    height: auto;\n  }\n}\n\n:root[data-report-context=\"devtools\"] .report {\n  margin: 10px 10px;\n  padding: 10px;\n  box-shadow: none;\n  max-width: none;\n  width: auto;\n}\n\n:root[data-report-context=\"devtools\"] .report-body__aggregations-container > section:first-child {\n  padding-top: calc(var(--heading-line-height) / 3);\n}\n:root[data-report-context=\"devtools\"] .report-body__menu-container {\n  display: none;\n}\n\n:root[data-report-context=\"devtools\"] .report-body__header-container {\n  display: none;\n}\n\n:root[data-report-context=\"devtools\"] .report-body__content {\n  margin-left: 0;\n}\n\n:root[data-report-context=\"devtools\"] .footer {\n  display: none;\n}\n\n:root[data-report-context=\"viewer\"] .share {\n  display: initial;\n}\n\n/* app z-indexes */\n.log-wrapper {\n  z-index: 3;\n}\n",
-...partialStyles];
-
-}
-
-
-
-
-
-
-getReportJS(reportContext){
-if(reportContext==='devtools'){
-return[];
-}else{
-return[
-"/**\n * @license\n * Copyright 2016 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use strict';\n\n/* eslint-disable no-console */\n\n/**\n * Logs messages via a UI butter.\n * @class\n */\nclass Logger {\n  constructor(selector) {\n    this.el = document.querySelector(selector);\n  }\n\n  /**\n   * Shows a butter bar.\n   * @param {!string} msg The message to show.\n   * @param {boolean=} optAutoHide True to hide the message after a duration.\n   *     Default is true.\n   */\n  log(msg, optAutoHide) {\n    const autoHide = typeof optAutoHide === 'undefined' ? true : optAutoHide;\n\n    clearTimeout(this._id);\n\n    this.el.textContent = msg;\n    this.el.classList.add('show');\n    if (autoHide) {\n      this._id = setTimeout(_ => {\n        this.el.classList.remove('show');\n      }, 7000);\n    }\n  }\n\n  warn(msg) {\n    this.log('Warning: ' + msg);\n    console.warn(msg);\n  }\n\n  error(msg) {\n    this.log(msg);\n    console.error(msg);\n  }\n\n  /**\n   * Explicitly hides the butter bar.\n   */\n  hide() {\n    clearTimeout(this._id);\n    this.el.classList.remove('show');\n  }\n}\n\nif (typeof module !== 'undefined' && module.exports) {\n  module.exports = Logger;\n}\n",
-"/**\n * @license\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use strict';\n\n/**\n * Generate a filenamePrefix of hostname_YYYY-MM-DD_HH-MM-SS\n * Date/time uses the local timezone, however Node has unreliable ICU\n * support, so we must construct a YYYY-MM-DD date format manually. :/\n * @param {!Object} results\n * @returns string\n */\nfunction getFilenamePrefix(results) {\n  // eslint-disable-next-line no-undef\n  const hostname = new (URLConstructor || URL)(results.url).hostname;\n  const date = (results.generatedTime && new Date(results.generatedTime)) || new Date();\n\n  const timeStr = date.toLocaleTimeString('en-US', {hour12: false});\n  const dateParts = date.toLocaleDateString('en-US', {\n    year: 'numeric', month: '2-digit', day: '2-digit'\n  }).split('/');\n  dateParts.unshift(dateParts.pop());\n  const dateStr = dateParts.join('-');\n\n  const filenamePrefix = `${hostname}_${dateStr}_${timeStr}`;\n  // replace characters that are unfriendly to filenames\n  return filenamePrefix.replace(/[\\/\\?<>\\\\:\\*\\|\":]/g, '-');\n}\n\nlet URLConstructor;\nif (typeof module !== 'undefined' && module.exports) {\n  URLConstructor = require('./url-shim');\n\n  module.exports = {\n    getFilenamePrefix\n  };\n}\n",
-"/**\n * @license\n * Copyright 2016 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* global ga, logger */\n\n'use strict';\n\nclass LighthouseReport {\n\n  /**\n   * @param {Object=} lhresults Lighthouse JSON results.\n   */\n  constructor(lhresults) {\n    this.json = lhresults || null;\n    this._copyAttempt = false;\n\n    this.onCopy = this.onCopy.bind(this);\n    this.onExportButtonClick = this.onExportButtonClick.bind(this);\n    this.onExport = this.onExport.bind(this);\n    this.onKeyDown = this.onKeyDown.bind(this);\n\n    this._addEventListeners();\n  }\n\n  _addEventListeners() {\n    this._setupExpandDetailsWhenPrinting();\n\n    const headerContainer = document.querySelector('.js-header-container');\n    if (headerContainer) {\n      const toggleButton = headerContainer.querySelector('.js-header-toggle');\n      toggleButton.addEventListener('click', () => headerContainer.classList.toggle('expanded'));\n    }\n\n    this.exportButton = document.querySelector('.js-export');\n    if (this.exportButton) {\n      this.exportButton.addEventListener('click', this.onExportButtonClick);\n      const dropdown = document.querySelector('.export-dropdown');\n      dropdown.addEventListener('click', this.onExport);\n\n      document.addEventListener('copy', this.onCopy);\n    }\n  }\n\n  /**\n   * Handler copy events.\n   */\n  onCopy(e) {\n    // Only handle copy button presses (e.g. ignore the user copying page text).\n    if (this._copyAttempt) {\n      // We want to write our own data to the clipboard, not the user's text selection.\n      e.preventDefault();\n      e.clipboardData.setData('text/plain', JSON.stringify(this.json, null, 2));\n      logger.log('Report JSON copied to clipboard');\n    }\n\n    this._copyAttempt = false;\n  }\n\n  /**\n   * Copies the report JSON to the clipboard (if supported by the browser).\n   */\n  onCopyButtonClick() {\n    if (window.ga) {\n      ga('send', 'event', 'report', 'copy');\n    }\n\n    try {\n      if (document.queryCommandSupported('copy')) {\n        this._copyAttempt = true;\n\n        // Note: In Safari 10.0.1, execCommand('copy') returns true if there's\n        // a valid text selection on the page. See http://caniuse.com/#feat=clipboard.\n        const successful = document.execCommand('copy');\n        if (!successful) {\n          this._copyAttempt = false; // Prevent event handler from seeing this as a copy attempt.\n          logger.warn('Your browser does not support copy to clipboard.');\n        }\n      }\n    } catch (err) {\n      this._copyAttempt = false;\n      logger.log(err.message);\n    }\n  }\n\n  closeExportDropdown() {\n    this.exportButton.classList.remove('active');\n  }\n\n  /**\n   * Click handler for export button.\n   */\n  onExportButtonClick(e) {\n    e.preventDefault();\n    e.target.classList.toggle('active');\n    document.addEventListener('keydown', this.onKeyDown);\n  }\n\n  /**\n   * Handler for \"export as\" button.\n   */\n  onExport(e) {\n    e.preventDefault();\n\n    if (!e.target.dataset.action) {\n      return;\n    }\n\n    switch (e.target.dataset.action) {\n      case 'copy':\n        this.onCopyButtonClick();\n        break;\n      case 'open-viewer':\n        this.sendJSONReport();\n        break;\n      case 'print':\n        window.print();\n        break;\n      case 'save-json': {\n        const jsonStr = JSON.stringify(this.json, null, 2);\n        this._saveFile(new Blob([jsonStr], {type: 'application/json'}));\n        break;\n      }\n      case 'save-html': {\n        let htmlStr = '';\n\n        // Since Viewer generates its page HTML dynamically from report JSON,\n        // run the ReportGenerator. For everything else, the page's HTML is\n        // already the final product.\n        if (e.target.dataset.context !== 'viewer') {\n          htmlStr = document.documentElement.outerHTML;\n        } else {\n          const reportGenerator = new ReportGenerator();\n          htmlStr = reportGenerator.generateHTML(this.json, 'cli');\n        }\n\n        try {\n          this._saveFile(new Blob([htmlStr], {type: 'text/html'}));\n        } catch (err) {\n          logger.error('Could not export as HTML. ' + err.message);\n        }\n        break;\n      }\n    }\n\n    this.closeExportDropdown();\n    document.removeEventListener('keydown', this.onKeyDown);\n  }\n\n  /**\n   * Keydown handler for the document.\n   */\n  onKeyDown(e) {\n    if (e.keyCode === 27) { // ESC\n      this.closeExportDropdown();\n    }\n  }\n\n  /**\n   * Opens a new tab to the online viewer and sends the local page's JSON results\n   * to the online viewer using postMessage.\n   */\n  sendJSONReport() {\n    const VIEWER_ORIGIN = 'https://googlechrome.github.io';\n    const VIEWER_URL = `${VIEWER_ORIGIN}/lighthouse/viewer/`;\n\n    // Chrome doesn't allow us to immediately postMessage to a popup right\n    // after it's created. Normally, we could also listen for the popup window's\n    // load event, however it is cross-domain and won't fire. Instead, listen\n    // for a message from the target app saying \"I'm open\".\n    window.addEventListener('message', function msgHandler(e) {\n      if (e.origin !== VIEWER_ORIGIN) {\n        return;\n      }\n\n      if (e.data.opened) {\n        popup.postMessage({lhresults: this.json}, VIEWER_ORIGIN);\n        window.removeEventListener('message', msgHandler);\n      }\n    }.bind(this));\n\n    const popup = window.open(VIEWER_URL, '_blank');\n  }\n\n  /**\n   * Sets up listeners to expand audit `<details>` when the user prints the page.\n   * Ideally, a print stylesheet could take care of this, but CSS has no way to\n   * open a `<details>` element. When the user closes the print dialog, all\n   * `<details>` are collapsed.\n   */\n  _setupExpandDetailsWhenPrinting() {\n    const details = Array.from(document.querySelectorAll('details'));\n\n    // FF and IE implement these old events.\n    if ('onbeforeprint' in window) {\n      window.addEventListener('beforeprint', _ => {\n        details.map(detail => detail.open = true);\n      });\n      window.addEventListener('afterprint', _ => {\n        details.map(detail => detail.open = false);\n      });\n    } else {\n      // Note: while FF has media listeners, it doesn't fire when matching 'print'.\n      window.matchMedia('print').addListener(mql => {\n        details.map(detail => detail.open = mql.matches);\n      });\n    }\n  }\n\n  /**\n   * Downloads a file (blob) using a[download].\n   * @param {Blob|File} blob The file to save.\n   */\n  _saveFile(blob) {\n    const filename = window.getFilenamePrefix({\n      url: this.json.url,\n      generatedTime: this.json.generatedTime\n    });\n\n    const ext = blob.type.match('json') ? '.json' : '.html';\n\n    const a = document.createElement('a');\n    a.download = `${filename}${ext}`;\n    a.href = URL.createObjectURL(blob);\n    document.body.appendChild(a); // Firefox requires anchor to be in the DOM.\n    a.click();\n\n    // cleanup.\n    document.body.removeChild(a);\n    setTimeout(_ => URL.revokeObjectURL(a.href), 500);\n  }\n}\n\n// Exports for Node usage (Viewer browserifies).\nlet ReportGenerator;\nif (typeof module !== 'undefined' && module.exports) {\n  module.exports = LighthouseReport;\n  ReportGenerator = require('../../../lighthouse-core/report/report-generator');\n  window.getFilenamePrefix = require('../../../lighthouse-core/lib/file-namer').getFilenamePrefix;\n}\n"];
-
-}
-}
-
-
-
-
-
-_createPWAAuditsByCategory(aggregations){
-const items={};
-
-aggregations.forEach(aggregation=>{
-
-
-if(!aggregation.categorizable){
-return;
-}
-
-aggregation.score.forEach(score=>{
-score.subItems.forEach(subItem=>{
-
-if(!items[subItem.category]){
-items[subItem.category]={};
-}
-
-
-if(!items[subItem.category][subItem.name]){
-items[subItem.category][subItem.name]=subItem;
-}
-});
-});
-});
-
-return items;
-}
-
-
-
-
-
-
-
-renderException(err,results){
-const template=reportTemplate.report.templates.exception;
-return template({
-errMessage:err.message,
-errStack:err.stack,
-css:this.getReportCSS(),
-results:JSON.stringify(results,null,2)});
-
-}
-
-
-
-
-
-_registerPartials(audits){
-Object.keys(audits).forEach(auditName=>{
-const audit=audits[auditName];
-
-if(!audit.extendedInfo){
-return;
-}
-
-const partialName=audit.extendedInfo.formatter;
-const partial=reportPartials.report.partials[partialName];
-if(!partial){
-throw new Error(`${auditName} requested unknown partial for formatting`);
-}
-
-Handlebars.registerPartial(audit.name,Handlebars.template(partial));
-});
-}
-
-
-
-
-
-
-
-
-generateHTML(results,reportContext='extension',reportsCatalog){
-this._registerPartials(results.audits);
-
-results.aggregations.forEach(aggregation=>{
-aggregation.score.forEach(score=>{
-
-
-score.subItems=score.subItems.map(subItem=>results.audits[subItem]||subItem);
-});
-});
-
-const template=Handlebars.template(reportTemplate.report.templates['report-template']);
-
-return template({
-url:results.url,
-lighthouseVersion:results.lighthouseVersion,
-generatedTime:results.generatedTime,
-lhresults:this._escapeScriptTags(JSON.stringify(results,null,2)),
-stylesheets:this.getReportCSS(),
-reportContext:reportContext,
-scripts:this.getReportJS(reportContext),
-aggregations:results.aggregations,
-auditsByCategory:this._createPWAAuditsByCategory(results.aggregations),
-runtimeConfig:results.runtimeConfig,
-reportsCatalog});
-
-}}
-
-
-module.exports=ReportGenerator;
-
-},{"../report/partials/templates/report-partials":29,"./handlebar-helpers":28,"./templates/report-templates":31,"handlebars/runtime":264,"path":203}],31:[function(require,module,exports){
-this["report"]=this["report"]||{};
-this["report"]["templates"]=this["report"]["templates"]||{};
-this["report"]["templates"]["exception"]={"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data){
-var stack1,helper,alias1=depth0!=null?depth0:{},alias2=helpers.helperMissing,alias3="function";
-
-return"<!--\n\nCopyright 2016 Google Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n-->\n<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width\">\n  <title>Lighthouse report - error</title>\n  <style>"+(
-(stack1=(helper=(helper=helpers.css||(depth0!=null?depth0.css:depth0))!=null?helper:alias2,typeof helper===alias3?helper.call(alias1,{"name":"css","hash":{},"data":data}):helper))!=null?stack1:"")+
-"</style>\n</head>\n<body>\n\n  <div class=\"js-report report\">\n    <section class=\"report-body\">\n      <div class=\"report-body__header\"></div>\n\n      <div class=\"report-body__content\">\n\n        <div class=\"report-error\">\n          <h1 class=\"error-message\">⚠️ Error: "+(
-(stack1=(helper=(helper=helpers.errMessage||(depth0!=null?depth0.errMessage:depth0))!=null?helper:alias2,typeof helper===alias3?helper.call(alias1,{"name":"errMessage","hash":{},"data":data}):helper))!=null?stack1:"")+
-"</h1>\n          <p class=\"error-stack\"> "+(
-(stack1=(helper=(helper=helpers.errStack||(depth0!=null?depth0.errStack:depth0))!=null?helper:alias2,typeof helper===alias3?helper.call(alias1,{"name":"errStack","hash":{},"data":data}):helper))!=null?stack1:"")+
-"</p>\n          <big>\n            ➡ <a href=\"https://github.com/GoogleChrome/lighthouse/issues\" target=\"_blank\" rel=\"noopener\">Please report this bug</a>\n          </big>\n          <div class=\"error-results\"><pre>"+(
-(stack1=(helper=(helper=helpers.results||(depth0!=null?depth0.results:depth0))!=null?helper:alias2,typeof helper===alias3?helper.call(alias1,{"name":"results","hash":{},"data":data}):helper))!=null?stack1:"")+
-"</pre></div>\n        </div>\n\n      </div>\n\n    </section>\n    <footer class=\"footer\">\n      Generated by <b>Lighthouse</b> on "+
-container.escapeExpression((helper=(helper=helpers.generatedTime||(depth0!=null?depth0.generatedTime:depth0))!=null?helper:alias2,typeof helper===alias3?helper.call(alias1,{"name":"generatedTime","hash":{},"data":data}):helper))+
-" | <a href=\"https://github.com/GoogleChrome/Lighthouse/issues\" target=\"_blank\" rel=\"noopener\">File an issue</a>\n    </footer>\n  </div>\n\n</body>\n</html>\n";
+  return "<!--\n\nCopyright 2016 Google Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n-->\n<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width\">\n  <title>Lighthouse report - error</title>\n  <style>"
+    + ((stack1 = ((helper = (helper = helpers.css || (depth0 != null ? depth0.css : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"css","hash":{},"data":data}) : helper))) != null ? stack1 : "")
+    + "</style>\n</head>\n<body>\n\n  <div class=\"js-report report\">\n    <section class=\"report-body\">\n      <div class=\"report-body__header\"></div>\n\n      <div class=\"report-body__content\">\n\n        <div class=\"report-error\">\n          <h1 class=\"error-message\">⚠️ Error: "
+    + ((stack1 = ((helper = (helper = helpers.errMessage || (depth0 != null ? depth0.errMessage : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"errMessage","hash":{},"data":data}) : helper))) != null ? stack1 : "")
+    + "</h1>\n          <p class=\"error-stack\"> "
+    + ((stack1 = ((helper = (helper = helpers.errStack || (depth0 != null ? depth0.errStack : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"errStack","hash":{},"data":data}) : helper))) != null ? stack1 : "")
+    + "</p>\n          <big>\n            ➡ <a href=\"https://github.com/GoogleChrome/lighthouse/issues\" target=\"_blank\" rel=\"noopener\">Please report this bug</a>\n          </big>\n          <div class=\"error-results\"><pre>"
+    + ((stack1 = ((helper = (helper = helpers.results || (depth0 != null ? depth0.results : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"results","hash":{},"data":data}) : helper))) != null ? stack1 : "")
+    + "</pre></div>\n        </div>\n\n      </div>\n\n    </section>\n    <footer class=\"footer\">\n      Generated by <b>Lighthouse</b> on "
+    + container.escapeExpression(((helper = (helper = helpers.generatedTime || (depth0 != null ? depth0.generatedTime : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"generatedTime","hash":{},"data":data}) : helper)))
+    + " | <a href=\"https://github.com/GoogleChrome/Lighthouse/issues\" target=\"_blank\" rel=\"noopener\">File an issue</a>\n    </footer>\n  </div>\n\n</body>\n</html>\n";
 },"useData":true};
-this["report"]["templates"]["report-template"]={"1":function(container,depth0,helpers,partials,data){
-var stack1;
+this["report"]["templates"]["report-template"] = {"1":function(container,depth0,helpers,partials,data) {
+    var stack1;
 
-return"<ul class=\"menu__nav\">\n"+(
-(stack1=helpers.each.call(depth0!=null?depth0:{},depth0,{"name":"each","hash":{},"fn":container.program(2,data,0),"inverse":container.noop,"data":data}))!=null?stack1:"")+
-"  </ul>";
-},"2":function(container,depth0,helpers,partials,data){
-var alias1=container.escapeExpression;
+  return "<ul class=\"menu__nav\">\n"
+    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},depth0,{"name":"each","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + "  </ul>";
+},"2":function(container,depth0,helpers,partials,data) {
+    var alias1=container.escapeExpression;
 
-return"    <li class=\"menu__nav-item\">\n     <a class=\"menu__link\" href=\"#"+
-alias1((helpers.nameToLink||depth0&&depth0.nameToLink||helpers.helperMissing).call(depth0!=null?depth0:{},depth0!=null?depth0.name:depth0,{"name":"nameToLink","hash":{},"data":data}))+
-"\">\n       "+
-alias1(container.lambda(depth0!=null?depth0.name:depth0,depth0))+
-"\n     </a>\n    </li>\n";
-},"4":function(container,depth0,helpers,partials,data){
-var stack1;
+  return "    <li class=\"menu__nav-item\">\n     <a class=\"menu__link\" href=\"#"
+    + alias1((helpers.nameToLink || (depth0 && depth0.nameToLink) || helpers.helperMissing).call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.name : depth0),{"name":"nameToLink","hash":{},"data":data}))
+    + "\">\n       "
+    + alias1(container.lambda((depth0 != null ? depth0.name : depth0), depth0))
+    + "\n     </a>\n    </li>\n";
+},"4":function(container,depth0,helpers,partials,data) {
+    var stack1;
 
-return"      "+(
-(stack1=container.lambda(depth0,depth0))!=null?stack1:"")+
-"\n";
-},"6":function(container,depth0,helpers,partials,data,blockParams,depths){
-var stack1;
+  return "      "
+    + ((stack1 = container.lambda(depth0, depth0)) != null ? stack1 : "")
+    + "\n";
+},"6":function(container,depth0,helpers,partials,data,blockParams,depths) {
+    var stack1;
 
-return(stack1=helpers.each.call(depth0!=null?depth0:{},(stack1=depth0!=null?depth0.reportsCatalog:depth0)!=null?stack1.reportsMetadata:stack1,{"name":"each","hash":{},"fn":container.program(7,data,0,blockParams,depths),"inverse":container.noop,"data":data}))!=null?stack1:"";
-},"7":function(container,depth0,helpers,partials,data,blockParams,depths){
-var stack1,helper,alias1=depth0!=null?depth0:{},alias2=helpers.helperMissing,alias3="function",alias4=container.escapeExpression;
+  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},((stack1 = (depth0 != null ? depth0.reportsCatalog : depth0)) != null ? stack1.reportsMetadata : stack1),{"name":"each","hash":{},"fn":container.program(7, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+},"7":function(container,depth0,helpers,partials,data,blockParams,depths) {
+    var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
 
-return"              <a class=\"menu__report-tab\" href=\""+
-alias4((helper=(helper=helpers.reportHref||(depth0!=null?depth0.reportHref:depth0))!=null?helper:alias2,typeof helper===alias3?helper.call(alias1,{"name":"reportHref","hash":{},"data":data}):helper))+
-"\">\n                <p class=\"menu__report-tab__url\" title=\""+
-alias4((helper=(helper=helpers.url||(depth0!=null?depth0.url:depth0))!=null?helper:alias2,typeof helper===alias3?helper.call(alias1,{"name":"url","hash":{},"data":data}):helper))+
-"\">Report for: "+
-alias4((helper=(helper=helpers.url||(depth0!=null?depth0.url:depth0))!=null?helper:alias2,typeof helper===alias3?helper.call(alias1,{"name":"url","hash":{},"data":data}):helper))+
-"</p>\n                <p class=\"menu__report-tab__time\">Generated on: "+
-alias4((helpers.formatDateTime||depth0&&depth0.formatDateTime||alias2).call(alias1,depth0!=null?depth0.generatedTime:depth0,{"name":"formatDateTime","hash":{},"data":data}))+
-"</p>\n              </a>\n"+(
-(stack1=(helpers.ifEq||depth0&&depth0.ifEq||alias2).call(alias1,depth0!=null?depth0.reportHref:depth0,(stack1=depths[1]!=null?depths[1].reportsCatalog:depths[1])!=null?stack1.selectedReportHref:stack1,{"name":"ifEq","hash":{},"fn":container.program(8,data,0,blockParams,depths),"inverse":container.noop,"data":data}))!=null?stack1:"");
-},"8":function(container,depth0,helpers,partials,data,blockParams,depths){
-var stack1;
+  return "              <a class=\"menu__report-tab\" href=\""
+    + alias4(((helper = (helper = helpers.reportHref || (depth0 != null ? depth0.reportHref : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"reportHref","hash":{},"data":data}) : helper)))
+    + "\">\n                <p class=\"menu__report-tab__url\" title=\""
+    + alias4(((helper = (helper = helpers.url || (depth0 != null ? depth0.url : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"url","hash":{},"data":data}) : helper)))
+    + "\">Report for: "
+    + alias4(((helper = (helper = helpers.url || (depth0 != null ? depth0.url : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"url","hash":{},"data":data}) : helper)))
+    + "</p>\n                <p class=\"menu__report-tab__time\">Generated on: "
+    + alias4((helpers.formatDateTime || (depth0 && depth0.formatDateTime) || alias2).call(alias1,(depth0 != null ? depth0.generatedTime : depth0),{"name":"formatDateTime","hash":{},"data":data}))
+    + "</p>\n              </a>\n"
+    + ((stack1 = (helpers.ifEq || (depth0 && depth0.ifEq) || alias2).call(alias1,(depth0 != null ? depth0.reportHref : depth0),((stack1 = (depths[1] != null ? depths[1].reportsCatalog : depths[1])) != null ? stack1.selectedReportHref : stack1),{"name":"ifEq","hash":{},"fn":container.program(8, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+},"8":function(container,depth0,helpers,partials,data,blockParams,depths) {
+    var stack1;
 
-return(stack1=container.invokePartial(partials.createNav,depths[1]!=null?depths[1].aggregations:depths[1],{"name":"createNav","data":data,"indent":"                ","helpers":helpers,"partials":partials,"decorators":container.decorators}))!=null?stack1:"";
-},"10":function(container,depth0,helpers,partials,data){
-var stack1;
+  return ((stack1 = container.invokePartial(partials.createNav,(depths[1] != null ? depths[1].aggregations : depths[1]),{"name":"createNav","data":data,"indent":"                ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
+},"10":function(container,depth0,helpers,partials,data) {
+    var stack1;
 
-return(stack1=container.invokePartial(partials.createNav,depth0!=null?depth0.aggregations:depth0,{"name":"createNav","data":data,"indent":"            ","helpers":helpers,"partials":partials,"decorators":container.decorators}))!=null?stack1:"";
-},"12":function(container,depth0,helpers,partials,data){
-var helper;
+  return ((stack1 = container.invokePartial(partials.createNav,(depth0 != null ? depth0.aggregations : depth0),{"name":"createNav","data":data,"indent":"            ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
+},"12":function(container,depth0,helpers,partials,data) {
+    var helper;
 
-return"                  <li><a href=\"#\" class=\"report__icon open\" data-action=\"open-viewer\" data-context=\""+
-container.escapeExpression((helper=(helper=helpers.reportContext||(depth0!=null?depth0.reportContext:depth0))!=null?helper:helpers.helperMissing,typeof helper==="function"?helper.call(depth0!=null?depth0:{},{"name":"reportContext","hash":{},"data":data}):helper))+
-"\">Open in Viewer</a></li>\n";
-},"14":function(container,depth0,helpers,partials,data){
-var stack1,alias1=container.lambda,alias2=container.escapeExpression;
+  return "                  <li><a href=\"#\" class=\"report__icon open\" data-action=\"open-viewer\" data-context=\""
+    + container.escapeExpression(((helper = (helper = helpers.reportContext || (depth0 != null ? depth0.reportContext : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"reportContext","hash":{},"data":data}) : helper)))
+    + "\">Open in Viewer</a></li>\n";
+},"14":function(container,depth0,helpers,partials,data) {
+    var stack1, alias1=container.lambda, alias2=container.escapeExpression;
 
-return"                <li class=\"config-section__item\">\n                  <p class=\"config-section__desc\">\n                    "+
-alias2(alias1(depth0!=null?depth0.name:depth0,depth0))+
-" ("+
-alias2(alias1(depth0!=null?depth0.description:depth0,depth0))+
-"):\n                    <strong> "+(
-(stack1=helpers["if"].call(depth0!=null?depth0:{},depth0!=null?depth0.enabled:depth0,{"name":"if","hash":{},"fn":container.program(15,data,0),"inverse":container.program(17,data,0),"data":data}))!=null?stack1:"")+
-"</strong>\n                  </p>\n                </li>\n";
-},"15":function(container,depth0,helpers,partials,data){
-return"Enabled";
-},"17":function(container,depth0,helpers,partials,data){
-return"Disabled";
-},"19":function(container,depth0,helpers,partials,data){
-var stack1;
+  return "                <li class=\"config-section__item\">\n                  <p class=\"config-section__desc\">\n                    "
+    + alias2(alias1((depth0 != null ? depth0.name : depth0), depth0))
+    + " ("
+    + alias2(alias1((depth0 != null ? depth0.description : depth0), depth0))
+    + "):\n                    <strong> "
+    + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.enabled : depth0),{"name":"if","hash":{},"fn":container.program(15, data, 0),"inverse":container.program(17, data, 0),"data":data})) != null ? stack1 : "")
+    + "</strong>\n                  </p>\n                </li>\n";
+},"15":function(container,depth0,helpers,partials,data) {
+    return "Enabled";
+},"17":function(container,depth0,helpers,partials,data) {
+    return "Disabled";
+},"19":function(container,depth0,helpers,partials,data) {
+    var stack1;
 
-return"            <section class=\"config-section\">\n              <h2 class=\"config-section__title\">Blocked URL Patterns</h2>\n              <ul class=\"config-section__items\">\n"+(
-(stack1=helpers.each.call(depth0!=null?depth0:{},(stack1=depth0!=null?depth0.runtimeConfig:depth0)!=null?stack1.blockedUrlPatterns:stack1,{"name":"each","hash":{},"fn":container.program(20,data,0),"inverse":container.noop,"data":data}))!=null?stack1:"")+
-"              </ul>\n            </section>\n";
-},"20":function(container,depth0,helpers,partials,data){
-var alias1=container.lambda,alias2=container.escapeExpression;
+  return "            <section class=\"config-section\">\n              <h2 class=\"config-section__title\">Blocked URL Patterns</h2>\n              <ul class=\"config-section__items\">\n"
+    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},((stack1 = (depth0 != null ? depth0.runtimeConfig : depth0)) != null ? stack1.blockedUrlPatterns : stack1),{"name":"each","hash":{},"fn":container.program(20, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + "              </ul>\n            </section>\n";
+},"20":function(container,depth0,helpers,partials,data) {
+    var alias1=container.lambda, alias2=container.escapeExpression;
 
-return"                <li class=\"config-section__item js-currently-blocked-url\" data-url-pattern=\""+
-alias2(alias1(depth0,depth0))+
-"\">\n                  <p class=\"config-section__desc\">"+
-alias2(alias1(depth0,depth0))+
-"</p>\n                </li>\n";
-},"22":function(container,depth0,helpers,partials,data,blockParams){
-var stack1,alias1=depth0!=null?depth0:{},alias2=helpers.helperMissing,alias3=container.escapeExpression;
+  return "                <li class=\"config-section__item js-currently-blocked-url\" data-url-pattern=\""
+    + alias2(alias1(depth0, depth0))
+    + "\">\n                  <p class=\"config-section__desc\">"
+    + alias2(alias1(depth0, depth0))
+    + "</p>\n                </li>\n";
+},"22":function(container,depth0,helpers,partials,data,blockParams) {
+    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3=container.escapeExpression;
 
-return"      <section class=\"js-breakdown aggregations\" id=\""+
-alias3((helpers.nameToLink||depth0&&depth0.nameToLink||alias2).call(alias1,depth0!=null?depth0.name:depth0,{"name":"nameToLink","hash":{},"data":data,"blockParams":blockParams}))+
-"\">\n        <header class=\"aggregations__header\">\n          <h2>"+
-alias3(container.lambda(depth0!=null?depth0.name:depth0,depth0))+
-"</h2>\n          <p class=\"aggregations__desc\">"+
-alias3((helpers.sanitize||depth0&&depth0.sanitize||alias2).call(alias1,depth0!=null?depth0.description:depth0,{"name":"sanitize","hash":{},"data":data,"blockParams":blockParams}))+
-"</p>\n"+(
-(stack1=helpers["if"].call(alias1,depth0!=null?depth0.scored:depth0,{"name":"if","hash":{},"fn":container.program(23,data,0,blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"        </header>\n\n        <div class=\"js-report-by-user-feature\">\n"+(
-(stack1=helpers.each.call(alias1,depth0!=null?depth0.score:depth0,{"name":"each","hash":{},"fn":container.program(25,data,1,blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"        </div>\n      </section>\n";
-},"23":function(container,depth0,helpers,partials,data){
-var alias1=depth0!=null?depth0:{},alias2=helpers.helperMissing,alias3=container.escapeExpression;
+  return "      <section class=\"js-breakdown aggregations\" id=\""
+    + alias3((helpers.nameToLink || (depth0 && depth0.nameToLink) || alias2).call(alias1,(depth0 != null ? depth0.name : depth0),{"name":"nameToLink","hash":{},"data":data,"blockParams":blockParams}))
+    + "\">\n        <header class=\"aggregations__header\">\n          <h2>"
+    + alias3(container.lambda((depth0 != null ? depth0.name : depth0), depth0))
+    + "</h2>\n          <p class=\"aggregations__desc\">"
+    + alias3((helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.description : depth0),{"name":"sanitize","hash":{},"data":data,"blockParams":blockParams}))
+    + "</p>\n"
+    + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.scored : depth0),{"name":"if","hash":{},"fn":container.program(23, data, 0, blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "        </header>\n\n        <div class=\"js-report-by-user-feature\">\n"
+    + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.score : depth0),{"name":"each","hash":{},"fn":container.program(25, data, 1, blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "        </div>\n      </section>\n";
+},"23":function(container,depth0,helpers,partials,data) {
+    var alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3=container.escapeExpression;
 
-return"          <div class=\"section-result\">\n            <span class=\"section-result__score score-"+
-alias3((helpers.getTotalScoreRating||depth0&&depth0.getTotalScoreRating||alias2).call(alias1,depth0,{"name":"getTotalScoreRating","hash":{},"data":data}))+
-"-bg\">\n              <span class=\"section-result__points\">"+
-alias3((helpers.getTotalScore||depth0&&depth0.getTotalScore||alias2).call(alias1,depth0,{"name":"getTotalScore","hash":{},"data":data}))+
-"</span>\n              <span class=\"section-result__divider\">/</span>\n              <span class=\"section-result__total\">100</span>\n            </span>\n          </div>\n";
-},"25":function(container,depth0,helpers,partials,data,blockParams){
-var stack1,alias1=depth0!=null?depth0:{},alias2=container.escapeExpression;
+  return "          <div class=\"section-result\">\n            <span class=\"section-result__score score-"
+    + alias3((helpers.getTotalScoreRating || (depth0 && depth0.getTotalScoreRating) || alias2).call(alias1,depth0,{"name":"getTotalScoreRating","hash":{},"data":data}))
+    + "-bg\">\n              <span class=\"section-result__points\">"
+    + alias3((helpers.getTotalScore || (depth0 && depth0.getTotalScore) || alias2).call(alias1,depth0,{"name":"getTotalScore","hash":{},"data":data}))
+    + "</span>\n              <span class=\"section-result__divider\">/</span>\n              <span class=\"section-result__total\">100</span>\n            </span>\n          </div>\n";
+},"25":function(container,depth0,helpers,partials,data,blockParams) {
+    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=container.escapeExpression;
 
-return"            <section class=\"aggregation\" data-rating=\""+
-alias2((helpers.getAggregationScoreRating||depth0&&depth0.getAggregationScoreRating||helpers.helperMissing).call(alias1,(stack1=blockParams[0][0])!=null?stack1.overall:stack1,{"name":"getAggregationScoreRating","hash":{},"data":data,"blockParams":blockParams}))+
-"\"\n                     data-score=\""+
-alias2(container.lambda((stack1=blockParams[0][0])!=null?stack1.overall:stack1,depth0))+
-"\">\n\n"+(
-(stack1=helpers["if"].call(alias1,(stack1=blockParams[0][0])!=null?stack1.name:stack1,{"name":"if","hash":{},"fn":container.program(26,data,0,blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"\n"+(
-(stack1=helpers["if"].call(alias1,(stack1=blockParams[0][0])!=null?stack1.description:stack1,{"name":"if","hash":{},"fn":container.program(29,data,0,blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"\n            <ul class=\"subitems\">\n"+(
-(stack1=helpers.each.call(alias1,(stack1=blockParams[0][0])!=null?stack1.subItems:stack1,{"name":"each","hash":{},"fn":container.program(31,data,1,blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"            </ul>\n          </section>\n"+(
-(stack1=helpers["if"].call(alias1,(stack1=blockParams[0][0])!=null?stack1.name:stack1,{"name":"if","hash":{},"fn":container.program(54,data,0,blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"");
-},"26":function(container,depth0,helpers,partials,data,blockParams){
-var stack1,alias1=depth0!=null?depth0:{},alias2=helpers.helperMissing,alias3=container.escapeExpression;
+  return "            <section class=\"aggregation\" data-rating=\""
+    + alias2((helpers.getAggregationScoreRating || (depth0 && depth0.getAggregationScoreRating) || helpers.helperMissing).call(alias1,((stack1 = blockParams[0][0]) != null ? stack1.overall : stack1),{"name":"getAggregationScoreRating","hash":{},"data":data,"blockParams":blockParams}))
+    + "\"\n                     data-score=\""
+    + alias2(container.lambda(((stack1 = blockParams[0][0]) != null ? stack1.overall : stack1), depth0))
+    + "\">\n\n"
+    + ((stack1 = helpers["if"].call(alias1,((stack1 = blockParams[0][0]) != null ? stack1.name : stack1),{"name":"if","hash":{},"fn":container.program(26, data, 0, blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "\n"
+    + ((stack1 = helpers["if"].call(alias1,((stack1 = blockParams[0][0]) != null ? stack1.description : stack1),{"name":"if","hash":{},"fn":container.program(29, data, 0, blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "\n            <ul class=\"subitems\">\n"
+    + ((stack1 = helpers.each.call(alias1,((stack1 = blockParams[0][0]) != null ? stack1.subItems : stack1),{"name":"each","hash":{},"fn":container.program(31, data, 1, blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "            </ul>\n          </section>\n"
+    + ((stack1 = helpers["if"].call(alias1,((stack1 = blockParams[0][0]) != null ? stack1.name : stack1),{"name":"if","hash":{},"fn":container.program(54, data, 0, blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "");
+},"26":function(container,depth0,helpers,partials,data,blockParams) {
+    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3=container.escapeExpression;
 
-return"            <details class=\"aggregation__details\" "+(
-(stack1=(helpers.ifNotEq||depth0&&depth0.ifNotEq||alias2).call(alias1,(stack1=blockParams[1][0])!=null?stack1.overall:stack1,1,{"name":"ifNotEq","hash":{},"fn":container.program(27,data,0,blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-">\n              <summary>\n                <header class=\"aggregation__header\">\n                  <span class=\"aggregation__header__score score-"+
-alias3((helpers.getAggregationScoreRating||depth0&&depth0.getAggregationScoreRating||alias2).call(alias1,(stack1=blockParams[1][0])!=null?stack1.overall:stack1,{"name":"getAggregationScoreRating","hash":{},"data":data,"blockParams":blockParams}))+
-"-bg subitem-result__"+
-alias3((helpers.getAggregationScoreRating||depth0&&depth0.getAggregationScoreRating||alias2).call(alias1,(stack1=blockParams[1][0])!=null?stack1.overall:stack1,{"name":"getAggregationScoreRating","hash":{},"data":data,"blockParams":blockParams}))+
-"\"></span>\n                  <h2>"+
-alias3(container.lambda((stack1=blockParams[1][0])!=null?stack1.name:stack1,depth0))+
-"</h2>\n                  <div class=\"report-body__more-toggle\" title=\"See audits\"></div>\n                </header>\n              </summary>\n";
-},"27":function(container,depth0,helpers,partials,data){
-return"open";
-},"29":function(container,depth0,helpers,partials,data,blockParams){
-var stack1;
+  return "            <details class=\"aggregation__details\" "
+    + ((stack1 = (helpers.ifNotEq || (depth0 && depth0.ifNotEq) || alias2).call(alias1,((stack1 = blockParams[1][0]) != null ? stack1.overall : stack1),1,{"name":"ifNotEq","hash":{},"fn":container.program(27, data, 0, blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + ">\n              <summary>\n                <header class=\"aggregation__header\">\n                  <span class=\"aggregation__header__score score-"
+    + alias3((helpers.getAggregationScoreRating || (depth0 && depth0.getAggregationScoreRating) || alias2).call(alias1,((stack1 = blockParams[1][0]) != null ? stack1.overall : stack1),{"name":"getAggregationScoreRating","hash":{},"data":data,"blockParams":blockParams}))
+    + "-bg subitem-result__"
+    + alias3((helpers.getAggregationScoreRating || (depth0 && depth0.getAggregationScoreRating) || alias2).call(alias1,((stack1 = blockParams[1][0]) != null ? stack1.overall : stack1),{"name":"getAggregationScoreRating","hash":{},"data":data,"blockParams":blockParams}))
+    + "\"></span>\n                  <h2>"
+    + alias3(container.lambda(((stack1 = blockParams[1][0]) != null ? stack1.name : stack1), depth0))
+    + "</h2>\n                  <div class=\"report-body__more-toggle\" title=\"See audits\"></div>\n                </header>\n              </summary>\n";
+},"27":function(container,depth0,helpers,partials,data) {
+    return "open";
+},"29":function(container,depth0,helpers,partials,data,blockParams) {
+    var stack1;
 
-return"              <p class=\"aggregation__desc\">"+
-container.escapeExpression((helpers.sanitize||depth0&&depth0.sanitize||helpers.helperMissing).call(depth0!=null?depth0:{},(stack1=blockParams[1][0])!=null?stack1.description:stack1,{"name":"sanitize","hash":{},"data":data,"blockParams":blockParams}))+
-"</p>\n";
-},"31":function(container,depth0,helpers,partials,data,blockParams){
-var stack1,alias1=depth0!=null?depth0:{},alias2=helpers.helperMissing;
+  return "              <p class=\"aggregation__desc\">"
+    + container.escapeExpression((helpers.sanitize || (depth0 && depth0.sanitize) || helpers.helperMissing).call(depth0 != null ? depth0 : {},((stack1 = blockParams[1][0]) != null ? stack1.description : stack1),{"name":"sanitize","hash":{},"data":data,"blockParams":blockParams}))
+    + "</p>\n";
+},"31":function(container,depth0,helpers,partials,data,blockParams) {
+    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;
 
-return"                <li class=\"subitem "+(
-(stack1=helpers["if"].call(alias1,(helpers.shouldShowHelpText||depth0&&depth0.shouldShowHelpText||alias2).call(alias1,(stack1=blockParams[0][0])!=null?stack1.score:stack1,{"name":"shouldShowHelpText","hash":{},"data":data,"blockParams":blockParams}),{"name":"if","hash":{},"fn":container.program(32,data,0,blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"\">\n\n                  <p class=\"subitem__desc\">\n\n                    "+
-container.escapeExpression((helpers.sanitize||depth0&&depth0.sanitize||alias2).call(alias1,(stack1=blockParams[0][0])!=null?stack1.description:stack1,{"name":"sanitize","hash":{},"data":data,"blockParams":blockParams}))+(
-(stack1=helpers["if"].call(alias1,(helpers.and||depth0&&depth0.and||alias2).call(alias1,(stack1=blockParams[0][0])!=null?stack1.displayValue:stack1,(helpers.not||depth0&&depth0.not||alias2).call(alias1,(helpers.isBool||depth0&&depth0.isBool||alias2).call(alias1,(stack1=blockParams[0][0])!=null?stack1.displayValue:stack1,{"name":"isBool","hash":{},"data":data,"blockParams":blockParams}),{"name":"not","hash":{},"data":data,"blockParams":blockParams}),{"name":"and","hash":{},"data":data,"blockParams":blockParams}),{"name":"if","hash":{},"fn":container.program(34,data,0,blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"\n"+(
-(stack1=helpers["if"].call(alias1,(stack1=blockParams[0][0])!=null?stack1.optimalValue:stack1,{"name":"if","hash":{},"fn":container.program(36,data,0,blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"\n"+(
-(stack1=helpers["if"].call(alias1,(stack1=blockParams[0][0])!=null?stack1.helpText:stack1,{"name":"if","hash":{},"fn":container.program(38,data,0,blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"                  </p>\n\n"+(
-(stack1=helpers["if"].call(alias1,(stack1=blockParams[0][0])!=null?stack1.debugString:stack1,{"name":"if","hash":{},"fn":container.program(40,data,0,blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"\n                  <div class=\"subitem-result\">\n"+(
-(stack1=helpers["if"].call(alias1,(stack1=blockParams[0][0])!=null?stack1.error:stack1,{"name":"if","hash":{},"fn":container.program(42,data,0,blockParams),"inverse":container.program(44,data,0,blockParams),"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"                  </div>"+(
-(stack1=helpers["if"].call(alias1,(stack1=(stack1=blockParams[0][0])!=null?stack1.extendedInfo:stack1)!=null?stack1.value:stack1,{"name":"if","hash":{},"fn":container.program(52,data,0,blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"                </li>\n";
-},"32":function(container,depth0,helpers,partials,data){
-return"--show-help";
-},"34":function(container,depth0,helpers,partials,data,blockParams){
-var stack1;
+  return "                <li class=\"subitem "
+    + ((stack1 = helpers["if"].call(alias1,(helpers.shouldShowHelpText || (depth0 && depth0.shouldShowHelpText) || alias2).call(alias1,((stack1 = blockParams[0][0]) != null ? stack1.score : stack1),{"name":"shouldShowHelpText","hash":{},"data":data,"blockParams":blockParams}),{"name":"if","hash":{},"fn":container.program(32, data, 0, blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "\">\n\n                  <p class=\"subitem__desc\">\n\n                    "
+    + container.escapeExpression((helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,((stack1 = blockParams[0][0]) != null ? stack1.description : stack1),{"name":"sanitize","hash":{},"data":data,"blockParams":blockParams}))
+    + ((stack1 = helpers["if"].call(alias1,(helpers.and || (depth0 && depth0.and) || alias2).call(alias1,((stack1 = blockParams[0][0]) != null ? stack1.displayValue : stack1),(helpers.not || (depth0 && depth0.not) || alias2).call(alias1,(helpers.isBool || (depth0 && depth0.isBool) || alias2).call(alias1,((stack1 = blockParams[0][0]) != null ? stack1.displayValue : stack1),{"name":"isBool","hash":{},"data":data,"blockParams":blockParams}),{"name":"not","hash":{},"data":data,"blockParams":blockParams}),{"name":"and","hash":{},"data":data,"blockParams":blockParams}),{"name":"if","hash":{},"fn":container.program(34, data, 0, blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "\n"
+    + ((stack1 = helpers["if"].call(alias1,((stack1 = blockParams[0][0]) != null ? stack1.optimalValue : stack1),{"name":"if","hash":{},"fn":container.program(36, data, 0, blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "\n"
+    + ((stack1 = helpers["if"].call(alias1,((stack1 = blockParams[0][0]) != null ? stack1.helpText : stack1),{"name":"if","hash":{},"fn":container.program(38, data, 0, blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "                  </p>\n\n"
+    + ((stack1 = helpers["if"].call(alias1,((stack1 = blockParams[0][0]) != null ? stack1.debugString : stack1),{"name":"if","hash":{},"fn":container.program(40, data, 0, blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "\n                  <div class=\"subitem-result\">\n"
+    + ((stack1 = helpers["if"].call(alias1,((stack1 = blockParams[0][0]) != null ? stack1.error : stack1),{"name":"if","hash":{},"fn":container.program(42, data, 0, blockParams),"inverse":container.program(44, data, 0, blockParams),"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "                  </div>"
+    + ((stack1 = helpers["if"].call(alias1,((stack1 = ((stack1 = blockParams[0][0]) != null ? stack1.extendedInfo : stack1)) != null ? stack1.value : stack1),{"name":"if","hash":{},"fn":container.program(52, data, 0, blockParams),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "                </li>\n";
+},"32":function(container,depth0,helpers,partials,data) {
+    return "--show-help";
+},"34":function(container,depth0,helpers,partials,data,blockParams) {
+    var stack1;
 
-return"<strong class=\"subitem__raw-value\">: "+
-container.escapeExpression(container.lambda((stack1=blockParams[1][0])!=null?stack1.displayValue:stack1,depth0))+
-"</strong>\n";
-},"36":function(container,depth0,helpers,partials,data,blockParams){
-var stack1;
+  return "<strong class=\"subitem__raw-value\">: "
+    + container.escapeExpression(container.lambda(((stack1 = blockParams[1][0]) != null ? stack1.displayValue : stack1), depth0))
+    + "</strong>\n";
+},"36":function(container,depth0,helpers,partials,data,blockParams) {
+    var stack1;
 
-return"                      <small>(target: "+
-container.escapeExpression(container.lambda((stack1=blockParams[1][0])!=null?stack1.optimalValue:stack1,depth0))+
-")</small>\n";
-},"38":function(container,depth0,helpers,partials,data,blockParams){
-var stack1;
+  return "                      <small>(target: "
+    + container.escapeExpression(container.lambda(((stack1 = blockParams[1][0]) != null ? stack1.optimalValue : stack1), depth0))
+    + ")</small>\n";
+},"38":function(container,depth0,helpers,partials,data,blockParams) {
+    var stack1;
 
-return"                      <input type=\"checkbox\" class=\"subitem__help-toggle\" title=\"Toggle help text\">\n                      <label class=\"subitem__help-toggle-label\"></label>\n                      <span class=\"subitem__help\">\n                        "+
-container.escapeExpression((helpers.sanitize||depth0&&depth0.sanitize||helpers.helperMissing).call(depth0!=null?depth0:{},(stack1=blockParams[1][0])!=null?stack1.helpText:stack1,{"name":"sanitize","hash":{},"data":data,"blockParams":blockParams}))+
-"\n                      </span>\n";
-},"40":function(container,depth0,helpers,partials,data,blockParams){
-var stack1;
+  return "                      <input type=\"checkbox\" class=\"subitem__help-toggle\" title=\"Toggle help text\">\n                      <label class=\"subitem__help-toggle-label\"></label>\n                      <span class=\"subitem__help\">\n                        "
+    + container.escapeExpression((helpers.sanitize || (depth0 && depth0.sanitize) || helpers.helperMissing).call(depth0 != null ? depth0 : {},((stack1 = blockParams[1][0]) != null ? stack1.helpText : stack1),{"name":"sanitize","hash":{},"data":data,"blockParams":blockParams}))
+    + "\n                      </span>\n";
+},"40":function(container,depth0,helpers,partials,data,blockParams) {
+    var stack1;
 
-return"                    <div class=\"subitem__debug\">\n                      "+
-container.escapeExpression(container.lambda((stack1=blockParams[1][0])!=null?stack1.debugString:stack1,depth0))+
-"\n                    </div>\n";
-},"42":function(container,depth0,helpers,partials,data){
-return"                      <span class=\"subitem-result__unknown score-warning-bg\">N/A</span>\n";
-},"44":function(container,depth0,helpers,partials,data,blockParams){
-var stack1,alias1=depth0!=null?depth0:{};
+  return "                    <div class=\"subitem__debug\">\n                      "
+    + container.escapeExpression(container.lambda(((stack1 = blockParams[1][0]) != null ? stack1.debugString : stack1), depth0))
+    + "\n                    </div>\n";
+},"42":function(container,depth0,helpers,partials,data) {
+    return "                      <span class=\"subitem-result__unknown score-warning-bg\">N/A</span>\n";
+},"44":function(container,depth0,helpers,partials,data,blockParams) {
+    var stack1, alias1=depth0 != null ? depth0 : {};
 
-return(stack1=helpers["if"].call(alias1,(helpers.isBool||depth0&&depth0.isBool||helpers.helperMissing).call(alias1,(stack1=blockParams[1][0])!=null?stack1.score:stack1,{"name":"isBool","hash":{},"data":data,"blockParams":blockParams}),{"name":"if","hash":{},"fn":container.program(45,data,0,blockParams),"inverse":container.program(50,data,0,blockParams),"data":data,"blockParams":blockParams}))!=null?stack1:"";
-},"45":function(container,depth0,helpers,partials,data,blockParams){
-var stack1;
+  return ((stack1 = helpers["if"].call(alias1,(helpers.isBool || (depth0 && depth0.isBool) || helpers.helperMissing).call(alias1,((stack1 = blockParams[1][0]) != null ? stack1.score : stack1),{"name":"isBool","hash":{},"data":data,"blockParams":blockParams}),{"name":"if","hash":{},"fn":container.program(45, data, 0, blockParams),"inverse":container.program(50, data, 0, blockParams),"data":data,"blockParams":blockParams})) != null ? stack1 : "");
+},"45":function(container,depth0,helpers,partials,data,blockParams) {
+    var stack1;
 
-return(stack1=helpers["if"].call(depth0!=null?depth0:{},(stack1=blockParams[2][0])!=null?stack1.score:stack1,{"name":"if","hash":{},"fn":container.program(46,data,0,blockParams),"inverse":container.program(48,data,0,blockParams),"data":data,"blockParams":blockParams}))!=null?stack1:"";
-},"46":function(container,depth0,helpers,partials,data,blockParams){
-var stack1;
+  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},((stack1 = blockParams[2][0]) != null ? stack1.score : stack1),{"name":"if","hash":{},"fn":container.program(46, data, 0, blockParams),"inverse":container.program(48, data, 0, blockParams),"data":data,"blockParams":blockParams})) != null ? stack1 : "");
+},"46":function(container,depth0,helpers,partials,data,blockParams) {
+    var stack1;
 
-return"                          <span class=\"subitem-result__"+
-container.escapeExpression((helpers.getScoreGoodIcon||depth0&&depth0.getScoreGoodIcon||helpers.helperMissing).call(depth0!=null?depth0:{},(stack1=blockParams[3][0])!=null?stack1.informative:stack1,{"name":"getScoreGoodIcon","hash":{},"data":data,"blockParams":blockParams}))+
-" score-good-bg\">Pass</span>\n";
-},"48":function(container,depth0,helpers,partials,data,blockParams){
-var stack1;
+  return "                          <span class=\"subitem-result__"
+    + container.escapeExpression((helpers.getScoreGoodIcon || (depth0 && depth0.getScoreGoodIcon) || helpers.helperMissing).call(depth0 != null ? depth0 : {},((stack1 = blockParams[3][0]) != null ? stack1.informative : stack1),{"name":"getScoreGoodIcon","hash":{},"data":data,"blockParams":blockParams}))
+    + " score-good-bg\">Pass</span>\n";
+},"48":function(container,depth0,helpers,partials,data,blockParams) {
+    var stack1;
 
-return"                          <span class=\"subitem-result__"+
-container.escapeExpression((helpers.getScoreBadIcon||depth0&&depth0.getScoreBadIcon||helpers.helperMissing).call(depth0!=null?depth0:{},(stack1=blockParams[3][0])!=null?stack1.informative:stack1,(stack1=blockParams[5][0])!=null?stack1.additional:stack1,{"name":"getScoreBadIcon","hash":{},"data":data,"blockParams":blockParams}))+
-"\">Fail</span>\n";
-},"50":function(container,depth0,helpers,partials,data,blockParams){
-var stack1,alias1=container.escapeExpression;
+  return "                          <span class=\"subitem-result__"
+    + container.escapeExpression((helpers.getScoreBadIcon || (depth0 && depth0.getScoreBadIcon) || helpers.helperMissing).call(depth0 != null ? depth0 : {},((stack1 = blockParams[3][0]) != null ? stack1.informative : stack1),((stack1 = blockParams[5][0]) != null ? stack1.additional : stack1),{"name":"getScoreBadIcon","hash":{},"data":data,"blockParams":blockParams}))
+    + "\">Fail</span>\n";
+},"50":function(container,depth0,helpers,partials,data,blockParams) {
+    var stack1, alias1=container.escapeExpression;
 
-return"                        <span class=\"subitem-result__points score-"+
-alias1((helpers.getItemRating||depth0&&depth0.getItemRating||helpers.helperMissing).call(depth0!=null?depth0:{},(stack1=blockParams[2][0])!=null?stack1.score:stack1,{"name":"getItemRating","hash":{},"data":data,"blockParams":blockParams}))+
-"-bg\">\n                          "+
-alias1(container.lambda((stack1=blockParams[2][0])!=null?stack1.score:stack1,depth0))+
-"\n                        </span>\n";
-},"52":function(container,depth0,helpers,partials,data,blockParams){
-var stack1;
+  return "                        <span class=\"subitem-result__points score-"
+    + alias1((helpers.getItemRating || (depth0 && depth0.getItemRating) || helpers.helperMissing).call(depth0 != null ? depth0 : {},((stack1 = blockParams[2][0]) != null ? stack1.score : stack1),{"name":"getItemRating","hash":{},"data":data,"blockParams":blockParams}))
+    + "-bg\">\n                          "
+    + alias1(container.lambda(((stack1 = blockParams[2][0]) != null ? stack1.score : stack1), depth0))
+    + "\n                        </span>\n";
+},"52":function(container,depth0,helpers,partials,data,blockParams) {
+    var stack1;
 
-return(stack1=container.invokePartial(helpers.lookup.call(depth0!=null?depth0:{},depth0,"name",{"name":"lookup","hash":{},"data":data}),(stack1=(stack1=blockParams[1][0])!=null?stack1.extendedInfo:stack1)!=null?stack1.value:stack1,{"data":data,"blockParams":blockParams,"helpers":helpers,"partials":partials,"decorators":container.decorators}))!=null?stack1:"";
-},"54":function(container,depth0,helpers,partials,data){
-return"            </details>\n";
-},"56":function(container,depth0,helpers,partials,data){
-var stack1;
+  return ((stack1 = container.invokePartial(helpers.lookup.call(depth0 != null ? depth0 : {},depth0,"name",{"name":"lookup","hash":{},"data":data}),((stack1 = ((stack1 = blockParams[1][0]) != null ? stack1.extendedInfo : stack1)) != null ? stack1.value : stack1),{"data":data,"blockParams":blockParams,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
+},"54":function(container,depth0,helpers,partials,data) {
+    return "            </details>\n";
+},"56":function(container,depth0,helpers,partials,data) {
+    var stack1;
 
-return"    <div class=\"report-body__fixed-footer-container\">\n"+(
-(stack1=container.invokePartial(partials["config-panel"],depth0,{"name":"config-panel","data":data,"indent":"      ","helpers":helpers,"partials":partials,"decorators":container.decorators}))!=null?stack1:"")+
-"    </div>\n";
-},"58":function(container,depth0,helpers,partials,data){
-var helper;
+  return "    <div class=\"report-body__fixed-footer-container\">\n"
+    + ((stack1 = container.invokePartial(partials["config-panel"],depth0,{"name":"config-panel","data":data,"indent":"      ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
+    + "    </div>\n";
+},"58":function(container,depth0,helpers,partials,data) {
+    var helper;
 
-return"<div id=\"lhresults-dump\">\n  "+
-container.escapeExpression((helper=(helper=helpers.lhresults||(depth0!=null?depth0.lhresults:depth0))!=null?helper:helpers.helperMissing,typeof helper==="function"?helper.call(depth0!=null?depth0:{},{"name":"lhresults","hash":{},"data":data}):helper))+
-"\n</div>\n<script>\n  window.lhresults = JSON.parse(document.querySelector('#lhresults-dump').textContent);\n</script>\n";
-},"60":function(container,depth0,helpers,partials,data){
-var stack1;
+  return "<div id=\"lhresults-dump\">\n  "
+    + container.escapeExpression(((helper = (helper = helpers.lhresults || (depth0 != null ? depth0.lhresults : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"lhresults","hash":{},"data":data}) : helper)))
+    + "\n</div>\n<script>\n  window.lhresults = JSON.parse(document.querySelector('#lhresults-dump').textContent);\n</script>\n";
+},"60":function(container,depth0,helpers,partials,data) {
+    var stack1;
 
-return"<script>"+(
-(stack1=helpers.each.call(depth0!=null?depth0:{},depth0!=null?depth0.scripts:depth0,{"name":"each","hash":{},"fn":container.program(61,data,0),"inverse":container.noop,"data":data}))!=null?stack1:"")+
-"</script>\n<script>\n// Init report.\nwindow.addEventListener('DOMContentLoaded', _ => {\n  window.logger = new Logger('#log');\n  new LighthouseReport(window.lhresults);\n});\n</script>";
-},"61":function(container,depth0,helpers,partials,data){
-var stack1;
+  return "<script>"
+    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.scripts : depth0),{"name":"each","hash":{},"fn":container.program(61, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + "</script>\n<script>\n// Init report.\nwindow.addEventListener('DOMContentLoaded', _ => {\n  window.logger = new Logger('#log');\n  new LighthouseReport(window.lhresults);\n});\n</script>";
+},"61":function(container,depth0,helpers,partials,data) {
+    var stack1;
 
-return(stack1=container.lambda(depth0,depth0))!=null?stack1:"";
-},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths){
-var stack1,helper,alias1=depth0!=null?depth0:{},alias2=helpers.helperMissing,alias3="function",alias4=container.escapeExpression;
+  return ((stack1 = container.lambda(depth0, depth0)) != null ? stack1 : "");
+},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {
+    var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
 
-return"<!--\nCopyright 2016 Google Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n--><!doctype html>\n<html lang=\"en\" data-report-context=\""+
-alias4((helper=(helper=helpers.reportContext||(depth0!=null?depth0.reportContext:depth0))!=null?helper:alias2,typeof helper===alias3?helper.call(alias1,{"name":"reportContext","hash":{},"data":data,"blockParams":blockParams}):helper))+
-"\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, minimum-scale=1\">\n  <link rel=\"icon\" href=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAADjklEQVR4AWI08P/HQEvAQrxSQKvlECfLFYXx75xCY2qmh89GbNvOMjb3v9jOOlxnFWxj206ebQ3b7q6q+z1rNagu8/zvPSZACAABpeUAA0miMgU7SA7JjCraFGwZwECOwvL75dWjsKgWBKtx0jvWo+vkBAFbACCkByMP6nMn48+AVgXB2fzSCwsv22/lMGlUhmJ0AE7BH8dyUUDbUEgN6RzJRSeaPxhdRYR0Inel+7Hd5lBiFpkMAxACc0394//9C4voFHDiAAGLpuOXebdfdHfctgwJKaZRLRKy6ItrSis6RBnVBgGtbHyKTEmJHQoEXoBCE5BCrDeA2ogMUIGDAKEBDEhUqwgMqBYDjW4DQzmuffVdqff42/ZQYYqVcMXGZsMPyCsH3lyJSetxvEaxAQXdjR1HjfwCdIS7lo2DZke26Qe+MXO12OWkGT0O6oE7vMGkMnkYw4aN1KQgMKExhXqswfiov4+a7MQ11XPnbr/5qpKlgACAAQj94Lu271bN9DUecQasIZlNzG72llRAAKJiAi+/BSHrSFjRvQhg3DEKEqJh08tsmLTx597+f6enr4cc2Zpk57pihfX24dW7RHcOLLUbJYhJSl0ErQCI9BVXH/XrO97QasuvQQSiECa0BrQCIIJp6X9T/r8QG6L71WYSqCoIIGo2BZDUBnS/D9EA9Nun1iYvbM0MFExIDQRoKFatc1Z6zrm5uWeObJotq0BGV9FuQBWq5a4Fw3PPz848rZHstZSuA5FWAFSMP2nOppOOGpl6qh9PCSg0IFyHKjSQyDNQHTru2t75NOEe0fsf246oAmFkI6vCdnWvbQFQFCKx8vCswV8TrDLiDLgH4Nr7RAtNsrC9d8sfk7b8ls4igdNy8CQKAISlsB0FjZfd3Lfp155tf8fKI4BxZZIj/oTdVEAIAcJFOCmzauHG71I7/rdreUAgAqpDP05fDARCAQQARwEIBQSVxq0FyaLvZZtevpHa8WHw8cft6cpxlq8eAJtIhnSbWDf951yx3y13OqUuu5qyGgkxCgGFh9cDihDGbTa6BqvT1lWmrav3bmt2ZMJ4mU6TGgIC4DBzcv/JqAau1WhzSt3x9Ixk/4Jk/8J4ZrrViFMA4W6A7+WK8xcVjvyrOmVD0FbAXokcT48r+xVqLKvuJYbmpNadnlp3mpufJHOe/GXktM+r09bT8kEdq9BRYAbGSgzP7ll82U71Mc+ZFooXgwAAAABJRU5ErkJggg==\">\n  <title>Lighthouse report: "+
-alias4((helper=(helper=helpers.url||(depth0!=null?depth0.url:depth0))!=null?helper:alias2,typeof helper===alias3?helper.call(alias1,{"name":"url","hash":{},"data":data,"blockParams":blockParams}):helper))+
-"</title>\n  <style>\n"+(
-(stack1=helpers.each.call(alias1,depth0!=null?depth0.stylesheets:depth0,{"name":"each","hash":{},"fn":container.program(4,data,0,blockParams,depths),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"    /*# sourceURL=inline-styles.css */\n  </style>\n</head>\n<body>\n\n<div class=\"js-report report\">\n  <section class=\"report-body\">\n    <div class=\"report-body__content\">\n      <div class=\"report-body__menu-container\">\n        <div class=\"menu\">\n          <div class=\"menu__header\">\n            <h1 class=\"menu__header-title\">Lighthouse</h1>\n            <div class=\"menu__header-version\">Version: "+
-alias4((helper=(helper=helpers.lighthouseVersion||(depth0!=null?depth0.lighthouseVersion:depth0))!=null?helper:alias2,typeof helper===alias3?helper.call(alias1,{"name":"lighthouseVersion","hash":{},"data":data,"blockParams":blockParams}):helper))+
-"</div>\n          </div>\n"+(
-(stack1=helpers["if"].call(alias1,depth0!=null?depth0.reportsCatalog:depth0,{"name":"if","hash":{},"fn":container.program(6,data,0,blockParams,depths),"inverse":container.program(10,data,0,blockParams,depths),"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"        </div>\n      </div>\n      <div class=\"report-body__header-container js-header-container\">\n        <div class=\"report-body__header\">\n          <button class=\"report-body__more-toggle js-header-toggle\" title=\"See report's runtime settings\"></button>\n          <div class=\"report-body__metadata\">\n            <div class=\"report-body__url\">Results for: <a href=\""+
-alias4((helper=(helper=helpers.url||(depth0!=null?depth0.url:depth0))!=null?helper:alias2,typeof helper===alias3?helper.call(alias1,{"name":"url","hash":{},"data":data,"blockParams":blockParams}):helper))+
-"\" target=\"_blank\" rel=\"noopener\">"+
-alias4((helper=(helper=helpers.url||(depth0!=null?depth0.url:depth0))!=null?helper:alias2,typeof helper===alias3?helper.call(alias1,{"name":"url","hash":{},"data":data,"blockParams":blockParams}):helper))+
-"</a></div>\n            <div class=\"report-body__url\">Generated on: "+
-alias4((helpers.formatDateTime||depth0&&depth0.formatDateTime||alias2).call(alias1,depth0!=null?depth0.generatedTime:depth0,{"name":"formatDateTime","hash":{},"data":data,"blockParams":blockParams}))+
-"</div>\n          </div>\n          <div class=\"report-body__buttons\">\n            <div class=\"export-section\">\n              <button class=\"export-button js-export\" title=\"Export report in different formats\">Export...</button>\n              <ul class=\"export-dropdown\">\n                <li><a href=\"#\" class=\"report__icon print\" data-action=\"print\">Print...</a></li>\n                <li><a href=\"#\" class=\"report__icon copy\" data-action=\"copy\">Copy JSON</a></li>\n                <li><a href=\"#\" class=\"report__icon download\" data-action=\"save-html\" data-context=\""+
-alias4((helper=(helper=helpers.reportContext||(depth0!=null?depth0.reportContext:depth0))!=null?helper:alias2,typeof helper===alias3?helper.call(alias1,{"name":"reportContext","hash":{},"data":data,"blockParams":blockParams}):helper))+
-"\">Save as HTML</a></li>\n                <li><a href=\"#\" class=\"report__icon download\" data-action=\"save-json\" data-context=\""+
-alias4((helper=(helper=helpers.reportContext||(depth0!=null?depth0.reportContext:depth0))!=null?helper:alias2,typeof helper===alias3?helper.call(alias1,{"name":"reportContext","hash":{},"data":data,"blockParams":blockParams}):helper))+
-"\">Save as JSON</a></li>\n"+(
-(stack1=(helpers.ifNotEq||depth0&&depth0.ifNotEq||alias2).call(alias1,depth0!=null?depth0.reportContext:depth0,"viewer",{"name":"ifNotEq","hash":{},"fn":container.program(12,data,0,blockParams,depths),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"              </ul>\n            </div>\n            <button class=\"report__icon report-body__icon share js-share\" title=\"Share report on GitHub\"></button>\n          </div>\n          <div class=\"report-body__header-content\">\n            <section class=\"config-section\">\n              <h2 class=\"config-section__title\">Runtime Environment</h2>\n              <ul class=\"config-section__items\">\n"+(
-(stack1=helpers.each.call(alias1,(stack1=depth0!=null?depth0.runtimeConfig:depth0)!=null?stack1.environment:stack1,{"name":"each","hash":{},"fn":container.program(14,data,0,blockParams,depths),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"              </ul>\n            </section>\n"+(
-(stack1=helpers["if"].call(alias1,(stack1=depth0!=null?depth0.runtimeConfig:depth0)!=null?stack1.blockedUrlPatterns:stack1,{"name":"if","hash":{},"fn":container.program(19,data,0,blockParams,depths),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"          </div>\n        </div>\n      </div>\n      <div class=\"report-body__aggregations-container\">\n"+(
-(stack1=helpers.each.call(alias1,depth0!=null?depth0.aggregations:depth0,{"name":"each","hash":{},"fn":container.program(22,data,1,blockParams,depths),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"      </div>\n    </div>\n\n    <footer class=\"footer\">\n      <div class=\"footer__legend\">\n        <h2 class=\"config-section__title\">Legend</h2>\n        <div class=\"legend_panel\">\n          <section class=\"legend_columns single-line\">\n            <div class=\"legend_categories\">\n              <h3 class=\"config-section__title\">Audit categories</h3>\n              <ul class=\"legend_column\">\n                <li>\n                  <span class=\"aggregation__header__score score-good-bg subitem-result__good\">Good</span>\n                  <span class=\"footer__key-description\">Overall category score is 75-100</span>\n                </li>\n                <li>\n                  <span class=\"aggregation__header__score score-average-bg subitem-result__good\">Average</span>\n                  <span class=\"footer__key-description\">Overall category score is 45-74</span>\n                </li>\n                <li>\n                  <span class=\"aggregation__header__score score-poor-bg subitem-result__poor\">Poor</span>\n                  <span class=\"footer__key-description\">Overall category score is < 45</span>\n                </li>\n              </ul>\n            </div>\n          </section>\n          <section class=\"legend_columns\">\n            <div>\n              <h3 class=\"config-section__title\">Scored audits</h3>\n              <ul class=\"legend_column\">\n                <li>\n                  <span class=\"subitem-result__points score-good-bg\">90</span>\n                  <span class=\"footer__key-description\">\"Good\" score of 90/100</span>\n                </li>\n                <li>\n                  <span class=\"subitem-result__points score-average-bg\">45</span>\n                  <span class=\"footer__key-description\">\"Average\" score of 45/100</span>\n                </li>\n                <li>\n                  <span class=\"subitem-result__points score-poor-bg\">25</span>\n                  <span class=\"footer__key-description\">\"Poor\" score of 25/100</span>\n                </li>\n              </ul>\n            </div>\n            <div>\n              <h3 class=\"config-section__title\">Pass/fail audits</h3>\n              <ul class=\"legend_column\">\n                <li>\n                  <span class=\"subitem-result__good score-good-bg\">Pass</span>\n                  <span class=\"footer__key-description\">Passed the audit</span>\n                </li>\n                <li>\n                  <span class=\"subitem-result__poor score-poor-bg\">Fail</span>\n                  <span class=\"footer__key-description\">Failed the audit</span>\n                </li>\n                <li>\n                  <span class=\"subitem-result__unknown score-warning-bg\">Unknown</span>\n                  <span class=\"footer__key-description\">Error or score can't be determined</span>\n                </li>\n              </ul>\n            </div>\n            <div>\n              <h3 class=\"config-section__title\">Informational audits</h3>\n              <ul class=\"legend_column\">\n                <li>\n                  <span class=\"subitem-result__info score-good-bg\">Pass</span>\n                  <span class=\"footer__key-description\">Passed the audit</span>\n                </li>\n                <li>\n                  <span class=\"subitem-result__warning score-warning-bg\">Fail</span>\n                  <span class=\"footer__key-description\">Failed the audit</span>\n                </li>\n              </ul>\n            </div>\n          </section>\n        </div>\n      </div>\n      <div class=\"footer__generated\">Generated by <b>Lighthouse</b> "+
-alias4((helper=(helper=helpers.lighthouseVersion||(depth0!=null?depth0.lighthouseVersion:depth0))!=null?helper:alias2,typeof helper===alias3?helper.call(alias1,{"name":"lighthouseVersion","hash":{},"data":data,"blockParams":blockParams}):helper))+
-" on "+
-alias4((helpers.formatDateTime||depth0&&depth0.formatDateTime||alias2).call(alias1,depth0!=null?depth0.generatedTime:depth0,{"name":"formatDateTime","hash":{},"data":data,"blockParams":blockParams}))+
-" | <a href=\"https://github.com/GoogleChrome/Lighthouse/issues\" target=\"_blank\" rel=\"noopener\">File an issue</a></div>\n    </footer>\n"+(
-(stack1=(helpers.ifEq||depth0&&depth0.ifEq||alias2).call(alias1,depth0!=null?depth0.reportContext:depth0,"perf-x",{"name":"ifEq","hash":{},"fn":container.program(56,data,0,blockParams,depths),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"  </section>\n</div>\n\n<div class=\"log-wrapper\">\n  <div id=\"log\"></div>\n</div>\n\n"+(
-(stack1=helpers["if"].call(alias1,(helpers.and||depth0&&depth0.and||alias2).call(alias1,depth0!=null?depth0.lhresults:depth0,depth0!=null?depth0.scripts:depth0,{"name":"and","hash":{},"data":data,"blockParams":blockParams}),{"name":"if","hash":{},"fn":container.program(58,data,0,blockParams,depths),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"")+
-"\n"+(
-(stack1=helpers["if"].call(alias1,depth0!=null?depth0.scripts:depth0,{"name":"if","hash":{},"fn":container.program(60,data,0,blockParams,depths),"inverse":container.noop,"data":data,"blockParams":blockParams}))!=null?stack1:"");
-},"main_d":function(fn,props,container,depth0,data,blockParams,depths){
+  return "<!--\nCopyright 2016 Google Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n--><!doctype html>\n<html lang=\"en\" data-report-context=\""
+    + alias4(((helper = (helper = helpers.reportContext || (depth0 != null ? depth0.reportContext : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"reportContext","hash":{},"data":data,"blockParams":blockParams}) : helper)))
+    + "\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, minimum-scale=1\">\n  <link rel=\"icon\" href=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAADjklEQVR4AWI08P/HQEvAQrxSQKvlECfLFYXx75xCY2qmh89GbNvOMjb3v9jOOlxnFWxj206ebQ3b7q6q+z1rNagu8/zvPSZACAABpeUAA0miMgU7SA7JjCraFGwZwECOwvL75dWjsKgWBKtx0jvWo+vkBAFbACCkByMP6nMn48+AVgXB2fzSCwsv22/lMGlUhmJ0AE7BH8dyUUDbUEgN6RzJRSeaPxhdRYR0Inel+7Hd5lBiFpkMAxACc0394//9C4voFHDiAAGLpuOXebdfdHfctgwJKaZRLRKy6ItrSis6RBnVBgGtbHyKTEmJHQoEXoBCE5BCrDeA2ogMUIGDAKEBDEhUqwgMqBYDjW4DQzmuffVdqff42/ZQYYqVcMXGZsMPyCsH3lyJSetxvEaxAQXdjR1HjfwCdIS7lo2DZke26Qe+MXO12OWkGT0O6oE7vMGkMnkYw4aN1KQgMKExhXqswfiov4+a7MQ11XPnbr/5qpKlgACAAQj94Lu271bN9DUecQasIZlNzG72llRAAKJiAi+/BSHrSFjRvQhg3DEKEqJh08tsmLTx597+f6enr4cc2Zpk57pihfX24dW7RHcOLLUbJYhJSl0ErQCI9BVXH/XrO97QasuvQQSiECa0BrQCIIJp6X9T/r8QG6L71WYSqCoIIGo2BZDUBnS/D9EA9Nun1iYvbM0MFExIDQRoKFatc1Z6zrm5uWeObJotq0BGV9FuQBWq5a4Fw3PPz848rZHstZSuA5FWAFSMP2nOppOOGpl6qh9PCSg0IFyHKjSQyDNQHTru2t75NOEe0fsf246oAmFkI6vCdnWvbQFQFCKx8vCswV8TrDLiDLgH4Nr7RAtNsrC9d8sfk7b8ls4igdNy8CQKAISlsB0FjZfd3Lfp155tf8fKI4BxZZIj/oTdVEAIAcJFOCmzauHG71I7/rdreUAgAqpDP05fDARCAQQARwEIBQSVxq0FyaLvZZtevpHa8WHw8cft6cpxlq8eAJtIhnSbWDf951yx3y13OqUuu5qyGgkxCgGFh9cDihDGbTa6BqvT1lWmrav3bmt2ZMJ4mU6TGgIC4DBzcv/JqAau1WhzSt3x9Ixk/4Jk/8J4ZrrViFMA4W6A7+WK8xcVjvyrOmVD0FbAXokcT48r+xVqLKvuJYbmpNadnlp3mpufJHOe/GXktM+r09bT8kEdq9BRYAbGSgzP7ll82U71Mc+ZFooXgwAAAABJRU5ErkJggg==\">\n  <title>Lighthouse report: "
+    + alias4(((helper = (helper = helpers.url || (depth0 != null ? depth0.url : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"url","hash":{},"data":data,"blockParams":blockParams}) : helper)))
+    + "</title>\n  <style>\n"
+    + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.stylesheets : depth0),{"name":"each","hash":{},"fn":container.program(4, data, 0, blockParams, depths),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "    /*# sourceURL=inline-styles.css */\n  </style>\n</head>\n<body>\n\n<div class=\"js-report report\">\n  <section class=\"report-body\">\n    <div class=\"report-body__content\">\n      <div class=\"report-body__menu-container\">\n        <div class=\"menu\">\n          <div class=\"menu__header\">\n            <h1 class=\"menu__header-title\">Lighthouse</h1>\n            <div class=\"menu__header-version\">Version: "
+    + alias4(((helper = (helper = helpers.lighthouseVersion || (depth0 != null ? depth0.lighthouseVersion : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"lighthouseVersion","hash":{},"data":data,"blockParams":blockParams}) : helper)))
+    + "</div>\n          </div>\n"
+    + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.reportsCatalog : depth0),{"name":"if","hash":{},"fn":container.program(6, data, 0, blockParams, depths),"inverse":container.program(10, data, 0, blockParams, depths),"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "        </div>\n      </div>\n      <div class=\"report-body__header-container js-header-container\">\n        <div class=\"report-body__header\">\n          <button class=\"report-body__more-toggle js-header-toggle\" title=\"See report's runtime settings\"></button>\n          <div class=\"report-body__metadata\">\n            <div class=\"report-body__url\">Results for: <a href=\""
+    + alias4(((helper = (helper = helpers.url || (depth0 != null ? depth0.url : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"url","hash":{},"data":data,"blockParams":blockParams}) : helper)))
+    + "\" target=\"_blank\" rel=\"noopener\">"
+    + alias4(((helper = (helper = helpers.url || (depth0 != null ? depth0.url : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"url","hash":{},"data":data,"blockParams":blockParams}) : helper)))
+    + "</a></div>\n            <div class=\"report-body__url\">Generated on: "
+    + alias4((helpers.formatDateTime || (depth0 && depth0.formatDateTime) || alias2).call(alias1,(depth0 != null ? depth0.generatedTime : depth0),{"name":"formatDateTime","hash":{},"data":data,"blockParams":blockParams}))
+    + "</div>\n          </div>\n          <div class=\"report-body__buttons\">\n            <div class=\"export-section\">\n              <button class=\"export-button js-export\" title=\"Export report in different formats\">Export...</button>\n              <ul class=\"export-dropdown\">\n                <li><a href=\"#\" class=\"report__icon print\" data-action=\"print\">Print...</a></li>\n                <li><a href=\"#\" class=\"report__icon copy\" data-action=\"copy\">Copy JSON</a></li>\n                <li><a href=\"#\" class=\"report__icon download\" data-action=\"save-html\" data-context=\""
+    + alias4(((helper = (helper = helpers.reportContext || (depth0 != null ? depth0.reportContext : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"reportContext","hash":{},"data":data,"blockParams":blockParams}) : helper)))
+    + "\">Save as HTML</a></li>\n                <li><a href=\"#\" class=\"report__icon download\" data-action=\"save-json\" data-context=\""
+    + alias4(((helper = (helper = helpers.reportContext || (depth0 != null ? depth0.reportContext : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"reportContext","hash":{},"data":data,"blockParams":blockParams}) : helper)))
+    + "\">Save as JSON</a></li>\n"
+    + ((stack1 = (helpers.ifNotEq || (depth0 && depth0.ifNotEq) || alias2).call(alias1,(depth0 != null ? depth0.reportContext : depth0),"viewer",{"name":"ifNotEq","hash":{},"fn":container.program(12, data, 0, blockParams, depths),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "              </ul>\n            </div>\n            <button class=\"report__icon report-body__icon share js-share\" title=\"Share report on GitHub\"></button>\n          </div>\n          <div class=\"report-body__header-content\">\n            <section class=\"config-section\">\n              <h2 class=\"config-section__title\">Runtime Environment</h2>\n              <ul class=\"config-section__items\">\n"
+    + ((stack1 = helpers.each.call(alias1,((stack1 = (depth0 != null ? depth0.runtimeConfig : depth0)) != null ? stack1.environment : stack1),{"name":"each","hash":{},"fn":container.program(14, data, 0, blockParams, depths),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "              </ul>\n            </section>\n"
+    + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.runtimeConfig : depth0)) != null ? stack1.blockedUrlPatterns : stack1),{"name":"if","hash":{},"fn":container.program(19, data, 0, blockParams, depths),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "          </div>\n        </div>\n      </div>\n      <div class=\"report-body__aggregations-container\">\n"
+    + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.aggregations : depth0),{"name":"each","hash":{},"fn":container.program(22, data, 1, blockParams, depths),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "      </div>\n    </div>\n\n    <footer class=\"footer\">\n      <div class=\"footer__legend\">\n        <h2 class=\"config-section__title\">Legend</h2>\n        <div class=\"legend_panel\">\n          <section class=\"legend_columns single-line\">\n            <div class=\"legend_categories\">\n              <h3 class=\"config-section__title\">Audit categories</h3>\n              <ul class=\"legend_column\">\n                <li>\n                  <span class=\"aggregation__header__score score-good-bg subitem-result__good\">Good</span>\n                  <span class=\"footer__key-description\">Overall category score is 75-100</span>\n                </li>\n                <li>\n                  <span class=\"aggregation__header__score score-average-bg subitem-result__good\">Average</span>\n                  <span class=\"footer__key-description\">Overall category score is 45-74</span>\n                </li>\n                <li>\n                  <span class=\"aggregation__header__score score-poor-bg subitem-result__poor\">Poor</span>\n                  <span class=\"footer__key-description\">Overall category score is < 45</span>\n                </li>\n              </ul>\n            </div>\n          </section>\n          <section class=\"legend_columns\">\n            <div>\n              <h3 class=\"config-section__title\">Scored audits</h3>\n              <ul class=\"legend_column\">\n                <li>\n                  <span class=\"subitem-result__points score-good-bg\">90</span>\n                  <span class=\"footer__key-description\">\"Good\" score of 90/100</span>\n                </li>\n                <li>\n                  <span class=\"subitem-result__points score-average-bg\">45</span>\n                  <span class=\"footer__key-description\">\"Average\" score of 45/100</span>\n                </li>\n                <li>\n                  <span class=\"subitem-result__points score-poor-bg\">25</span>\n                  <span class=\"footer__key-description\">\"Poor\" score of 25/100</span>\n                </li>\n              </ul>\n            </div>\n            <div>\n              <h3 class=\"config-section__title\">Pass/fail audits</h3>\n              <ul class=\"legend_column\">\n                <li>\n                  <span class=\"subitem-result__good score-good-bg\">Pass</span>\n                  <span class=\"footer__key-description\">Passed the audit</span>\n                </li>\n                <li>\n                  <span class=\"subitem-result__poor score-poor-bg\">Fail</span>\n                  <span class=\"footer__key-description\">Failed the audit</span>\n                </li>\n                <li>\n                  <span class=\"subitem-result__unknown score-warning-bg\">Unknown</span>\n                  <span class=\"footer__key-description\">Error or score can't be determined</span>\n                </li>\n              </ul>\n            </div>\n            <div>\n              <h3 class=\"config-section__title\">Informational audits</h3>\n              <ul class=\"legend_column\">\n                <li>\n                  <span class=\"subitem-result__info score-good-bg\">Pass</span>\n                  <span class=\"footer__key-description\">Passed the audit</span>\n                </li>\n                <li>\n                  <span class=\"subitem-result__warning score-warning-bg\">Fail</span>\n                  <span class=\"footer__key-description\">Failed the audit</span>\n                </li>\n              </ul>\n            </div>\n          </section>\n        </div>\n      </div>\n      <div class=\"footer__generated\">Generated by <b>Lighthouse</b> "
+    + alias4(((helper = (helper = helpers.lighthouseVersion || (depth0 != null ? depth0.lighthouseVersion : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"lighthouseVersion","hash":{},"data":data,"blockParams":blockParams}) : helper)))
+    + " on "
+    + alias4((helpers.formatDateTime || (depth0 && depth0.formatDateTime) || alias2).call(alias1,(depth0 != null ? depth0.generatedTime : depth0),{"name":"formatDateTime","hash":{},"data":data,"blockParams":blockParams}))
+    + " | <a href=\"https://github.com/GoogleChrome/Lighthouse/issues\" target=\"_blank\" rel=\"noopener\">File an issue</a></div>\n    </footer>\n"
+    + ((stack1 = (helpers.ifEq || (depth0 && depth0.ifEq) || alias2).call(alias1,(depth0 != null ? depth0.reportContext : depth0),"perf-x",{"name":"ifEq","hash":{},"fn":container.program(56, data, 0, blockParams, depths),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "  </section>\n</div>\n\n<div class=\"log-wrapper\">\n  <div id=\"log\"></div>\n</div>\n\n"
+    + ((stack1 = helpers["if"].call(alias1,(helpers.and || (depth0 && depth0.and) || alias2).call(alias1,(depth0 != null ? depth0.lhresults : depth0),(depth0 != null ? depth0.scripts : depth0),{"name":"and","hash":{},"data":data,"blockParams":blockParams}),{"name":"if","hash":{},"fn":container.program(58, data, 0, blockParams, depths),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "")
+    + "\n"
+    + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.scripts : depth0),{"name":"if","hash":{},"fn":container.program(60, data, 0, blockParams, depths),"inverse":container.noop,"data":data,"blockParams":blockParams})) != null ? stack1 : "");
+},"main_d":  function(fn, props, container, depth0, data, blockParams, depths) {
 
-var decorators=container.decorators;
+  var decorators = container.decorators;
 
-fn=decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(1,data,0,blockParams,depths),"inverse":container.noop,"args":["createNav"],"data":data,"blockParams":blockParams})||fn;
-return fn;
-},
+  fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.noop,"args":["createNav"],"data":data,"blockParams":blockParams}) || fn;
+  return fn;
+  }
 
-"useDecorators":true,"usePartial":true,"useData":true,"useDepths":true,"useBlockParams":true};
+,"useDecorators":true,"usePartial":true,"useData":true,"useDepths":true,"useBlockParams":true};
 },{}],32:[function(require,module,exports){
-(function(process,__dirname){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+/**
+ * @license
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-const Driver=require('./gather/driver.js');
-const GatherRunner=require('./gather/gather-runner');
-const Aggregate=require('./aggregator/aggregate');
-const Audit=require('./audits/audit');
-const emulation=require('./lib/emulation');
-const log=require('./lib/log');
-
-const path=require('path');
-const URL=require('./lib/url-shim');
-
-class Runner{
-static run(connection,opts){
-
-opts.flags=opts.flags||{};
-
-const config=opts.config;
 
 
-opts.initialUrl=opts.url;
-if(typeof opts.initialUrl!=='string'||opts.initialUrl.length===0){
-return Promise.reject(new Error('You must provide a url to the runner'));
+const REPORT_TEMPLATE = "<!--\n\nCopyright 2017 Google Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n-->\n<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, minimum-scale=1\">\n  <link rel=\"icon\" href=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAADjklEQVR4AWI08P/HQEvAQrxSQKvlECfLFYXx75xCY2qmh89GbNvOMjb3v9jOOlxnFWxj206ebQ3b7q6q+z1rNagu8/zvPSZACAABpeUAA0miMgU7SA7JjCraFGwZwECOwvL75dWjsKgWBKtx0jvWo+vkBAFbACCkByMP6nMn48+AVgXB2fzSCwsv22/lMGlUhmJ0AE7BH8dyUUDbUEgN6RzJRSeaPxhdRYR0Inel+7Hd5lBiFpkMAxACc0394//9C4voFHDiAAGLpuOXebdfdHfctgwJKaZRLRKy6ItrSis6RBnVBgGtbHyKTEmJHQoEXoBCE5BCrDeA2ogMUIGDAKEBDEhUqwgMqBYDjW4DQzmuffVdqff42/ZQYYqVcMXGZsMPyCsH3lyJSetxvEaxAQXdjR1HjfwCdIS7lo2DZke26Qe+MXO12OWkGT0O6oE7vMGkMnkYw4aN1KQgMKExhXqswfiov4+a7MQ11XPnbr/5qpKlgACAAQj94Lu271bN9DUecQasIZlNzG72llRAAKJiAi+/BSHrSFjRvQhg3DEKEqJh08tsmLTx597+f6enr4cc2Zpk57pihfX24dW7RHcOLLUbJYhJSl0ErQCI9BVXH/XrO97QasuvQQSiECa0BrQCIIJp6X9T/r8QG6L71WYSqCoIIGo2BZDUBnS/D9EA9Nun1iYvbM0MFExIDQRoKFatc1Z6zrm5uWeObJotq0BGV9FuQBWq5a4Fw3PPz848rZHstZSuA5FWAFSMP2nOppOOGpl6qh9PCSg0IFyHKjSQyDNQHTru2t75NOEe0fsf246oAmFkI6vCdnWvbQFQFCKx8vCswV8TrDLiDLgH4Nr7RAtNsrC9d8sfk7b8ls4igdNy8CQKAISlsB0FjZfd3Lfp155tf8fKI4BxZZIj/oTdVEAIAcJFOCmzauHG71I7/rdreUAgAqpDP05fDARCAQQARwEIBQSVxq0FyaLvZZtevpHa8WHw8cft6cpxlq8eAJtIhnSbWDf951yx3y13OqUuu5qyGgkxCgGFh9cDihDGbTa6BqvT1lWmrav3bmt2ZMJ4mU6TGgIC4DBzcv/JqAau1WhzSt3x9Ixk/4Jk/8J4ZrrViFMA4W6A7+WK8xcVjvyrOmVD0FbAXokcT48r+xVqLKvuJYbmpNadnlp3mpufJHOe/GXktM+r09bT8kEdq9BRYAbGSgzP7ll82U71Mc+ZFooXgwAAAABJRU5ErkJggg==\">\n  <title>Lighthouse Report</title>\n  <style>/*%%LIGHTHOUSE_CSS%%*/</style>\n</head>\n<body>\n  <noscript>Lighthouse report requires JavaScript. Please enable.</noscript>\n  <div hidden>%%LIGHTHOUSE_TEMPLATES%%</div>\n  <script>%%LIGHTHOUSE_JAVASCRIPT%%</script>\n  <script>window.__LIGHTHOUSE_JSON__ = %%LIGHTHOUSE_JSON%%;</script>\n  <script>\n    const dom = new DOM(document);\n    const detailsRenderer = new DetailsRenderer(dom);\n    const renderer = new ReportRenderer(dom, detailsRenderer);\n    const reportElem = renderer.renderReport(window.__LIGHTHOUSE_JSON__);\n    document.body.appendChild(reportElem);\n  </script>\n</body>\n</html>\n";
+const REPORT_JAVASCRIPT = [
+  "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use strict';\n\n/* globals URL */\n\nclass DOM {\n  /**\n   * @param {!Document} document\n   */\n  constructor(document) {\n    this._document = document;\n  }\n\n /**\n   * @param {string} name\n   * @param {string=} className\n   * @param {!Object<string, (string|undefined)>=} attrs Attribute key/val pairs.\n   *     Note: if an attribute key has an undefined value, this method does not\n   *     set the attribute on the node.\n   * @return {!Element}\n   */\n  createElement(name, className, attrs) {\n    // TODO(all): adopt `attrs` default arg when https://codereview.chromium.org/2821773002/ lands\n    attrs = attrs || {};\n    const element = this._document.createElement(name);\n    if (className) {\n      element.className = className;\n    }\n    Object.keys(attrs).forEach(key => {\n      const value = attrs[key];\n      if (typeof value !== 'undefined') {\n        element.setAttribute(key, value);\n      }\n    });\n    return element;\n  }\n\n  /**\n   * @param {string} selector\n   * @param {!Document|!Element} context\n   * @return {!DocumentFragment} A clone of the template content.\n   * @throws {Error}\n   */\n  cloneTemplate(selector, context) {\n    const template = context.querySelector(selector);\n    if (!template) {\n      throw new Error(`Template not found: template${selector}`);\n    }\n    return /** @type {!DocumentFragment} */ (this._document.importNode(template.content, true));\n  }\n\n  /**\n   * @param {string} text\n   * @return {!Element}\n   */\n  createSpanFromMarkdown(text) {\n    const element = this.createElement('span');\n\n    // Split on markdown links (e.g. [some link](https://...)).\n    const parts = text.split(/\\[(.*?)\\]\\((https?:\\/\\/.*?)\\)/g);\n\n    while (parts.length) {\n      // Pop off the same number of elements as there are capture groups.\n      const [preambleText, linkText, linkHref] = parts.splice(0, 3);\n      element.appendChild(this._document.createTextNode(preambleText));\n\n      // Append link if there are any.\n      if (linkText && linkHref) {\n        const a = this.createElement('a');\n        a.rel = 'noopener';\n        a.target = '_blank';\n        a.textContent = linkText;\n        a.href = (new URL(linkHref)).href;\n        element.appendChild(a);\n      }\n    }\n\n    return element;\n  }\n\n  /**\n   * @return {!Document}\n   */\n  document() {\n    return this._document;\n  }\n}\n\nif (typeof module !== 'undefined' && module.exports) {\n  module.exports = DOM;\n}\n",
+  "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use strict';\n\nclass DetailsRenderer {\n  /**\n   * @param {!DOM} dom\n   */\n  constructor(dom) {\n    this._dom = dom;\n  }\n\n  /**\n   * @param {(!DetailsRenderer.DetailsJSON|!DetailsRenderer.CardsDetailsJSON)} details\n   * @return {!Element}\n   */\n  render(details) {\n    switch (details.type) {\n      case 'text':\n        return this._renderText(details);\n      case 'block':\n        return this._renderBlock(details);\n      case 'cards':\n        return this._renderCards(/** @type {!DetailsRenderer.CardsDetailsJSON} */ (details));\n      case 'list':\n        return this._renderList(details);\n      default:\n        throw new Error(`Unknown type: ${details.type}`);\n    }\n  }\n\n  /**\n   * @param {!DetailsRenderer.DetailsJSON} text\n   * @return {!Element}\n   */\n  _renderText(text) {\n    const element = this._dom.createElement('div', 'lh-text');\n    element.textContent = text.text;\n    return element;\n  }\n\n  /**\n   * @param {!DetailsRenderer.DetailsJSON} block\n   * @return {!Element}\n   */\n  _renderBlock(block) {\n    const element = this._dom.createElement('div', 'lh-block');\n    const items = block.items || [];\n    for (const item of items) {\n      element.appendChild(this.render(item));\n    }\n    return element;\n  }\n\n  /**\n   * @param {!DetailsRenderer.DetailsJSON} list\n   * @return {!Element}\n   */\n  _renderList(list) {\n    const element = this._dom.createElement('details', 'lh-details');\n    if (list.header) {\n      const summary = this._dom.createElement('summary', 'lh-list__header');\n      summary.textContent = list.header.text;\n      element.appendChild(summary);\n    }\n\n    const itemsElem = this._dom.createElement('div', 'lh-list__items');\n    const items = list.items || [];\n    for (const item of items) {\n      itemsElem.appendChild(this.render(item));\n    }\n    element.appendChild(itemsElem);\n    return element;\n  }\n\n  /**\n   * @param {!DetailsRenderer.CardsDetailsJSON} details\n   * @return {!Element}\n   */\n  _renderCards(details) {\n    const element = this._dom.createElement('details', 'lh-details');\n    if (details.header) {\n      element.appendChild(this._dom.createElement('summary')).textContent = details.header.text;\n    }\n\n    const cardsParent = this._dom.createElement('div', 'lh-scorecards');\n    for (const item of details.items) {\n      const card = cardsParent.appendChild(\n          this._dom.createElement('div', 'lh-scorecard', {title: item.snippet}));\n      const titleEl = this._dom.createElement('div', 'lh-scorecard__title');\n      const valueEl = this._dom.createElement('div', 'lh-scorecard__value');\n      const targetEl = this._dom.createElement('div', 'lh-scorecard__target');\n\n      card.appendChild(titleEl).textContent = item.title;\n      card.appendChild(valueEl).textContent = item.value;\n\n      if (item.target) {\n        card.appendChild(targetEl).textContent = `target: ${item.target}`;\n      }\n    }\n\n    element.appendChild(cardsParent);\n    return element;\n  }\n}\n\nif (typeof module !== 'undefined' && module.exports) {\n  module.exports = DetailsRenderer;\n}\n\n/**\n * @typedef {{\n *     type: string,\n *     text: (string|undefined),\n *     header: (!DetailsRenderer.DetailsJSON|undefined),\n *     items: (!Array<!DetailsRenderer.DetailsJSON>|undefined)\n * }}\n */\nDetailsRenderer.DetailsJSON; // eslint-disable-line no-unused-expressions\n\n/** @typedef {{\n *     type: string,\n *     text: string,\n *     header: !DetailsRenderer.DetailsJSON,\n *     items: !Array<{title: string, value: string, snippet: (string|undefined), target: string}>\n * }}\n */\nDetailsRenderer.CardsDetailsJSON; // eslint-disable-line no-unused-expressions\n",
+  "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use strict';\n\n/**\n * @fileoverview The entry point for rendering the Lighthouse report based on the JSON output.\n *    This file is injected into the report HTML along with the JSON report.\n *\n * Dummy text for ensuring report robustness: </script> pre$`post %%LIGHTHOUSE_JSON%%\n */\n\nconst RATINGS = {\n  PASS: {label: 'pass', minScore: 75},\n  AVERAGE: {label: 'average', minScore: 45},\n  FAIL: {label: 'fail'}\n};\n\n/**\n * Convert a score to a rating label.\n * @param {number} score\n * @return {string}\n */\nfunction calculateRating(score) {\n  let rating = RATINGS.FAIL.label;\n  if (score >= RATINGS.PASS.minScore) {\n    rating = RATINGS.PASS.label;\n  } else if (score >= RATINGS.AVERAGE.minScore) {\n    rating = RATINGS.AVERAGE.label;\n  }\n  return rating;\n}\n\n/**\n * Format number.\n * @param {number} number\n * @return {string}\n */\nfunction formatNumber(number) {\n  return number.toLocaleString(undefined, {maximumFractionDigits: 1});\n}\n\nclass ReportRenderer {\n  /**\n   * @param {!DOM} dom\n   * @param {!DetailsRenderer} detailsRenderer\n   */\n  constructor(dom, detailsRenderer) {\n    this._dom = dom;\n    this._detailsRenderer = detailsRenderer;\n\n    this._templateContext = this._dom.document();\n  }\n\n  /**\n   * @param {!ReportRenderer.ReportJSON} report\n   * @return {!Element}\n   */\n  renderReport(report) {\n    try {\n      return this._renderReport(report);\n    } catch (e) {\n      return this._renderException(e);\n    }\n  }\n\n  /**\n   * @param {!DocumentFragment|!Element} element DOM node to populate with values.\n   * @param {number} score\n   * @param {string} scoringMode\n   * @param {string} title\n   * @param {string} description\n   * @return {!Element}\n   */\n  _populateScore(element, score, scoringMode, title, description) {\n    // Fill in the blanks.\n    const valueEl = element.querySelector('.lh-score__value');\n    valueEl.textContent = formatNumber(score);\n    valueEl.classList.add(`lh-score__value--${calculateRating(score)}`,\n                          `lh-score__value--${scoringMode}`);\n\n    element.querySelector('.lh-score__title').textContent = title;\n    element.querySelector('.lh-score__description')\n        .appendChild(this._dom.createSpanFromMarkdown(description));\n\n    return /** @type {!Element} **/ (element);\n  }\n\n  /**\n   * Define a custom element for <templates> to be extracted from. For example:\n   *     this.setTemplateContext(new DOMParser().parseFromString(htmlStr, 'text/html'))\n   * @param {!Document|!Element} context\n   */\n  setTemplateContext(context) {\n    this._templateContext = context;\n  }\n\n  /**\n   * @param {!ReportRenderer.AuditJSON} audit\n   * @return {!Element}\n   */\n  _renderAuditScore(audit) {\n    const tmpl = this._dom.cloneTemplate('#tmpl-lh-audit-score', this._templateContext);\n\n    const scoringMode = audit.result.scoringMode;\n    const description = audit.result.helpText;\n    let title = audit.result.description;\n\n    if (audit.result.displayValue) {\n      title += `:  ${audit.result.displayValue}`;\n    }\n    if (audit.result.optimalValue) {\n      title += ` (target: ${audit.result.optimalValue})`;\n    }\n\n    // Append audit details to header section so the entire audit is within a <details>.\n    const header = tmpl.querySelector('.lh-score__header');\n    header.open = audit.score < 100; // expand failed audits\n    if (audit.result.details) {\n      header.appendChild(this._detailsRenderer.render(audit.result.details));\n    }\n\n    return this._populateScore(tmpl, audit.score, scoringMode, title, description);\n  }\n\n  /**\n   * @param {!ReportRenderer.CategoryJSON} category\n   * @return {!Element}\n   */\n  _renderCategoryScore(category) {\n    const tmpl = this._dom.cloneTemplate('#tmpl-lh-category-score', this._templateContext);\n    const score = Math.round(category.score);\n    return this._populateScore(tmpl, score, 'numeric', category.name, category.description);\n  }\n\n  /**\n   * @param {!Error} e\n   * @return {!Element}\n   */\n  _renderException(e) {\n    const element = this._dom.createElement('div', 'lh-exception');\n    element.textContent = String(e.stack);\n    return element;\n  }\n\n  /**\n   * @param {!ReportRenderer.ReportJSON} report\n   * @return {!Element}\n   */\n  _renderReport(report) {\n    const element = this._dom.createElement('div', 'lh-report');\n    for (const category of report.reportCategories) {\n      element.appendChild(this._renderCategory(category));\n    }\n    return element;\n  }\n\n  /**\n   * @param {!ReportRenderer.CategoryJSON} category\n   * @return {!Element}\n   */\n  _renderCategory(category) {\n    const element = this._dom.createElement('div', 'lh-category');\n    element.appendChild(this._renderCategoryScore(category));\n\n    const passedAudits = category.audits.filter(audit => audit.score === 100);\n    const nonPassedAudits = category.audits.filter(audit => !passedAudits.includes(audit));\n\n    for (const audit of nonPassedAudits) {\n      element.appendChild(this._renderAudit(audit));\n    }\n\n    // don't create a passed section if there are no passed\n    if (!passedAudits.length) return element;\n\n    const passedElem = this._dom.createElement('details', 'lh-passed-audits');\n    const passedSummary = this._dom.createElement('summary', 'lh-passed-audits-summary');\n    passedSummary.textContent = `View ${passedAudits.length} passed items`;\n    passedElem.appendChild(passedSummary);\n\n    for (const audit of passedAudits) {\n      passedElem.appendChild(this._renderAudit(audit));\n    }\n    element.appendChild(passedElem);\n    return element;\n  }\n\n  /**\n   * @param {!ReportRenderer.AuditJSON} audit\n   * @return {!Element}\n   */\n  _renderAudit(audit) {\n    const element = this._dom.createElement('div', 'lh-audit');\n    element.appendChild(this._renderAuditScore(audit));\n    return element;\n  }\n}\n\nif (typeof module !== 'undefined' && module.exports) {\n  module.exports = ReportRenderer;\n}\n\n/**\n * @typedef {{\n *     id: string, weight:\n *     number, score: number,\n *     result: {\n *       description: string,\n *       displayValue: string,\n *       helpText: string,\n *       score: (number|boolean),\n *       scoringMode: string,\n *       details: (!DetailsRenderer.DetailsJSON|!DetailsRenderer.CardsDetailsJSON|undefined)\n *     }\n * }}\n */\nReportRenderer.AuditJSON; // eslint-disable-line no-unused-expressions\n\n/**\n * @typedef {{\n *     name: string,\n *     weight: number,\n *     score: number,\n *     description: string,\n *     audits: !Array<!ReportRenderer.AuditJSON>\n * }}\n */\nReportRenderer.CategoryJSON; // eslint-disable-line no-unused-expressions\n\n/**\n * @typedef {{\n *     lighthouseVersion: !string,\n *     generatedTime: !string,\n *     initialUrl: !string,\n *     url: !string,\n *     audits: ?Object,\n *     reportCategories: !Array<!ReportRenderer.CategoryJSON>\n * }}\n */\nReportRenderer.ReportJSON; // eslint-disable-line no-unused-expressions\n",
+].join(';\n');
+const REPORT_CSS = "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n :root {\n  --text-font-family: '.SFNSDisplay-Regular', 'Helvetica Neue', 'Lucida Grande', sans-serif;\n  --body-font-size: 13px;\n  --default-padding: 16px;\n\n  --secondary-text-color: #565656;\n  /*--accent-color: #3879d9;*/\n  --fail-color: #df332f;\n  --pass-color: #2b882f;\n  --average-color: #ef6c00; /* md orange 800 */\n  --warning-color: #757575; /* md grey 600 */\n\n  --report-border-color: #ebebeb;\n\n  --lh-score-highlight-bg: #fafafa;\n  --lh-score-icon-background-size: 17px;\n  --lh-score-margin: var(--default-padding);\n  --lh-audit-score-width: 35px;\n  --lh-category-score-width: 50px;\n}\n\n* {\n  box-sizing: border-box;\n}\n\nbody {\n  font-family: var(--text-font-family);\n  font-size: var(--body-font-size);\n  margin: 0;\n  line-height: var(--body-line-height);\n}\n\n[hidden] {\n  display: none !important;\n}\n\n.lh-details {\n  font-size: smaller;\n  margin-top: var(--default-padding);\n}\n\n.lh-details summary {\n  cursor: pointer;\n}\n\n.lh-details[open] summary {\n  margin-bottom: var(--default-padding);\n}\n\n/* List */\n.lh-list__items {\n  padding-left: var(--default-padding);\n}\n\n.lh-list__items > * {\n  border-bottom: 1px solid gray;\n  margin-bottom: 2px;\n}\n\n/* Card */\n.lh-scorecards {\n  display: flex;\n  flex-wrap: wrap;\n}\n.lh-scorecard {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex: 0 0 180px;\n  flex-direction: column;\n  padding: var(--default-padding);\n  padding-top: calc(32px + var(--default-padding));\n  border-radius: 3px;\n  margin-right: var(--default-padding);\n  position: relative;\n  line-height: inherit;\n  border: 1px solid #ebebeb;\n}\n.lh-scorecard__title {\n  background-color: #eee;\n  position: absolute;\n  top: 0;\n  right: 0;\n  left: 0;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  padding: calc(var(--default-padding) / 2);\n}\n.lh-scorecard__value {\n  font-size: 28px;\n}\n.lh-scorecard__target {\n  margin-top: calc(var(--default-padding) / 2);\n}\n\n/* Score */\n\n.lh-score {\n  display: flex;\n  align-items: flex-start;\n}\n\n.lh-score__value {\n  flex: none;\n  padding: 5px;\n  margin-right: var(--lh-score-margin);\n  width: var(--lh-audit-score-width);\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  background: var(--warning-color);\n  color: #fff;\n  border-radius: 2px;\n  position: relative;\n}\n\n.lh-score__value::after {\n  content: '';\n  position: absolute;\n  left: 0;\n  right: 0;\n  top: 0;\n  bottom: 0;\n  background-color: #000;\n  border-radius: inherit;\n}\n\n.lh-score__value--binary {\n  text-indent: -500px;\n}\n\n/* No icon for audits with number scores. */\n.lh-score__value:not(.lh-score__value--binary)::after {\n  content: none;\n}\n\n.lh-score__value--pass {\n  background: var(--pass-color);\n}\n\n.lh-score__value--pass::after {\n  background: url('data:image/svg+xml;utf8,<svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" xmlns=\"http://www.w3.org/2000/svg\"><title>pass</title><path d=\"M9.17 2.33L4.5 7 2.83 5.33 1.5 6.66l3 3 6-6z\" fill=\"white\" fill-rule=\"evenodd\"/></svg>') no-repeat 50% 50%;\n  background-size: var(--lh-score-icon-background-size);\n}\n\n.lh-score__value--average {\n  background: var(--average-color);\n}\n\n.lh-score__value--average::after {\n  background: none;\n  content: '!';\n  background-color: var(--average-color);\n  color: #fff;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  font-weight: 500;\n  font-size: 15px;\n}\n\n.lh-score__value--fail {\n  background: var(--fail-color);\n}\n\n.lh-score__value--fail::after {\n  background: url('data:image/svg+xml;utf8,<svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" xmlns=\"http://www.w3.org/2000/svg\"><title>fail</title><path d=\"M8.33 2.33l1.33 1.33-2.335 2.335L9.66 8.33 8.33 9.66 5.995 7.325 3.66 9.66 2.33 8.33l2.335-2.335L2.33 3.66l1.33-1.33 2.335 2.335z\" fill=\"white\"/></svg>') no-repeat 50% 50%;\n  background-size: var(--lh-score-icon-background-size);\n}\n\n.lh-score__title {\n  margin-bottom: calc(var(--default-padding) / 2);\n}\n\n.lh-score__description {\n  font-size: smaller;\n  color: var(--secondary-text-color);\n  margin-top: calc(var(--default-padding) / 2);\n}\n\n.lh-score__header {\n  flex: 1;\n  margin-top: 2px;\n}\n\n.lh-score__header[open] .lh-score__arrow {\n  transform: rotateZ(90deg);\n}\n\n.lh-score__arrow {\n  background: url('data:image/svg+xml;utf8,<svg fill=\"black\" height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z\"/><path d=\"M0-.25h24v24H0z\" fill=\"none\"/></svg>') no-repeat 50% 50%;\n  background-size: contain;\n  background-color: transparent;\n  width: 24px;\n  height: 24px;\n  flex: none;\n  margin: 0 8px 0 8px;\n  transition: transform 150ms ease-in-out;\n}\n\n.lh-score__snippet {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  cursor: pointer;\n  /*outline: none;*/\n}\n\n.lh-score__snippet::-moz-list-bullet {\n  display: none;\n}\n\n.lh-score__snippet::-webkit-details-marker {\n  display: none;\n}\n\n/*.lh-score__snippet:focus .lh-score__title {\n  outline: rgb(59, 153, 252) auto 5px;\n}*/\n\n/* Audit */\n\n.lh-audit {\n  margin-top: var(--default-padding);\n}\n\n.lh-audit > .lh-score {\n  font-size: 16px;\n}\n\n/* Report */\n\n.lh-exception {\n  font-size: large;\n}\n\n.lh-report {\n  padding: var(--default-padding);\n}\n\n.lh-category {\n  padding: 24px 0;\n  border-top: 1px solid var(--report-border-color);\n}\n\n.lh-category:first-of-type {\n  border: none;\n  padding-top: 0;\n}\n\n.lh-category > .lh-audit,\n.lh-category > .lh-passed-audits > .lh-audit {\n  margin-left: calc(var(--lh-category-score-width) + var(--lh-score-margin));\n}\n\n.lh-category > .lh-score {\n  font-size: 20px;\n}\n\n.lh-category > .lh-score .lh-score__value {\n  width: var(--lh-category-score-width);\n}\n\n/* Category snippet shouldnt have pointer cursor. */\n.lh-category > .lh-score .lh-score__snippet {\n  cursor: initial;\n}\n\n.lh-category > .lh-score .lh-score__title {\n  font-size: 24px;\n  font-weight: 400;\n}\n\nsummary.lh-passed-audits-summary {\n  margin: 10px 5px;\n  font-size: 15px;\n}\n\nsummary.lh-passed-audits-summary::-webkit-details-marker {\n  background: rgb(66, 175, 69);\n  color: white;\n  position:relative;\n  content: '';\n  padding: 3px;\n}\n\n/*# sourceURL=report.styles.css */\n";
+const REPORT_TEMPLATES = "<!-- Lighthouse category score -->\n<template id=\"tmpl-lh-category-score\">\n  <div class=\"lh-score\">\n    <div class=\"lh-score__value\"><!-- fill me --></div>\n    <div class=\"lh-score__header\">\n      <div class=\"lh-score__snippet\">\n        <span class=\"lh-score__title\"><!-- fill me --></span>\n      </div>\n      <div class=\"lh-score__description\"><!-- fill me --></div>\n    </div>\n  </div>\n</template>\n\n<!-- Lighthouse audit score -->\n<template id=\"tmpl-lh-audit-score\">\n  <div class=\"lh-score\">\n    <div class=\"lh-score__value\"><!-- fill me --></div>\n    <details class=\"lh-score__header\">\n      <summary class=\"lh-score__snippet\">\n        <span class=\"lh-score__title\"><!-- fill me --></span>\n        <div class=\"lh-score__arrow\" title=\"See audits\"></div>\n      </summary>\n      <div class=\"lh-score__description\"><!-- fill me --></div>\n    </details>\n  </div>\n</template>\n";
+
+class ReportGeneratorV2 {
+  /**
+   * Computes the weighted-average of the score of the list of items.
+   * @param {!Array<{score: number|undefined, weight: number|undefined}} items
+   * @return {number}
+   */
+  static arithmeticMean(items) {
+    const results = items.reduce((result, item) => {
+      const score = Number(item.score) || 0;
+      const weight = Number(item.weight) || 0;
+      return {
+        weight: result.weight + weight,
+        sum: result.sum + score * weight,
+      };
+    }, {weight: 0, sum: 0});
+
+    return (results.sum / results.weight) || 0;
+  }
+
+  /**
+   * Replaces all the specified strings in source without serial replacements.
+   * @param {string} source
+   * @param {!Array<{search: string, replacement: string}>} replacements
+   */
+  static replaceStrings(source, replacements) {
+    if (replacements.length === 0) {
+      return source;
+    }
+
+    const firstReplacement = replacements[0];
+    const nextReplacements = replacements.slice(1);
+    return source
+        .split(firstReplacement.search)
+        .map(part => ReportGeneratorV2.replaceStrings(part, nextReplacements))
+        .join(firstReplacement.replacement);
+  }
+
+  /**
+   * Convert categories into old-school aggregations for old HTML report compat.
+   * @param {!Array<{name: string, description: string, id: string, score: number,
+   *    audits: !Array<{result: Object}>}>} categories
+   * @return {!Array<!Aggregation>}
+   */
+  static _getAggregations(reportCategories) {
+    return reportCategories.map(category => {
+      const name = category.name;
+      const description = category.description;
+
+      return {
+        name, description,
+        categorizable: false,
+        scored: category.id === 'pwa',
+        total: category.score / 100,
+        score: [{
+          name, description,
+          overall: category.score / 100,
+          subItems: category.audits.map(audit => audit.result),
+        }],
+      };
+    });
+  }
+
+  /**
+   * Returns the report JSON object with computed scores.
+   * @param {{categories: !Object<{audits: !Array}>}} config
+   * @param {!Object<{score: ?number|boolean|undefined}>} resultsByAuditId
+   * @return {{categories: !Array<{audits: !Array<{score: number, result: !Object}>}>}}
+   */
+  generateReportJson(config, resultsByAuditId) {
+    const categories = Object.keys(config.categories).map(categoryId => {
+      const category = config.categories[categoryId];
+      category.id = categoryId;
+
+      const audits = category.audits.map(audit => {
+        const result = resultsByAuditId[audit.id];
+        // Cast to number to catch `null` and undefined when audits error
+        let auditScore = Number(result.score) || 0;
+        if (typeof result.score === 'boolean') {
+          auditScore = result.score ? 100 : 0;
+        }
+
+        return Object.assign({}, audit, {result, score: auditScore});
+      });
+
+      const categoryScore = ReportGeneratorV2.arithmeticMean(audits);
+      return Object.assign({}, category, {audits, score: categoryScore});
+    });
+
+    const overallScore = ReportGeneratorV2.arithmeticMean(categories);
+    // TODO: remove aggregations when old report is fully replaced
+    const aggregations = ReportGeneratorV2._getAggregations(categories);
+    return {score: overallScore, categories, aggregations};
+  }
+
+  /**
+   * Returns the report HTML as a string with the report JSON and renderer JS inlined.
+   * @param {!Object} reportAsJson
+   * @return {string}
+   */
+  generateReportHtml(reportAsJson) {
+    const sanitizedJson = JSON.stringify(reportAsJson).replace(/</g, '\\u003c');
+    const sanitizedJavascript = REPORT_JAVASCRIPT.replace(/<\//g, '\\u003c/');
+
+    return ReportGeneratorV2.replaceStrings(REPORT_TEMPLATE, [
+      {search: '%%LIGHTHOUSE_JSON%%', replacement: sanitizedJson},
+      {search: '%%LIGHTHOUSE_JAVASCRIPT%%', replacement: sanitizedJavascript},
+      {search: '/*%%LIGHTHOUSE_CSS%%*/', replacement: REPORT_CSS},
+      {search: '%%LIGHTHOUSE_TEMPLATES%%', replacement: REPORT_TEMPLATES},
+    ]);
+  }
 }
 
-let parsedURL;
-try{
-parsedURL=new URL(opts.url);
-}catch(e){
-const err=new Error('The url provided should have a proper protocol and hostname.');
-return Promise.reject(err);
+module.exports = ReportGeneratorV2;
+
+},{}],33:[function(require,module,exports){
+(function (process,__dirname){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+const Driver = require('./gather/driver.js');
+const GatherRunner = require('./gather/gather-runner');
+const ReportGeneratorV2 = require('./report/v2/report-generator');
+const Audit = require('./audits/audit');
+const emulation = require('./lib/emulation');
+const log = require('./lib/log');
+
+const path = require('path');
+const URL = require('./lib/url-shim');
+
+class Runner {
+  static run(connection, opts) {
+    // Clean opts input.
+    opts.flags = opts.flags || {};
+
+    const config = opts.config;
+
+    // save the initialUrl provided by the user
+    opts.initialUrl = opts.url;
+    if (typeof opts.initialUrl !== 'string' || opts.initialUrl.length === 0) {
+      return Promise.reject(new Error('You must provide a url to the runner'));
+    }
+
+    let parsedURL;
+    try {
+      parsedURL = new URL(opts.url);
+    } catch (e) {
+      const err = new Error('The url provided should have a proper protocol and hostname.');
+      return Promise.reject(err);
+    }
+
+    // If the URL isn't https and is also not localhost complain to the user.
+    if (parsedURL.protocol !== 'https:' && parsedURL.hostname !== 'localhost') {
+      log.warn('Lighthouse', 'The URL provided should be on HTTPS');
+      log.warn('Lighthouse', 'Performance stats will be skewed redirecting from HTTP to HTTPS.');
+    }
+
+    // canonicalize URL with any trailing slashes neccessary
+    opts.url = parsedURL.href;
+
+    // Check that there are passes & audits...
+    const validPassesAndAudits = config.passes && config.audits;
+
+    // ... or that there are artifacts & audits.
+    const validArtifactsAndAudits = config.artifacts && config.audits;
+
+    // Make a run, which can be .then()'d with whatever needs to run (based on the config).
+    let run = Promise.resolve();
+
+    // If there are passes run the GatherRunner and gather the artifacts. If not, we will need
+    // to check that there are artifacts specified in the config, and throw if not.
+    if (validPassesAndAudits || validArtifactsAndAudits) {
+      if (validPassesAndAudits) {
+        opts.driver = opts.driverMock || new Driver(connection);
+        // Finally set up the driver to gather.
+        run = run.then(_ => GatherRunner.run(config.passes, opts));
+      } else if (validArtifactsAndAudits) {
+        run = run.then(_ => {
+          return Object.assign(GatherRunner.instantiateComputedArtifacts(), config.artifacts);
+        });
+      }
+
+      // Basic check that the traces (gathered or loaded) are valid.
+      run = run.then(artifacts => {
+        for (const passName of Object.keys(artifacts.traces || {})) {
+          const trace = artifacts.traces[passName];
+          if (!Array.isArray(trace.traceEvents)) {
+            throw new Error(passName + ' trace was invalid. `traceEvents` was not an array.');
+          }
+        }
+
+        return artifacts;
+      });
+
+      // Run each audit sequentially, the auditResults array has all our fine work
+      const auditResults = [];
+      for (const audit of config.audits) {
+        run = run.then(artifacts => {
+          return Runner._runAudit(audit, artifacts)
+            .then(ret => auditResults.push(ret))
+            .then(_ => artifacts);
+        });
+      }
+      run = run.then(artifacts => {
+        return {artifacts, auditResults};
+      });
+    } else if (config.auditResults) {
+      // If there are existing audit results, surface those here.
+      // Instantiate and return artifacts for consistency.
+      const artifacts = Object.assign(GatherRunner.instantiateComputedArtifacts(),
+                                      config.artifacts || {});
+      run = run.then(_ => {
+        return {
+          artifacts,
+          auditResults: config.auditResults
+        };
+      });
+    } else {
+      const err = Error(
+          'The config must provide passes and audits, artifacts and audits, or auditResults');
+      return Promise.reject(err);
+    }
+
+    // Format and aggregate results before returning.
+    run = run
+      .then(runResults => {
+        const resultsById = runResults.auditResults.reduce((results, audit) => {
+          results[audit.name] = audit;
+          return results;
+        }, {});
+
+        let aggregations = [];
+        let reportCategories = [];
+        let score = 0;
+        if (config.categories) {
+          const reportGenerator = new ReportGeneratorV2();
+          const report = reportGenerator.generateReportJson(config, resultsById);
+          reportCategories = report.categories;
+          aggregations = report.aggregations;
+          score = report.score;
+        }
+
+        return {
+          userAgent: runResults.artifacts.UserAgent,
+          lighthouseVersion: require('../package').version,
+          generatedTime: (new Date()).toJSON(),
+          initialUrl: opts.initialUrl,
+          url: opts.url,
+          audits: resultsById,
+          artifacts: runResults.artifacts,
+          runtimeConfig: Runner.getRuntimeConfig(opts.flags),
+          score,
+          reportCategories,
+          aggregations
+        };
+      });
+
+    return run;
+  }
+
+  /**
+   * Checks that the audit's required artifacts exist and runs the audit if so.
+   * Otherwise returns error audit result.
+   * @param {!Audit} audit
+   * @param {!Artifacts} artifacts
+   * @return {!Promise<!AuditResult>}
+   * @private
+   */
+  static _runAudit(audit, artifacts) {
+    const status = `Evaluating: ${audit.meta.description}`;
+
+    return Promise.resolve().then(_ => {
+      log.log('status', status);
+
+      // Return an early error if an artifact required for the audit is missing or an error.
+      for (const artifactName of audit.meta.requiredArtifacts) {
+        const noArtifact = typeof artifacts[artifactName] === 'undefined';
+
+        // If trace required, check that DEFAULT_PASS trace exists.
+        // TODO: need pass-specific check of networkRecords and traces.
+        const noTrace = artifactName === 'traces' && !artifacts.traces[Audit.DEFAULT_PASS];
+
+        if (noArtifact || noTrace) {
+          log.warn('Runner',
+              `${artifactName} gatherer, required by audit ${audit.meta.name}, did not run.`);
+          throw new Error(`Required ${artifactName} gatherer did not run.`);
+        }
+
+        // If artifact was an error, it must be non-fatal (or gatherRunner would
+        // have thrown). Output error result on behalf of audit.
+        if (artifacts[artifactName] instanceof Error) {
+          const artifactError = artifacts[artifactName];
+          log.warn('Runner', `${artifactName} gatherer, required by audit ${audit.meta.name},` +
+            ` encountered an error: ${artifactError.message}`);
+          throw new Error(
+              `Required ${artifactName} gatherer encountered an error: ${artifactError.message}`);
+        }
+      }
+      // all required artifacts are in good shape, so we proceed
+      return audit.audit(artifacts);
+    // Fill remaining audit result fields.
+    }).then(auditResult => Audit.generateAuditResult(audit, auditResult))
+    .catch(err => {
+      if (err.fatal) {
+        throw err;
+      }
+
+      // Non-fatal error become error audit result.
+      return Audit.generateErrorAuditResult(audit, 'Audit error: ' + err.message);
+    }).then(result => {
+      log.verbose('statusEnd', status);
+      return result;
+    });
+  }
+
+  /**
+   * Returns list of audit names for external querying.
+   * @return {!Array<string>}
+   */
+  static getAuditList() {
+    const ignoredFiles = [
+      'audit.js',
+      'accessibility/axe-audit.js',
+      'multi-check-audit.js',
+      'byte-efficiency/byte-efficiency-audit.js'
+    ];
+
+    const fileList = [
+      ...["accessibility","audit.js","byte-efficiency","cache-start-url.js","content-width.js","critical-request-chains.js","deprecations.js","dobetterweb","estimated-input-latency.js","first-meaningful-paint.js","is-on-https.js","load-fast-enough-for-pwa.js","manifest-short-name-length.js","multi-check-audit.js","redirects-http.js","service-worker.js","speed-index-metric.js","splash-screen.js","themed-omnibox.js","time-to-interactive.js","user-timings.js","viewport.js","webapp-install-banner.js","without-javascript.js","works-offline.js"],
+      ...["appcache-manifest.js","dom-size.js","external-anchors-use-rel-noopener.js","geolocation-on-start.js","link-blocking-first-paint.js","no-console-time.js","no-datenow.js","no-document-write.js","no-mutation-events.js","no-old-flexbox.js","no-websql.js","notification-on-start.js","script-blocking-first-paint.js","uses-http2.js","uses-passive-event-listeners.js"].map(f => `dobetterweb/${f}`),
+      ...["accesskeys.js","aria-allowed-attr.js","aria-required-attr.js","aria-required-children.js","aria-required-parent.js","aria-roles.js","aria-valid-attr-value.js","aria-valid-attr.js","audio-caption.js","axe-audit.js","button-name.js","bypass.js","color-contrast.js","definition-list.js","dlitem.js","document-title.js","duplicate-id.js","frame-title.js","html-has-lang.js","html-lang-valid.js","image-alt.js","input-image-alt.js","label.js","layout-table.js","link-name.js","list.js","listitem.js","meta-refresh.js","meta-viewport.js","object-alt.js","tabindex.js","td-headers-attr.js","th-has-data-cells.js","valid-lang.js","video-caption.js","video-description.js"]
+          .map(f => `accessibility/${f}`),
+      ...["byte-efficiency-audit.js","offscreen-images.js","total-byte-weight.js","unused-css-rules.js","uses-optimized-images.js","uses-request-compression.js","uses-responsive-images.js"]
+          .map(f => `byte-efficiency/${f}`)
+    ];
+    return fileList.filter(f => {
+      return /\.js$/.test(f) && !ignoredFiles.includes(f);
+    }).sort();
+  }
+
+  /**
+   * Returns list of gatherer names for external querying.
+   * @return {!Array<string>}
+   */
+  static getGathererList() {
+    const fileList = [
+      ...["accessibility.js","cache-contents.js","chrome-console-messages.js","css-usage.js","dobetterweb","gatherer.js","html-without-javascript.js","http-redirect.js","image-usage.js","manifest.js","offline.js","service-worker.js","styles.js","theme-color.js","url.js","viewport-dimensions.js","viewport.js"],
+      ...["all-event-listeners.js","anchors-with-no-rel-noopener.js","appcache.js","console-time-usage.js","datenow.js","document-write.js","domstats.js","geolocation-on-start.js","notification-on-start.js","optimized-images.js","response-compression.js","tags-blocking-first-paint.js","websql.js"]
+          .map(f => `dobetterweb/${f}`)
+    ];
+    return fileList.filter(f => /\.js$/.test(f) && f !== 'gatherer.js').sort();
+  }
+
+  /**
+   * Resolves the location of the specified plugin and returns an absolute
+   * string path to the file. Used for loading custom audits and gatherers.
+   * Throws an error if no plugin is found.
+   * @param {string} plugin
+   * @param {string=} configDir The absolute path to the directory of the config file, if there is one.
+   * @param {string=} category Optional plugin category (e.g. 'audit') for better error messages.
+   * @return {string}
+   * @throws {Error}
+   */
+  static resolvePlugin(plugin, configDir, category) {
+    // First try straight `require()`. Unlikely to be specified relative to this
+    // file, but adds support for Lighthouse plugins in npm modules as
+    // `require()` walks up parent directories looking inside any node_modules/
+    // present. Also handles absolute paths.
+    try {
+      return require.resolve(plugin);
+    } catch (e) {}
+
+    // See if the plugin resolves relative to the current working directory.
+    // Most useful to handle the case of invoking Lighthouse as a module, since
+    // then the config is an object and so has no path.
+    const cwdPath = path.resolve(process.cwd(), plugin);
+    try {
+      return require.resolve(cwdPath);
+    } catch (e) {}
+
+    const errorString = 'Unable to locate ' +
+        (category ? `${category}: ` : '') +
+        `${plugin} (tried to require() from '${__dirname}' and load from '${cwdPath}'`;
+
+    if (!configDir) {
+      throw new Error(errorString + ')');
+    }
+
+    // Finally, try looking up relative to the config file path. Just like the
+    // relative path passed to `require()` is found relative to the file it's
+    // in, this allows plugin paths to be specified relative to the config file.
+    const relativePath = path.resolve(configDir, plugin);
+    try {
+      return require.resolve(relativePath);
+    } catch (requireError) {}
+
+    throw new Error(errorString + ` and '${relativePath}')`);
+  }
+
+  /**
+   * Get runtime configuration specified by the flags
+   * @param {!Object} flags
+   * @return {!Object} runtime config
+   */
+  static getRuntimeConfig(flags) {
+    const emulationDesc = emulation.getEmulationDesc();
+    const environment = [
+      {
+        name: 'Device Emulation',
+        enabled: !flags.disableDeviceEmulation,
+        description: emulationDesc['deviceEmulation']
+      },
+      {
+        name: 'Network Throttling',
+        enabled: !flags.disableNetworkThrottling,
+        description: emulationDesc['networkThrottling']
+      },
+      {
+        name: 'CPU Throttling',
+        enabled: !flags.disableCpuThrottling,
+        description: emulationDesc['cpuThrottling']
+      }
+    ];
+
+    return {environment, blockedUrlPatterns: flags.blockedUrlPatterns || []};
+  }
 }
 
+module.exports = Runner;
 
-if(parsedURL.protocol!=='https:'&&parsedURL.hostname!=='localhost'){
-log.warn('Lighthouse','The URL provided should be on HTTPS');
-log.warn('Lighthouse','Performance stats will be skewed redirecting from HTTP to HTTPS.');
-}
-
-
-opts.url=parsedURL.href;
-
-
-const validPassesAndAudits=config.passes&&config.audits;
-
-
-const validArtifactsAndAudits=config.artifacts&&config.audits;
-
-
-let run=Promise.resolve();
-
-
-
-if(validPassesAndAudits||validArtifactsAndAudits){
-if(validPassesAndAudits){
-opts.driver=opts.driverMock||new Driver(connection);
-
-run=run.then(_=>GatherRunner.run(config.passes,opts));
-}else if(validArtifactsAndAudits){
-run=run.then(_=>{
-return Object.assign(GatherRunner.instantiateComputedArtifacts(),config.artifacts);
-});
-}
-
-
-run=run.then(artifacts=>{
-for(const passName of Object.keys(artifacts.traces||{})){
-const trace=artifacts.traces[passName];
-if(!Array.isArray(trace.traceEvents)){
-throw new Error(passName+' trace was invalid. `traceEvents` was not an array.');
-}
-}
-
-return artifacts;
-});
-
-
-const auditResults=[];
-for(const audit of config.audits){
-run=run.then(artifacts=>{
-return Runner._runAudit(audit,artifacts).
-then(ret=>auditResults.push(ret)).
-then(_=>artifacts);
-});
-}
-run=run.then(artifacts=>{
-return{artifacts,auditResults};
-});
-}else if(config.auditResults){
-
-
-const artifacts=Object.assign(GatherRunner.instantiateComputedArtifacts(),
-config.artifacts||{});
-run=run.then(_=>{
-return{
-artifacts,
-auditResults:config.auditResults};
-
-});
-}else{
-const err=Error(
-'The config must provide passes and audits, artifacts and audits, or auditResults');
-return Promise.reject(err);
-}
-
-
-run=run.
-then(runResults=>{
-const formattedAudits=runResults.auditResults.reduce((formatted,audit)=>{
-formatted[audit.name]=audit;
-return formatted;
-},{});
-
-
-let aggregations=[];
-if(config.aggregations){
-aggregations=config.aggregations.map(
-a=>Aggregate.aggregate(a,runResults.auditResults));
-}
-
-return{
-lighthouseVersion:require('../package').version,
-generatedTime:new Date().toJSON(),
-initialUrl:opts.initialUrl,
-url:opts.url,
-audits:formattedAudits,
-artifacts:runResults.artifacts,
-runtimeConfig:Runner.getRuntimeConfig(opts.flags),
-aggregations};
-
-});
-
-return run;
-}
-
-
-
-
-
-
-
-
-
-static _runAudit(audit,artifacts){
-const status=`Evaluating: ${audit.meta.description}`;
-
-return Promise.resolve().then(_=>{
-log.log('status',status);
-
-
-for(const artifactName of audit.meta.requiredArtifacts){
-const noArtifact=typeof artifacts[artifactName]==='undefined';
-
-
-
-const noTrace=artifactName==='traces'&&!artifacts.traces[Audit.DEFAULT_PASS];
-
-if(noArtifact||noTrace){
-log.warn('Runner',
-`${artifactName} gatherer, required by audit ${audit.meta.name}, did not run.`);
-throw new Error(`Required ${artifactName} gatherer did not run.`);
-}
-
-
-
-if(artifacts[artifactName]instanceof Error){
-const artifactError=artifacts[artifactName];
-log.warn('Runner',`${artifactName} gatherer, required by audit ${audit.meta.name},`+
-` encountered an error: ${artifactError.message}`);
-throw new Error(
-`Required ${artifactName} gatherer encountered an error: ${artifactError.message}`);
-}
-}
-
-return audit.audit(artifacts);
-}).catch(err=>{
-if(err.fatal){
-throw err;
-}
-
-
-return audit.generateErrorAuditResult('Audit error: '+err.message);
-}).then(result=>{
-log.verbose('statusEnd',status);
-return result;
-});
-}
-
-
-
-
-
-static getAuditList(){
-const ignoredFiles=[
-'audit.js',
-'accessibility/axe-audit.js',
-'byte-efficiency/byte-efficiency-audit.js'];
-
-
-const fileList=[
-...["accessibility","audit.js","byte-efficiency","cache-start-url.js","content-width.js","critical-request-chains.js","deprecations.js","dobetterweb","estimated-input-latency.js","first-meaningful-paint.js","is-on-https.js","manifest-background-color.js","manifest-display.js","manifest-exists.js","manifest-icons-min-144.js","manifest-icons-min-192.js","manifest-name.js","manifest-short-name-length.js","manifest-short-name.js","manifest-start-url.js","manifest-theme-color.js","redirects-http.js","screenshots.js","service-worker.js","speed-index-metric.js","theme-color-meta.js","time-to-interactive.js","user-timings.js","viewport.js","without-javascript.js","works-offline.js"],
-...["appcache-manifest.js","dom-size.js","external-anchors-use-rel-noopener.js","geolocation-on-start.js","link-blocking-first-paint.js","no-console-time.js","no-datenow.js","no-document-write.js","no-mutation-events.js","no-old-flexbox.js","no-websql.js","notification-on-start.js","script-blocking-first-paint.js","uses-http2.js","uses-passive-event-listeners.js"].map(f=>`dobetterweb/${f}`),
-...["aria-allowed-attr.js","aria-required-attr.js","aria-valid-attr-value.js","aria-valid-attr.js","axe-audit.js","color-contrast.js","image-alt.js","label.js","tabindex.js"].
-map(f=>`accessibility/${f}`),
-...["byte-efficiency-audit.js","total-byte-weight.js","unused-css-rules.js","uses-optimized-images.js","uses-responsive-images.js"].
-map(f=>`byte-efficiency/${f}`)];
-
-return fileList.filter(f=>{
-return /\.js$/.test(f)&&!ignoredFiles.includes(f);
-}).sort();
-}
-
-
-
-
-
-static getGathererList(){
-const fileList=[
-...["accessibility.js","cache-contents.js","chrome-console-messages.js","content-width.js","css-usage.js","dobetterweb","gatherer.js","html-without-javascript.js","http-redirect.js","https.js","image-usage.js","manifest.js","offline.js","service-worker.js","styles.js","theme-color.js","url.js","viewport.js"],
-...["all-event-listeners.js","anchors-with-no-rel-noopener.js","appcache.js","console-time-usage.js","datenow.js","document-write.js","domstats.js","geolocation-on-start.js","notification-on-start.js","optimized-images.js","tags-blocking-first-paint.js","websql.js"].
-map(f=>`dobetterweb/${f}`)];
-
-return fileList.filter(f=>/\.js$/.test(f)&&f!=='gatherer.js').sort();
-}
-
-
-
-
-
-
-
-
-
-
-
-static resolvePlugin(plugin,configDir,category){
-
-
-
-
-try{
-return require.resolve(plugin);
-}catch(e){}
-
-
-
-
-const cwdPath=path.resolve(process.cwd(),plugin);
-try{
-return require.resolve(cwdPath);
-}catch(e){}
-
-const errorString='Unable to locate '+(
-category?`${category}: `:'')+
-`${plugin} (tried to require() from '${__dirname}' and load from '${cwdPath}'`;
-
-if(!configDir){
-throw new Error(errorString+')');
-}
-
-
-
-
-const relativePath=path.resolve(configDir,plugin);
-try{
-return require.resolve(relativePath);
-}catch(requireError){}
-
-throw new Error(errorString+` and '${relativePath}')`);
-}
-
-
-
-
-
-
-static getRuntimeConfig(flags){
-const emulationDesc=emulation.getEmulationDesc();
-const environment=[
-{
-name:'Device Emulation',
-enabled:!flags.disableDeviceEmulation,
-description:emulationDesc['deviceEmulation']},
-
-{
-name:'Network Throttling',
-enabled:!flags.disableNetworkThrottling,
-description:emulationDesc['networkThrottling']},
-
-{
-name:'CPU Throttling',
-enabled:!flags.disableCpuThrottling,
-description:emulationDesc['cpuThrottling']}];
-
-
-
-return{environment,blockedUrlPatterns:flags.blockedUrlPatterns||[]};
-}}
-
-
-module.exports=Runner;
-
-}).call(this,require('_process'),"/../lighthouse-core");
-},{"../package":275,"./aggregator/aggregate":1,"./audits/audit":3,"./gather/driver.js":11,"./gather/gather-runner":12,"./lib/emulation":16,"./lib/log":19,"./lib/url-shim":25,"_process":204,"path":203}],33:[function(require,module,exports){
-(function(global){
+}).call(this,require('_process'),"/../lighthouse-core")
+},{"../package":304,"./audits/audit":2,"./gather/driver.js":11,"./gather/gather-runner":12,"./lib/emulation":16,"./lib/log":19,"./lib/url-shim":25,"./report/v2/report-generator":32,"_process":222,"path":220}],34:[function(require,module,exports){
+(function (global){
 "use strict";'use strict';global.tr=function(){if(global.tr){console.warn('Base was multiply initialized. First init wins.');return global.tr;}function exportPath(name){var parts=name.split('.');var cur=global;for(var part;parts.length&&(part=parts.shift());){if(part in cur){cur=cur[part];}else{cur=cur[part]={};}}return cur;};function isExported(name){var parts=name.split('.');var cur=global;for(var part;parts.length&&(part=parts.shift());){if(part in cur){cur=cur[part];}else{return false;}}return true;}function isDefined(name){var parts=name.split('.');var curObject=global;for(var i=0;i<parts.length;i++){var partName=parts[i];var nextObject=curObject[partName];if(nextObject===undefined)return false;curObject=nextObject;}return true;}var panicElement=undefined;var rawPanicMessages=[];function showPanicElementIfNeeded(){if(panicElement)return;var panicOverlay=document.createElement('div');panicOverlay.style.backgroundColor='white';panicOverlay.style.border='3px solid red';panicOverlay.style.boxSizing='border-box';panicOverlay.style.color='black';panicOverlay.style.display='-webkit-flex';panicOverlay.style.height='100%';panicOverlay.style.left=0;panicOverlay.style.padding='8px';panicOverlay.style.position='fixed';panicOverlay.style.top=0;panicOverlay.style.webkitFlexDirection='column';panicOverlay.style.width='100%';panicElement=document.createElement('div');panicElement.style.webkitFlex='1 1 auto';panicElement.style.overflow='auto';panicOverlay.appendChild(panicElement);if(!document.body){setTimeout(function(){document.body.appendChild(panicOverlay);},150);}else{document.body.appendChild(panicOverlay);}}function showPanic(panicTitle,panicDetails){if(tr.isHeadless){if(panicDetails instanceof Error)throw panicDetails;throw new Error('Panic: '+panicTitle+':\n'+panicDetails);}if(panicDetails instanceof Error)panicDetails=panicDetails.stack;showPanicElementIfNeeded();var panicMessageEl=document.createElement('div');panicMessageEl.innerHTML='<h2 id="message"></h2>'+'<pre id="details"></pre>';panicMessageEl.querySelector('#message').textContent=panicTitle;panicMessageEl.querySelector('#details').textContent=panicDetails;panicElement.appendChild(panicMessageEl);rawPanicMessages.push({title:panicTitle,details:panicDetails});}function hasPanic(){return rawPanicMessages.length!==0;}function getPanicText(){return rawPanicMessages.map(function(msg){return msg.title;}).join(', ');}function exportTo(namespace,fn){var obj=exportPath(namespace);var exports=fn();for(var propertyName in exports){var propertyDescriptor=Object.getOwnPropertyDescriptor(exports,propertyName);if(propertyDescriptor)Object.defineProperty(obj,propertyName,propertyDescriptor);}};function initialize(){if(global.isVinn){tr.isVinn=true;}else if(global.process&&global.process.versions.node){tr.isNode=true;}else{tr.isVinn=false;tr.isNode=false;tr.doc=document;tr.isMac=/Mac/.test(navigator.platform);tr.isWindows=/Win/.test(navigator.platform);tr.isChromeOS=/CrOS/.test(navigator.userAgent);tr.isLinux=/Linux/.test(navigator.userAgent);}tr.isHeadless=tr.isVinn||tr.isNode;}return{initialize:initialize,exportTo:exportTo,isExported:isExported,isDefined:isDefined,showPanic:showPanic,hasPanic:hasPanic,getPanicText:getPanicText};}();tr.initialize();
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{}],34:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],35:[function(require,module,exports){
+(function (global){
 "use strict";require("./base.js");'use strict';global.tr.exportTo('tr.b',function(){function Base64(){}function b64ToUint6(nChr){if(nChr>64&&nChr<91)return nChr-65;if(nChr>96&&nChr<123)return nChr-71;if(nChr>47&&nChr<58)return nChr+4;if(nChr===43)return 62;if(nChr===47)return 63;return 0;}Base64.getDecodedBufferLength=function(input){return input.length*3+1>>2;};Base64.EncodeArrayBufferToString=function(input){var binary='';var bytes=new Uint8Array(input);var len=bytes.byteLength;for(var i=0;i<len;i++)binary+=String.fromCharCode(bytes[i]);return btoa(binary);};Base64.DecodeToTypedArray=function(input,output){var nInLen=input.length;var nOutLen=nInLen*3+1>>2;var nMod3=0;var nMod4=0;var nUint24=0;var nOutIdx=0;if(nOutLen>output.byteLength)throw new Error('Output buffer too small to decode.');for(var nInIdx=0;nInIdx<nInLen;nInIdx++){nMod4=nInIdx&3;nUint24|=b64ToUint6(input.charCodeAt(nInIdx))<<18-6*nMod4;if(nMod4===3||nInLen-nInIdx===1){for(nMod3=0;nMod3<3&&nOutIdx<nOutLen;nMod3++,nOutIdx++){output.setUint8(nOutIdx,nUint24>>>(16>>>nMod3&24)&255);}nUint24=0;}}return nOutIdx-1;};Base64.btoa=function(input){return btoa(input);};Base64.atob=function(input){return atob(input);};return{Base64:Base64};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./base.js":33}],35:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./base.js":34}],36:[function(require,module,exports){
+(function (global){
 "use strict";require("./base.js");'use strict';global.tr.exportTo('tr.b',function(){var categoryPartsFor={};function getCategoryParts(category){var parts=categoryPartsFor[category];if(parts!==undefined)return parts;parts=category.split(',');categoryPartsFor[category]=parts;return parts;}return{getCategoryParts:getCategoryParts};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./base.js":33}],36:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./base.js":34}],37:[function(require,module,exports){
+(function (global){
 "use strict";require("./base.js");'use strict';global.tr.exportTo('tr.b',function(){function clamp01(value){return Math.max(0,Math.min(1,value));}function Color(opt_r,opt_g,opt_b,opt_a){this.r=Math.floor(opt_r)||0;this.g=Math.floor(opt_g)||0;this.b=Math.floor(opt_b)||0;this.a=opt_a;}Color.fromString=function(str){var tmp;var values;if(str.substr(0,4)=='rgb('){tmp=str.substr(4,str.length-5);values=tmp.split(',').map(function(v){return v.replace(/^\s+/,'','g');});if(values.length!=3)throw new Error('Malformatted rgb-expression');return new Color(parseInt(values[0]),parseInt(values[1]),parseInt(values[2]));}else if(str.substr(0,5)=='rgba('){tmp=str.substr(5,str.length-6);values=tmp.split(',').map(function(v){return v.replace(/^\s+/,'','g');});if(values.length!=4)throw new Error('Malformatted rgb-expression');return new Color(parseInt(values[0]),parseInt(values[1]),parseInt(values[2]),parseFloat(values[3]));}else if(str[0]=='#'&&str.length==7){return new Color(parseInt(str.substr(1,2),16),parseInt(str.substr(3,2),16),parseInt(str.substr(5,2),16));}else{throw new Error('Unrecognized string format.');}};Color.lerp=function(a,b,percent){if(a.a!==undefined&&b.a!==undefined)return Color.lerpRGBA(a,b,percent);return Color.lerpRGB(a,b,percent);};Color.lerpRGB=function(a,b,percent){return new Color((b.r-a.r)*percent+a.r,(b.g-a.g)*percent+a.g,(b.b-a.b)*percent+a.b);};Color.lerpRGBA=function(a,b,percent){return new Color((b.r-a.r)*percent+a.r,(b.g-a.g)*percent+a.g,(b.b-a.b)*percent+a.b,(b.a-a.a)*percent+a.a);};Color.fromDict=function(dict){return new Color(dict.r,dict.g,dict.b,dict.a);};Color.fromHSLExplicit=function(h,s,l,a){var r,g,b;function hue2rgb(p,q,t){if(t<0)t+=1;if(t>1)t-=1;if(t<1/6)return p+(q-p)*6*t;if(t<1/2)return q;if(t<2/3)return p+(q-p)*(2/3-t)*6;return p;}if(s===0){r=g=b=l;}else{var q=l<0.5?l*(1+s):l+s-l*s;var p=2*l-q;r=hue2rgb(p,q,h+1/3);g=hue2rgb(p,q,h);b=hue2rgb(p,q,h-1/3);}return new Color(Math.floor(r*255),Math.floor(g*255),Math.floor(b*255),a);};Color.fromHSL=function(hsl){return Color.fromHSLExplicit(hsl.h,hsl.s,hsl.l,hsl.a);};Color.prototype={clone:function(){var c=new Color();c.r=this.r;c.g=this.g;c.b=this.b;c.a=this.a;return c;},blendOver:function(bgColor){var oneMinusThisAlpha=1-this.a;var outA=this.a+bgColor.a*oneMinusThisAlpha;var bgBlend=bgColor.a*oneMinusThisAlpha/bgColor.a;return new Color(this.r*this.a+bgColor.r*bgBlend,this.g*this.a+bgColor.g*bgBlend,this.b*this.a+bgColor.b*bgBlend,outA);},brighten:function(opt_k){var k;k=opt_k||0.45;return new Color(Math.min(255,this.r+Math.floor(this.r*k)),Math.min(255,this.g+Math.floor(this.g*k)),Math.min(255,this.b+Math.floor(this.b*k)),this.a);},lighten:function(k,opt_maxL){var maxL=opt_maxL!==undefined?opt_maxL:1.0;var hsl=this.toHSL();hsl.l=clamp01(hsl.l+k);return Color.fromHSL(hsl);},darken:function(opt_k){var k;if(opt_k!==undefined)k=opt_k;else k=0.45;return new Color(Math.min(255,this.r-Math.floor(this.r*k)),Math.min(255,this.g-Math.floor(this.g*k)),Math.min(255,this.b-Math.floor(this.b*k)),this.a);},desaturate:function(opt_desaturateFactor){var desaturateFactor;if(opt_desaturateFactor!==undefined)desaturateFactor=opt_desaturateFactor;else desaturateFactor=1;var hsl=this.toHSL();hsl.s=clamp01(hsl.s*(1-desaturateFactor));return Color.fromHSL(hsl);},withAlpha:function(a){return new Color(this.r,this.g,this.b,a);},toString:function(){if(this.a!==undefined){return'rgba('+this.r+','+this.g+','+this.b+','+this.a+')';}return'rgb('+this.r+','+this.g+','+this.b+')';},toHSL:function(){var r=this.r/255;var g=this.g/255;var b=this.b/255;var max=Math.max(r,g,b);var min=Math.min(r,g,b);var h,s;var l=(max+min)/2;if(min===max){h=0;s=0;}else{var delta=max-min;if(l>0.5)s=delta/(2-max-min);else s=delta/(max+min);if(r===max){h=(g-b)/delta;if(g<b)h+=6;}else if(g===max){h=2+(b-r)/delta;}else{h=4+(r-g)/delta;}h/=6;}return{h:h,s:s,l:l,a:this.a};},toStringWithAlphaOverride:function(alpha){return'rgba('+this.r+','+this.g+','+this.b+','+alpha+')';}};return{Color:Color};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./base.js":33}],37:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./base.js":34}],38:[function(require,module,exports){
+(function (global){
 "use strict";require("./base.js");require("./color.js");require("./iteration_helpers.js");'use strict';global.tr.exportTo('tr.b',function(){var generalPurposeColors=[new tr.b.Color(122,98,135),new tr.b.Color(150,83,105),new tr.b.Color(44,56,189),new tr.b.Color(99,86,147),new tr.b.Color(104,129,107),new tr.b.Color(130,178,55),new tr.b.Color(87,109,147),new tr.b.Color(111,145,88),new tr.b.Color(81,152,131),new tr.b.Color(142,91,111),new tr.b.Color(81,163,70),new tr.b.Color(148,94,86),new tr.b.Color(144,89,118),new tr.b.Color(83,150,97),new tr.b.Color(105,94,139),new tr.b.Color(89,144,122),new tr.b.Color(105,119,128),new tr.b.Color(96,128,137),new tr.b.Color(145,88,145),new tr.b.Color(88,145,144),new tr.b.Color(90,100,143),new tr.b.Color(121,97,136),new tr.b.Color(111,160,73),new tr.b.Color(112,91,142),new tr.b.Color(86,147,86),new tr.b.Color(63,100,170),new tr.b.Color(81,152,107),new tr.b.Color(60,164,173),new tr.b.Color(143,72,161),new tr.b.Color(159,74,86)];var reservedColorsByName={thread_state_uninterruptible:new tr.b.Color(182,125,143),thread_state_iowait:new tr.b.Color(255,140,0),thread_state_running:new tr.b.Color(126,200,148),thread_state_runnable:new tr.b.Color(133,160,210),thread_state_sleeping:new tr.b.Color(240,240,240),thread_state_unknown:new tr.b.Color(199,155,125),background_memory_dump:new tr.b.Color(0,180,180),light_memory_dump:new tr.b.Color(0,0,180),detailed_memory_dump:new tr.b.Color(180,0,180),generic_work:new tr.b.Color(125,125,125),good:new tr.b.Color(0,125,0),bad:new tr.b.Color(180,125,0),terrible:new tr.b.Color(180,0,0),black:new tr.b.Color(0,0,0),rail_response:new tr.b.Color(67,135,253),rail_animation:new tr.b.Color(244,74,63),rail_idle:new tr.b.Color(238,142,0),rail_load:new tr.b.Color(13,168,97),startup:new tr.b.Color(230,230,0),used_memory_column:new tr.b.Color(0,0,255),older_used_memory_column:new tr.b.Color(153,204,255),tracing_memory_column:new tr.b.Color(153,153,153),heap_dump_stack_frame:new tr.b.Color(128,128,128),heap_dump_object_type:new tr.b.Color(0,0,255),heap_dump_child_node_arrow:new tr.b.Color(204,102,0),cq_build_running:new tr.b.Color(255,255,119),cq_build_passed:new tr.b.Color(153,238,102),cq_build_failed:new tr.b.Color(238,136,136),cq_build_abandoned:new tr.b.Color(187,187,187),cq_build_attempt_runnig:new tr.b.Color(222,222,75),cq_build_attempt_passed:new tr.b.Color(103,218,35),cq_build_attempt_failed:new tr.b.Color(197,81,81)};var numGeneralPurposeColorIds=generalPurposeColors.length;var numReservedColorIds=tr.b.dictionaryLength(reservedColorsByName);var numColorsPerVariant=numGeneralPurposeColorIds+numReservedColorIds;function ColorScheme(){}var paletteBase=[];paletteBase.push.apply(paletteBase,generalPurposeColors);paletteBase.push.apply(paletteBase,tr.b.dictionaryValues(reservedColorsByName));ColorScheme.colors=[];ColorScheme.properties={};ColorScheme.properties={numColorsPerVariant:numColorsPerVariant};function pushVariant(func){var variantColors=paletteBase.map(func);ColorScheme.colors.push.apply(ColorScheme.colors,variantColors);}pushVariant(function(c){return c;});ColorScheme.properties.brightenedOffsets=[];ColorScheme.properties.brightenedOffsets.push(ColorScheme.colors.length);pushVariant(function(c){return c.lighten(0.3,0.9);});ColorScheme.properties.brightenedOffsets.push(ColorScheme.colors.length);pushVariant(function(c){return c.lighten(0.48,0.9);});ColorScheme.properties.brightenedOffsets.push(ColorScheme.colors.length);pushVariant(function(c){return c.lighten(0.65,0.9);});ColorScheme.properties.dimmedOffsets=[];ColorScheme.properties.dimmedOffsets.push(ColorScheme.colors.length);pushVariant(function(c){return c.desaturate();});ColorScheme.properties.dimmedOffsets.push(ColorScheme.colors.length);pushVariant(function(c){return c.desaturate(0.5);});ColorScheme.properties.dimmedOffsets.push(ColorScheme.colors.length);pushVariant(function(c){return c.desaturate(0.3);});ColorScheme.colorsAsStrings=ColorScheme.colors.map(function(c){return c.toString();});var reservedColorNameToIdMap=function(){var m=new Map();var i=generalPurposeColors.length;tr.b.iterItems(reservedColorsByName,function(key,value){m.set(key,i++);});return m;}();ColorScheme.getColorIdForReservedName=function(name){var id=reservedColorNameToIdMap.get(name);if(id===undefined)throw new Error('Unrecognized color ')+name;return id;};ColorScheme.getColorForReservedNameAsString=function(reservedName){var id=ColorScheme.getColorIdForReservedName(reservedName);return ColorScheme.colorsAsStrings[id];};ColorScheme.getStringHash=function(name){var hash=0;for(var i=0;i<name.length;++i)hash=(hash+37*hash+11*name.charCodeAt(i))%0xFFFFFFFF;return hash;};var stringColorIdCache=new Map();ColorScheme.getColorIdForGeneralPurposeString=function(string){if(stringColorIdCache.get(string)===undefined){var hash=ColorScheme.getStringHash(string);stringColorIdCache.set(string,hash%numGeneralPurposeColorIds);}return stringColorIdCache.get(string);};return{ColorScheme:ColorScheme};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./base.js":33,"./color.js":36,"./iteration_helpers.js":46}],38:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./base.js":34,"./color.js":37,"./iteration_helpers.js":47}],39:[function(require,module,exports){
+(function (global){
 "use strict";require("./event_target.js");'use strict';global.tr.exportTo('tr.b',function(){var Event;if(tr.isHeadless){function HeadlessEvent(type,opt_bubbles,opt_preventable){this.type=type;this.bubbles=opt_bubbles!==undefined?!!opt_bubbles:false;this.cancelable=opt_preventable!==undefined?!!opt_preventable:false;this.defaultPrevented=false;this.cancelBubble=false;};HeadlessEvent.prototype={preventDefault:function(){this.defaultPrevented=true;},stopPropagation:function(){this.cancelBubble=true;}};Event=HeadlessEvent;}else{function TrEvent(type,opt_bubbles,opt_preventable){var e=tr.doc.createEvent('Event');e.initEvent(type,!!opt_bubbles,!!opt_preventable);e.__proto__=global.Event.prototype;return e;};TrEvent.prototype={__proto__:global.Event.prototype};Event=TrEvent;}function dispatchSimpleEvent(target,type,opt_bubbles,opt_cancelable,opt_fields){var e=new tr.b.Event(type,opt_bubbles,opt_cancelable);if(opt_fields){tr.b.iterItems(opt_fields,function(name,value){e[name]=value;});}return target.dispatchEvent(e);}return{Event:Event,dispatchSimpleEvent:dispatchSimpleEvent};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./event_target.js":39}],39:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./event_target.js":40}],40:[function(require,module,exports){
+(function (global){
 "use strict";require("./base.js");'use strict';global.tr.exportTo('tr.b',function(){function EventTarget(){}EventTarget.decorate=function(target){for(var k in EventTarget.prototype){if(k=='decorate')continue;var v=EventTarget.prototype[k];if(typeof v!=='function')continue;target[k]=v;}};EventTarget.prototype={addEventListener:function(type,handler){if(!this.listeners_)this.listeners_=Object.create(null);if(!(type in this.listeners_)){this.listeners_[type]=[handler];}else{var handlers=this.listeners_[type];if(handlers.indexOf(handler)<0)handlers.push(handler);}},removeEventListener:function(type,handler){if(!this.listeners_)return;if(type in this.listeners_){var handlers=this.listeners_[type];var index=handlers.indexOf(handler);if(index>=0){if(handlers.length==1)delete this.listeners_[type];else handlers.splice(index,1);}}},dispatchEvent:function(event){if(!this.listeners_)return true;var self=this;event.__defineGetter__('target',function(){return self;});var realPreventDefault=event.preventDefault;event.preventDefault=function(){realPreventDefault.call(this);this.rawReturnValue=false;};var type=event.type;var prevented=0;if(type in this.listeners_){var handlers=this.listeners_[type].concat();for(var i=0,handler;handler=handlers[i];i++){if(handler.handleEvent)prevented|=handler.handleEvent.call(handler,event)===false;else prevented|=handler.call(this,event)===false;}}return!prevented&&event.rawReturnValue;},hasEventListener:function(type){return this.listeners_[type]!==undefined;}};var EventTargetHelper={decorate:function(target){for(var k in EventTargetHelper){if(k=='decorate')continue;var v=EventTargetHelper[k];if(typeof v!=='function')continue;target[k]=v;}target.listenerCounts_={};},addEventListener:function(type,listener,useCapture){this.__proto__.addEventListener.call(this,type,listener,useCapture);if(this.listenerCounts_[type]===undefined)this.listenerCounts_[type]=0;this.listenerCounts_[type]++;},removeEventListener:function(type,listener,useCapture){this.__proto__.removeEventListener.call(this,type,listener,useCapture);this.listenerCounts_[type]--;},hasEventListener:function(type){return this.listenerCounts_[type]>0;}};return{EventTarget:EventTarget,EventTargetHelper:EventTargetHelper};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./base.js":33}],40:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./base.js":34}],41:[function(require,module,exports){
+(function (global){
 "use strict";require("./event_target.js");require("./extension_registry_base.js");require("./extension_registry_basic.js");require("./extension_registry_type_based.js");require("./iteration_helpers.js");'use strict';global.tr.exportTo('tr.b',function(){function decorateExtensionRegistry(registry,registryOptions){if(registry.register)throw new Error('Already has registry');registryOptions.freeze();if(registryOptions.mode==tr.b.BASIC_REGISTRY_MODE){tr.b._decorateBasicExtensionRegistry(registry,registryOptions);}else if(registryOptions.mode==tr.b.TYPE_BASED_REGISTRY_MODE){tr.b._decorateTypeBasedExtensionRegistry(registry,registryOptions);}else{throw new Error('Unrecognized mode');}if(registry.addEventListener===undefined)tr.b.EventTarget.decorate(registry);}return{decorateExtensionRegistry:decorateExtensionRegistry};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./event_target.js":39,"./extension_registry_base.js":41,"./extension_registry_basic.js":42,"./extension_registry_type_based.js":43,"./iteration_helpers.js":46}],41:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./event_target.js":40,"./extension_registry_base.js":42,"./extension_registry_basic.js":43,"./extension_registry_type_based.js":44,"./iteration_helpers.js":47}],42:[function(require,module,exports){
+(function (global){
 "use strict";require("./base.js");'use strict';global.tr.exportTo('tr.b',function(){function RegisteredTypeInfo(constructor,metadata){this.constructor=constructor;this.metadata=metadata;};var BASIC_REGISTRY_MODE='BASIC_REGISTRY_MODE';var TYPE_BASED_REGISTRY_MODE='TYPE_BASED_REGISTRY_MODE';var ALL_MODES={BASIC_REGISTRY_MODE:true,TYPE_BASED_REGISTRY_MODE:true};function ExtensionRegistryOptions(mode){if(mode===undefined)throw new Error('Mode is required');if(!ALL_MODES[mode])throw new Error('Not a mode.');this.mode_=mode;this.defaultMetadata_={};this.defaultConstructor_=undefined;this.defaultTypeInfo_=undefined;this.frozen_=false;}ExtensionRegistryOptions.prototype={freeze:function(){if(this.frozen_)throw new Error('Frozen');this.frozen_=true;},get mode(){return this.mode_;},get defaultMetadata(){return this.defaultMetadata_;},set defaultMetadata(defaultMetadata){if(this.frozen_)throw new Error('Frozen');this.defaultMetadata_=defaultMetadata;this.defaultTypeInfo_=undefined;},get defaultConstructor(){return this.defaultConstructor_;},set defaultConstructor(defaultConstructor){if(this.frozen_)throw new Error('Frozen');this.defaultConstructor_=defaultConstructor;this.defaultTypeInfo_=undefined;},get defaultTypeInfo(){if(this.defaultTypeInfo_===undefined&&this.defaultConstructor_){this.defaultTypeInfo_=new RegisteredTypeInfo(this.defaultConstructor,this.defaultMetadata);}return this.defaultTypeInfo_;},validateConstructor:function(constructor){if(!this.mandatoryBaseClass)return;var curProto=constructor.prototype.__proto__;var ok=false;while(curProto){if(curProto===this.mandatoryBaseClass.prototype){ok=true;break;}curProto=curProto.__proto__;}if(!ok)throw new Error(constructor+'must be subclass of '+registry);}};return{BASIC_REGISTRY_MODE:BASIC_REGISTRY_MODE,TYPE_BASED_REGISTRY_MODE:TYPE_BASED_REGISTRY_MODE,ExtensionRegistryOptions:ExtensionRegistryOptions,RegisteredTypeInfo:RegisteredTypeInfo};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./base.js":33}],42:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./base.js":34}],43:[function(require,module,exports){
+(function (global){
 "use strict";require("./event.js");require("./extension_registry_base.js");'use strict';global.tr.exportTo('tr.b',function(){var RegisteredTypeInfo=tr.b.RegisteredTypeInfo;var ExtensionRegistryOptions=tr.b.ExtensionRegistryOptions;function decorateBasicExtensionRegistry(registry,extensionRegistryOptions){var savedStateStack=[];registry.registeredTypeInfos_=[];registry.register=function(constructor,opt_metadata){if(registry.findIndexOfRegisteredConstructor(constructor)!==undefined)throw new Error('Handler already registered for '+constructor);extensionRegistryOptions.validateConstructor(constructor);var metadata={};for(var k in extensionRegistryOptions.defaultMetadata)metadata[k]=extensionRegistryOptions.defaultMetadata[k];if(opt_metadata){for(var k in opt_metadata)metadata[k]=opt_metadata[k];}var typeInfo=new RegisteredTypeInfo(constructor,metadata);var e=new tr.b.Event('will-register');e.typeInfo=typeInfo;registry.dispatchEvent(e);registry.registeredTypeInfos_.push(typeInfo);e=new tr.b.Event('registry-changed');registry.dispatchEvent(e);};registry.pushCleanStateBeforeTest=function(){savedStateStack.push(registry.registeredTypeInfos_);registry.registeredTypeInfos_=[];var e=new tr.b.Event('registry-changed');registry.dispatchEvent(e);};registry.popCleanStateAfterTest=function(){registry.registeredTypeInfos_=savedStateStack[0];savedStateStack.splice(0,1);var e=new tr.b.Event('registry-changed');registry.dispatchEvent(e);};registry.findIndexOfRegisteredConstructor=function(constructor){for(var i=0;i<registry.registeredTypeInfos_.length;i++)if(registry.registeredTypeInfos_[i].constructor==constructor)return i;return undefined;};registry.unregister=function(constructor){var foundIndex=registry.findIndexOfRegisteredConstructor(constructor);if(foundIndex===undefined)throw new Error(constructor+' not registered');registry.registeredTypeInfos_.splice(foundIndex,1);var e=new tr.b.Event('registry-changed');registry.dispatchEvent(e);};registry.getAllRegisteredTypeInfos=function(){return registry.registeredTypeInfos_;};registry.findTypeInfo=function(constructor){var foundIndex=this.findIndexOfRegisteredConstructor(constructor);if(foundIndex!==undefined)return this.registeredTypeInfos_[foundIndex];return undefined;};registry.findTypeInfoMatching=function(predicate,opt_this){opt_this=opt_this?opt_this:undefined;for(var i=0;i<registry.registeredTypeInfos_.length;++i){var typeInfo=registry.registeredTypeInfos_[i];if(predicate.call(opt_this,typeInfo))return typeInfo;}return extensionRegistryOptions.defaultTypeInfo;};registry.findTypeInfoWithName=function(name){if(typeof name!=='string')throw new Error('Name is not a string.');var typeInfo=registry.findTypeInfoMatching(function(ti){return ti.constructor.name===name;});if(typeInfo)return typeInfo;return undefined;};}return{_decorateBasicExtensionRegistry:decorateBasicExtensionRegistry};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./event.js":38,"./extension_registry_base.js":41}],43:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./event.js":39,"./extension_registry_base.js":42}],44:[function(require,module,exports){
+(function (global){
 "use strict";require("./category_util.js");require("./event.js");require("./extension_registry_base.js");'use strict';global.tr.exportTo('tr.b',function(){var getCategoryParts=tr.b.getCategoryParts;var RegisteredTypeInfo=tr.b.RegisteredTypeInfo;var ExtensionRegistryOptions=tr.b.ExtensionRegistryOptions;function decorateTypeBasedExtensionRegistry(registry,extensionRegistryOptions){var savedStateStack=[];registry.registeredTypeInfos_=[];registry.categoryPartToTypeInfoMap_=new Map();registry.typeNameToTypeInfoMap_=new Map();registry.register=function(constructor,metadata){extensionRegistryOptions.validateConstructor(constructor);var typeInfo=new RegisteredTypeInfo(constructor,metadata||extensionRegistryOptions.defaultMetadata);typeInfo.typeNames=[];typeInfo.categoryParts=[];if(metadata&&metadata.typeName)typeInfo.typeNames.push(metadata.typeName);if(metadata&&metadata.typeNames){typeInfo.typeNames.push.apply(typeInfo.typeNames,metadata.typeNames);}if(metadata&&metadata.categoryParts){typeInfo.categoryParts.push.apply(typeInfo.categoryParts,metadata.categoryParts);}if(typeInfo.typeNames.length===0&&typeInfo.categoryParts.length===0)throw new Error('typeName or typeNames must be provided');typeInfo.typeNames.forEach(function(typeName){if(registry.typeNameToTypeInfoMap_.has(typeName))throw new Error('typeName '+typeName+' already registered');});typeInfo.categoryParts.forEach(function(categoryPart){if(registry.categoryPartToTypeInfoMap_.has(categoryPart)){throw new Error('categoryPart '+categoryPart+' already registered');}});var e=new tr.b.Event('will-register');e.typeInfo=typeInfo;registry.dispatchEvent(e);typeInfo.typeNames.forEach(function(typeName){registry.typeNameToTypeInfoMap_.set(typeName,typeInfo);});typeInfo.categoryParts.forEach(function(categoryPart){registry.categoryPartToTypeInfoMap_.set(categoryPart,typeInfo);});registry.registeredTypeInfos_.push(typeInfo);var e=new tr.b.Event('registry-changed');registry.dispatchEvent(e);};registry.pushCleanStateBeforeTest=function(){savedStateStack.push({registeredTypeInfos:registry.registeredTypeInfos_,typeNameToTypeInfoMap:registry.typeNameToTypeInfoMap_,categoryPartToTypeInfoMap:registry.categoryPartToTypeInfoMap_});registry.registeredTypeInfos_=[];registry.typeNameToTypeInfoMap_=new Map();registry.categoryPartToTypeInfoMap_=new Map();var e=new tr.b.Event('registry-changed');registry.dispatchEvent(e);};registry.popCleanStateAfterTest=function(){var state=savedStateStack[0];savedStateStack.splice(0,1);registry.registeredTypeInfos_=state.registeredTypeInfos;registry.typeNameToTypeInfoMap_=state.typeNameToTypeInfoMap;registry.categoryPartToTypeInfoMap_=state.categoryPartToTypeInfoMap;var e=new tr.b.Event('registry-changed');registry.dispatchEvent(e);};registry.unregister=function(constructor){var typeInfoIndex=-1;for(var i=0;i<registry.registeredTypeInfos_.length;i++){if(registry.registeredTypeInfos_[i].constructor==constructor){typeInfoIndex=i;break;}}if(typeInfoIndex===-1)throw new Error(constructor+' not registered');var typeInfo=registry.registeredTypeInfos_[typeInfoIndex];registry.registeredTypeInfos_.splice(typeInfoIndex,1);typeInfo.typeNames.forEach(function(typeName){registry.typeNameToTypeInfoMap_.delete(typeName);});typeInfo.categoryParts.forEach(function(categoryPart){registry.categoryPartToTypeInfoMap_.delete(categoryPart);});var e=new tr.b.Event('registry-changed');registry.dispatchEvent(e);};registry.getTypeInfo=function(category,typeName){if(category){var categoryParts=getCategoryParts(category);for(var i=0;i<categoryParts.length;i++){var categoryPart=categoryParts[i];var typeInfo=registry.categoryPartToTypeInfoMap_.get(categoryPart);if(typeInfo!==undefined)return typeInfo;}}var typeInfo=registry.typeNameToTypeInfoMap_.get(typeName);if(typeInfo!==undefined)return typeInfo;return extensionRegistryOptions.defaultTypeInfo;};registry.getConstructor=function(category,typeName){var typeInfo=registry.getTypeInfo(category,typeName);if(typeInfo)return typeInfo.constructor;return undefined;};}return{_decorateTypeBasedExtensionRegistry:decorateTypeBasedExtensionRegistry};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./category_util.js":35,"./event.js":38,"./extension_registry_base.js":41}],44:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./category_util.js":36,"./event.js":39,"./extension_registry_base.js":42}],45:[function(require,module,exports){
+(function (global){
 "use strict";require("./base.js");'use strict';global.tr.exportTo('tr.b',function(){var nextGUID=1;var UUID4_PATTERN='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';var GUID={allocateSimple:function(){return nextGUID++;},getLastSimpleGuid:function(){return nextGUID-1;},allocateUUID4:function(){return UUID4_PATTERN.replace(/[xy]/g,function(c){var r=parseInt(Math.random()*16);if(c==='y')r=(r&3)+8;return r.toString(16);});}};return{GUID:GUID};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./base.js":33}],45:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./base.js":34}],46:[function(require,module,exports){
+(function (global){
 "use strict";require("./base.js");'use strict';global.tr.exportTo('tr.b',function(){function max(a,b){if(a===undefined)return b;if(b===undefined)return a;return Math.max(a,b);}function IntervalTree(beginPositionCb,endPositionCb){this.beginPositionCb_=beginPositionCb;this.endPositionCb_=endPositionCb;this.root_=undefined;this.size_=0;}IntervalTree.prototype={insert:function(datum){var startPosition=this.beginPositionCb_(datum);var endPosition=this.endPositionCb_(datum);var node=new IntervalTreeNode(datum,startPosition,endPosition);this.size_++;this.root_=this.insertNode_(this.root_,node);this.root_.colour=Colour.BLACK;return datum;},insertNode_:function(root,node){if(root===undefined)return node;if(root.leftNode&&root.leftNode.isRed&&root.rightNode&&root.rightNode.isRed)this.flipNodeColour_(root);if(node.key<root.key)root.leftNode=this.insertNode_(root.leftNode,node);else if(node.key===root.key)root.merge(node);else root.rightNode=this.insertNode_(root.rightNode,node);if(root.rightNode&&root.rightNode.isRed&&(root.leftNode===undefined||!root.leftNode.isRed))root=this.rotateLeft_(root);if(root.leftNode&&root.leftNode.isRed&&root.leftNode.leftNode&&root.leftNode.leftNode.isRed)root=this.rotateRight_(root);return root;},rotateRight_:function(node){var sibling=node.leftNode;node.leftNode=sibling.rightNode;sibling.rightNode=node;sibling.colour=node.colour;node.colour=Colour.RED;return sibling;},rotateLeft_:function(node){var sibling=node.rightNode;node.rightNode=sibling.leftNode;sibling.leftNode=node;sibling.colour=node.colour;node.colour=Colour.RED;return sibling;},flipNodeColour_:function(node){node.colour=this.flipColour_(node.colour);node.leftNode.colour=this.flipColour_(node.leftNode.colour);node.rightNode.colour=this.flipColour_(node.rightNode.colour);},flipColour_:function(colour){return colour===Colour.RED?Colour.BLACK:Colour.RED;},updateHighValues:function(){this.updateHighValues_(this.root_);},updateHighValues_:function(node){if(node===undefined)return undefined;node.maxHighLeft=this.updateHighValues_(node.leftNode);node.maxHighRight=this.updateHighValues_(node.rightNode);return max(max(node.maxHighLeft,node.highValue),node.maxHighRight);},validateFindArguments_:function(queryLow,queryHigh){if(queryLow===undefined||queryHigh===undefined)throw new Error('queryLow and queryHigh must be defined');if(typeof queryLow!=='number'||typeof queryHigh!=='number')throw new Error('queryLow and queryHigh must be numbers');},findIntersection:function(queryLow,queryHigh){this.validateFindArguments_(queryLow,queryHigh);if(this.root_===undefined)return[];var ret=[];this.root_.appendIntersectionsInto_(ret,queryLow,queryHigh);return ret;},get size(){return this.size_;},get root(){return this.root_;},dump_:function(){if(this.root_===undefined)return[];return this.root_.dump();}};var Colour={RED:'red',BLACK:'black'};function IntervalTreeNode(datum,lowValue,highValue){this.lowValue_=lowValue;this.data_=[{datum:datum,high:highValue,low:lowValue}];this.colour_=Colour.RED;this.parentNode_=undefined;this.leftNode_=undefined;this.rightNode_=undefined;this.maxHighLeft_=undefined;this.maxHighRight_=undefined;}IntervalTreeNode.prototype={appendIntersectionsInto_:function(ret,queryLow,queryHigh){if(this.lowValue_>=queryHigh){if(!this.leftNode_)return;return this.leftNode_.appendIntersectionsInto_(ret,queryLow,queryHigh);}if(this.maxHighLeft_>queryLow){this.leftNode_.appendIntersectionsInto_(ret,queryLow,queryHigh);}if(this.highValue>queryLow){for(var i=this.data.length-1;i>=0;--i){if(this.data[i].high<queryLow)break;ret.push(this.data[i].datum);}}if(this.rightNode_){this.rightNode_.appendIntersectionsInto_(ret,queryLow,queryHigh);}},get colour(){return this.colour_;},set colour(colour){this.colour_=colour;},get key(){return this.lowValue_;},get lowValue(){return this.lowValue_;},get highValue(){return this.data_[this.data_.length-1].high;},set leftNode(left){this.leftNode_=left;},get leftNode(){return this.leftNode_;},get hasLeftNode(){return this.leftNode_!==undefined;},set rightNode(right){this.rightNode_=right;},get rightNode(){return this.rightNode_;},get hasRightNode(){return this.rightNode_!==undefined;},set parentNode(parent){this.parentNode_=parent;},get parentNode(){return this.parentNode_;},get isRootNode(){return this.parentNode_===undefined;},set maxHighLeft(high){this.maxHighLeft_=high;},get maxHighLeft(){return this.maxHighLeft_;},set maxHighRight(high){this.maxHighRight_=high;},get maxHighRight(){return this.maxHighRight_;},get data(){return this.data_;},get isRed(){return this.colour_===Colour.RED;},merge:function(node){for(var i=0;i<node.data.length;i++)this.data_.push(node.data[i]);this.data_.sort(function(a,b){return a.high-b.high;});},dump:function(){var ret={};if(this.leftNode_)ret['left']=this.leftNode_.dump();ret['data']=this.data_.map(function(d){return[d.low,d.high];});if(this.rightNode_)ret['right']=this.rightNode_.dump();return ret;}};return{IntervalTree:IntervalTree};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./base.js":33}],46:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./base.js":34}],47:[function(require,module,exports){
+(function (global){
 "use strict";require("./base.js");'use strict';global.tr.exportTo('tr.b',function(){function asArray(x){var values=[];if(x[Symbol.iterator])for(var value of x)values.push(value);else for(var i=0;i<x.length;i++)values.push(x[i]);return values;}function getOnlyElement(iterable){var iterator=iterable[Symbol.iterator]();var firstIteration=iterator.next();if(firstIteration.done)throw new Error('getOnlyElement was passed an empty iterable.');var secondIteration=iterator.next();if(!secondIteration.done)throw new Error('getOnlyElement was passed an iterable with multiple elements.');return firstIteration.value;}function getFirstElement(iterable){var iterator=iterable[Symbol.iterator]();var result=iterator.next();if(result.done)throw new Error('getFirstElement was passed an empty iterable.');return result.value;}function compareArrays(x,y,elementCmp){var minLength=Math.min(x.length,y.length);for(var i=0;i<minLength;i++){var tmp=elementCmp(x[i],y[i]);if(tmp)return tmp;}if(x.length==y.length)return 0;if(x[i]===undefined)return-1;return 1;}function comparePossiblyUndefinedValues(x,y,cmp,opt_this){if(x!==undefined&&y!==undefined)return cmp.call(opt_this,x,y);if(x!==undefined)return-1;if(y!==undefined)return 1;return 0;}function compareNumericWithNaNs(x,y){if(!isNaN(x)&&!isNaN(y))return x-y;if(isNaN(x))return 1;if(isNaN(y))return-1;return 0;}function concatenateArrays(){var values=[];for(var i=0;i<arguments.length;i++){if(!(arguments[i]instanceof Array))throw new Error('Arguments '+i+'is not an array');values.push.apply(values,arguments[i]);}return values;}function concatenateObjects(){var result={};for(var i=0;i<arguments.length;i++){var object=arguments[i];for(var j in object){result[j]=object[j];}}return result;}function cloneDictionary(dict){var clone={};for(var k in dict){clone[k]=dict[k];}return clone;}function dictionaryKeys(dict){var keys=[];for(var key in dict)keys.push(key);return keys;}function dictionaryValues(dict){var values=[];for(var key in dict)values.push(dict[key]);return values;}function dictionaryLength(dict){var n=0;for(var key in dict)n++;return n;}function dictionaryContainsValue(dict,value){for(var key in dict)if(dict[key]===value)return true;return false;}function every(iterable,predicate){for(var x of iterable)if(!predicate(x))return false;return true;}function group(ary,callback,opt_this,opt_arrayConstructor){var arrayConstructor=opt_arrayConstructor||Array;var results={};for(var element of ary){var key=callback.call(opt_this,element);if(!(key in results))results[key]=new arrayConstructor();results[key].push(element);}return results;}function groupIntoMap(ary,callback,opt_this,opt_arrayConstructor){var arrayConstructor=opt_arrayConstructor||Array;var results=new Map();for(var element of ary){var key=callback.call(opt_this,element);var items=results.get(key);if(items===undefined){items=new arrayConstructor();results.set(key,items);}items.push(element);}return results;}function iterItems(dict,fn,opt_this){opt_this=opt_this||this;var keys=Object.keys(dict);for(var i=0;i<keys.length;i++){var key=keys[i];fn.call(opt_this,key,dict[key]);}}function mapItems(dict,fn,opt_this){opt_this=opt_this||this;var result={};var keys=Object.keys(dict);for(var i=0;i<keys.length;i++){var key=keys[i];result[key]=fn.call(opt_this,key,dict[key]);}return result;}function filterItems(dict,predicate,opt_this){opt_this=opt_this||this;var result={};var keys=Object.keys(dict);for(var i=0;i<keys.length;i++){var key=keys[i];var value=dict[key];if(predicate.call(opt_this,key,value))result[key]=value;}return result;}function iterObjectFieldsRecursively(object,func){if(!(object instanceof Object))return;if(object instanceof Array){for(var i=0;i<object.length;i++){func(object,i,object[i]);iterObjectFieldsRecursively(object[i],func);}return;}for(var key in object){var value=object[key];func(object,key,value);iterObjectFieldsRecursively(value,func);}}function invertArrayOfDicts(array,opt_dictGetter,opt_this){opt_this=opt_this||this;var result={};for(var i=0;i<array.length;i++){var item=array[i];if(item===undefined)continue;var dict=opt_dictGetter?opt_dictGetter.call(opt_this,item):item;if(dict===undefined)continue;for(var key in dict){var valueList=result[key];if(valueList===undefined)result[key]=valueList=new Array(array.length);valueList[i]=dict[key];}}return result;}function arrayToDict(array,valueToKeyFn,opt_this){opt_this=opt_this||this;var result={};var length=array.length;for(var i=0;i<length;i++){var value=array[i];var key=valueToKeyFn.call(opt_this,value);result[key]=value;}return result;}function identity(d){return d;}function findFirstIndexInArray(ary,opt_func,opt_this){var func=opt_func||identity;for(var i=0;i<ary.length;i++){if(func.call(opt_this,ary[i],i))return i;}return-1;}function findFirstInArray(ary,opt_func,opt_this){var i=findFirstIndexInArray(ary,opt_func,opt_func);if(i===-1)return undefined;return ary[i];}function findFirstKeyInDictMatching(dict,opt_func,opt_this){var func=opt_func||identity;for(var key in dict){if(func.call(opt_this,key,dict[key]))return key;}return undefined;}function mapValues(map){var values=[];for(var value of map.values())values.push(value);return values;}function iterMapItems(map,fn,opt_this){opt_this=opt_this||this;for(var key of map.keys())fn.call(opt_this,key,map.get(key));}return{asArray:asArray,concatenateArrays:concatenateArrays,concatenateObjects:concatenateObjects,compareArrays:compareArrays,comparePossiblyUndefinedValues:comparePossiblyUndefinedValues,compareNumericWithNaNs:compareNumericWithNaNs,cloneDictionary:cloneDictionary,dictionaryLength:dictionaryLength,dictionaryKeys:dictionaryKeys,dictionaryValues:dictionaryValues,dictionaryContainsValue:dictionaryContainsValue,every:every,getOnlyElement:getOnlyElement,getFirstElement:getFirstElement,group:group,groupIntoMap:groupIntoMap,iterItems:iterItems,mapItems:mapItems,filterItems:filterItems,iterObjectFieldsRecursively:iterObjectFieldsRecursively,invertArrayOfDicts:invertArrayOfDicts,arrayToDict:arrayToDict,identity:identity,findFirstIndexInArray:findFirstIndexInArray,findFirstInArray:findFirstInArray,findFirstKeyInDictMatching:findFirstKeyInDictMatching,mapValues:mapValues,iterMapItems:iterMapItems};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./base.js":33}],47:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./base.js":34}],48:[function(require,module,exports){
+(function (global){
 "use strict";require("./base.js");'use strict';(function(){if(tr.isNode){var glMatrixAbsPath=HTMLImportsLoader.hrefToAbsolutePath('/gl-matrix-min.js');var glMatrixModule=require(glMatrixAbsPath);for(var exportName in glMatrixModule){global[exportName]=glMatrixModule[exportName];}}})(this);'use strict';global.tr.exportTo('tr.b',function(){function approximately(x,y,delta){if(delta===undefined)delta=1e-9;return Math.abs(x-y)<delta;}function clamp(x,lo,hi){return Math.min(Math.max(x,lo),hi);}function lerp(percentage,lo,hi){var range=hi-lo;return lo+percentage*range;}function normalize(value,lo,hi){return(value-lo)/(hi-lo);}function deg2rad(deg){return Math.PI*deg/180.0;}function erf(x){var sign=x>=0?1:-1;x=Math.abs(x);var a1=0.254829592;var a2=-0.284496736;var a3=1.421413741;var a4=-1.453152027;var a5=1.061405429;var p=0.3275911;var t=1.0/(1.0+p*x);var y=1.0-((((a5*t+a4)*t+a3)*t+a2)*t+a1)*t*Math.exp(-x*x);return sign*y;}var tmpVec2=vec2.create();var tmpVec2b=vec2.create();var tmpVec4=vec4.create();var tmpMat2d=mat2d.create();vec2.createFromArray=function(arr){if(arr.length!=2)throw new Error('Should be length 2');var v=vec2.create();vec2.set(v,arr[0],arr[1]);return v;};vec2.createXY=function(x,y){var v=vec2.create();vec2.set(v,x,y);return v;};vec2.toString=function(a){return'['+a[0]+', '+a[1]+']';};vec2.addTwoScaledUnitVectors=function(out,u1,scale1,u2,scale2){vec2.scale(tmpVec2,u1,scale1);vec2.scale(tmpVec2b,u2,scale2);vec2.add(out,tmpVec2,tmpVec2b);};vec2.interpolatePiecewiseFunction=function(points,x){if(x<points[0][0])return points[0][1];for(var i=1;i<points.length;++i){if(x<points[i][0]){var percent=normalize(x,points[i-1][0],points[i][0]);return lerp(percent,points[i-1][1],points[i][1]);}}return points[points.length-1][1];};vec3.createXYZ=function(x,y,z){var v=vec3.create();vec3.set(v,x,y,z);return v;};vec3.toString=function(a){return'vec3('+a[0]+', '+a[1]+', '+a[2]+')';};mat2d.translateXY=function(out,x,y){vec2.set(tmpVec2,x,y);mat2d.translate(out,out,tmpVec2);};mat2d.scaleXY=function(out,x,y){vec2.set(tmpVec2,x,y);mat2d.scale(out,out,tmpVec2);};vec4.unitize=function(out,a){out[0]=a[0]/a[3];out[1]=a[1]/a[3];out[2]=a[2]/a[3];out[3]=1;return out;};vec2.copyFromVec4=function(out,a){vec4.unitize(tmpVec4,a);vec2.copy(out,tmpVec4);};return{approximately:approximately,clamp:clamp,lerp:lerp,normalize:normalize,deg2rad:deg2rad,erf:erf};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./base.js":33}],48:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./base.js":34}],49:[function(require,module,exports){
+(function (global){
 "use strict";require("./base.js");'use strict';global.tr.exportTo('tr.b',function(){function MultiDimensionalViewNode(title,valueCount){this.title=title;var dimensions=title.length;this.children=new Array(dimensions);for(var i=0;i<dimensions;i++)this.children[i]=new Map();this.values=new Array(valueCount);for(var v=0;v<valueCount;v++)this.values[v]={self:0,total:0,totalState:NOT_PROVIDED};}MultiDimensionalViewNode.TotalState={NOT_PROVIDED:0,LOWER_BOUND:1,EXACT:2};var NOT_PROVIDED=MultiDimensionalViewNode.TotalState.NOT_PROVIDED;var LOWER_BOUND=MultiDimensionalViewNode.TotalState.LOWER_BOUND;var EXACT=MultiDimensionalViewNode.TotalState.EXACT;MultiDimensionalViewNode.prototype={get subRows(){return tr.b.mapValues(this.children[0]);}};function MultiDimensionalViewBuilder(dimensions,valueCount){if(typeof dimensions!=='number'||dimensions<0)throw new Error('Dimensions must be a non-negative number');this.dimensions_=dimensions;if(typeof valueCount!=='number'||valueCount<0)throw new Error('Number of values must be a non-negative number');this.valueCount_=valueCount;this.buildRoot_=this.createRootNode_();this.topDownTreeViewRoot_=undefined;this.topDownHeavyViewRoot_=undefined;this.bottomUpHeavyViewNode_=undefined;this.maxDimensionDepths_=new Array(dimensions);for(var d=0;d<dimensions;d++)this.maxDimensionDepths_[d]=0;}MultiDimensionalViewBuilder.ValueKind={SELF:0,TOTAL:1};MultiDimensionalViewBuilder.ViewType={TOP_DOWN_TREE_VIEW:0,TOP_DOWN_HEAVY_VIEW:1,BOTTOM_UP_HEAVY_VIEW:2};MultiDimensionalViewBuilder.prototype={addPath:function(path,values,valueKind){if(this.buildRoot_===undefined){throw new Error('Paths cannot be added after either view has been built');}if(path.length!==this.dimensions_)throw new Error('Path must be '+this.dimensions_+'-dimensional');if(values.length!==this.valueCount_)throw new Error('Must provide '+this.valueCount_+' values');var isTotal;switch(valueKind){case MultiDimensionalViewBuilder.ValueKind.SELF:isTotal=false;break;case MultiDimensionalViewBuilder.ValueKind.TOTAL:isTotal=true;break;default:throw new Error('Invalid value kind: '+valueKind);}var node=this.buildRoot_;for(var d=0;d<path.length;d++){var singleDimensionPath=path[d];var singleDimensionPathLength=singleDimensionPath.length;this.maxDimensionDepths_[d]=Math.max(this.maxDimensionDepths_[d],singleDimensionPathLength);for(var i=0;i<singleDimensionPathLength;i++)node=this.getOrCreateChildNode_(node,d,singleDimensionPath[i]);}for(var v=0;v<this.valueCount_;v++){var addedValue=values[v];if(addedValue===undefined)continue;var nodeValue=node.values[v];if(isTotal){nodeValue.total+=addedValue;nodeValue.totalState=EXACT;}else{nodeValue.self+=addedValue;nodeValue.totalState=Math.max(nodeValue.totalState,LOWER_BOUND);}}},buildView:function(viewType){switch(viewType){case MultiDimensionalViewBuilder.ViewType.TOP_DOWN_TREE_VIEW:return this.buildTopDownTreeView();case MultiDimensionalViewBuilder.ViewType.TOP_DOWN_HEAVY_VIEW:return this.buildTopDownHeavyView();case MultiDimensionalViewBuilder.ViewType.BOTTOM_UP_HEAVY_VIEW:return this.buildBottomUpHeavyView();default:throw new Error('Unknown multi-dimensional view type: '+viewType);}},buildTopDownTreeView:function(){if(this.topDownTreeViewRoot_===undefined){var treeViewRoot=this.buildRoot_;this.buildRoot_=undefined;this.setUpMissingChildRelationships_(treeViewRoot,0);this.finalizeTotalValues_(treeViewRoot,0,new WeakMap());this.topDownTreeViewRoot_=treeViewRoot;}return this.topDownTreeViewRoot_;},buildTopDownHeavyView:function(){if(this.topDownHeavyViewRoot_===undefined){this.topDownHeavyViewRoot_=this.buildGenericHeavyView_(this.addDimensionToTopDownHeavyViewNode_.bind(this));}return this.topDownHeavyViewRoot_;},buildBottomUpHeavyView:function(){if(this.bottomUpHeavyViewNode_===undefined){this.bottomUpHeavyViewNode_=this.buildGenericHeavyView_(this.addDimensionToBottomUpHeavyViewNode_.bind(this));}return this.bottomUpHeavyViewNode_;},createRootNode_:function(){return new MultiDimensionalViewNode(new Array(this.dimensions_),this.valueCount_);},getOrCreateChildNode_:function(parentNode,dimension,childDimensionTitle){if(dimension<0||dimension>=this.dimensions_)throw new Error('Invalid dimension');var dimensionChildren=parentNode.children[dimension];var childNode=dimensionChildren.get(childDimensionTitle);if(childNode!==undefined)return childNode;var childTitle=parentNode.title.slice();childTitle[dimension]=childDimensionTitle;childNode=new MultiDimensionalViewNode(childTitle,this.valueCount_);dimensionChildren.set(childDimensionTitle,childNode);return childNode;},setUpMissingChildRelationships_:function(node,firstDimensionToSetUp){for(var d=firstDimensionToSetUp;d<this.dimensions_;d++){var currentDimensionChildTitles=new Set(node.children[d].keys());for(var i=0;i<d;i++){for(var previousDimensionChildNode of node.children[i].values()){for(var previousDimensionGrandChildTitle of previousDimensionChildNode.children[d].keys()){currentDimensionChildTitles.add(previousDimensionGrandChildTitle);}}}for(var currentDimensionChildTitle of currentDimensionChildTitles){var currentDimensionChildNode=this.getOrCreateChildNode_(node,d,currentDimensionChildTitle);for(var i=0;i<d;i++){for(var previousDimensionChildNode of node.children[i].values()){var previousDimensionGrandChildNode=previousDimensionChildNode.children[d].get(currentDimensionChildTitle);if(previousDimensionGrandChildNode!==undefined){currentDimensionChildNode.children[i].set(previousDimensionChildNode.title[i],previousDimensionGrandChildNode);}}}this.setUpMissingChildRelationships_(currentDimensionChildNode,d);}}},finalizeTotalValues_:function(node,firstDimensionToFinalize,dimensionalSelfSumsMap){var dimensionalSelfSums=new Array(this.dimensions_);var minResidual=new Array(this.valueCount_);for(var v=0;v<this.valueCount_;v++)minResidual[v]=0;var nodeValues=node.values;var nodeSelfSums=new Array(this.valueCount_);for(var v=0;v<this.valueCount_;v++)nodeSelfSums[v]=nodeValues[v].self;for(var d=0;d<this.dimensions_;d++){var childResidualSums=new Array(this.valueCount_);for(var v=0;v<this.valueCount_;v++)childResidualSums[v]=0;for(var childNode of node.children[d].values()){if(d>=firstDimensionToFinalize)this.finalizeTotalValues_(childNode,d,dimensionalSelfSumsMap);var childNodeSelfSums=dimensionalSelfSumsMap.get(childNode);var childNodeValues=childNode.values;for(var v=0;v<this.valueCount_;v++){nodeSelfSums[v]+=childNodeSelfSums[d][v];var residual=childNodeValues[v].total-childNodeSelfSums[this.dimensions_-1][v];childResidualSums[v]+=residual;if(childNodeValues[v].totalState>NOT_PROVIDED){nodeValues[v].totalState=Math.max(nodeValues[v].totalState,LOWER_BOUND);}}}dimensionalSelfSums[d]=nodeSelfSums.slice();for(var v=0;v<this.valueCount_;v++)minResidual[v]=Math.max(minResidual[v],childResidualSums[v]);}for(var v=0;v<this.valueCount_;v++){nodeValues[v].total=Math.max(nodeValues[v].total,nodeSelfSums[v]+minResidual[v]);}if(dimensionalSelfSumsMap.has(node))throw new Error('Internal error: Node finalized more than once');dimensionalSelfSumsMap.set(node,dimensionalSelfSums);},buildGenericHeavyView_:function(treeViewNodeHandler){var treeViewRoot=this.buildTopDownTreeView();var heavyViewRoot=this.createRootNode_();heavyViewRoot.values=treeViewRoot.values;var recursionDepthTrackers=new Array(this.dimensions_);for(var d=0;d<this.dimensions_;d++){recursionDepthTrackers[d]=new RecursionDepthTracker(this.maxDimensionDepths_[d],d);}this.addDimensionsToGenericHeavyViewNode_(treeViewRoot,heavyViewRoot,0,recursionDepthTrackers,false,treeViewNodeHandler);this.setUpMissingChildRelationships_(heavyViewRoot,0);return heavyViewRoot;},addDimensionsToGenericHeavyViewNode_:function(treeViewParentNode,heavyViewParentNode,startDimension,recursionDepthTrackers,previousDimensionsRecursive,treeViewNodeHandler){for(var d=startDimension;d<this.dimensions_;d++){this.addDimensionDescendantsToGenericHeavyViewNode_(treeViewParentNode,heavyViewParentNode,d,recursionDepthTrackers,previousDimensionsRecursive,treeViewNodeHandler);}},addDimensionDescendantsToGenericHeavyViewNode_:function(treeViewParentNode,heavyViewParentNode,currentDimension,recursionDepthTrackers,previousDimensionsRecursive,treeViewNodeHandler){var treeViewChildren=treeViewParentNode.children[currentDimension];var recursionDepthTracker=recursionDepthTrackers[currentDimension];for(var treeViewChildNode of treeViewChildren.values()){recursionDepthTracker.push(treeViewChildNode);treeViewNodeHandler(treeViewChildNode,heavyViewParentNode,currentDimension,recursionDepthTrackers,previousDimensionsRecursive);this.addDimensionDescendantsToGenericHeavyViewNode_(treeViewChildNode,heavyViewParentNode,currentDimension,recursionDepthTrackers,previousDimensionsRecursive,treeViewNodeHandler);recursionDepthTracker.pop();}},addDimensionToTopDownHeavyViewNode_:function(treeViewChildNode,heavyViewParentNode,currentDimension,recursionDepthTrackers,previousDimensionsRecursive){this.addDimensionToTopDownHeavyViewNodeRecursively_(treeViewChildNode,heavyViewParentNode,currentDimension,recursionDepthTrackers,previousDimensionsRecursive,1);},addDimensionToTopDownHeavyViewNodeRecursively_:function(treeViewChildNode,heavyViewParentNode,currentDimension,recursionDepthTrackers,previousDimensionsRecursive,subTreeDepth){var recursionDepthTracker=recursionDepthTrackers[currentDimension];var currentDimensionRecursive=subTreeDepth<=recursionDepthTracker.recursionDepth;var currentOrPreviousDimensionsRecursive=currentDimensionRecursive||previousDimensionsRecursive;var dimensionTitle=treeViewChildNode.title[currentDimension];var heavyViewChildNode=this.getOrCreateChildNode_(heavyViewParentNode,currentDimension,dimensionTitle);this.addNodeValues_(treeViewChildNode,heavyViewChildNode,!currentOrPreviousDimensionsRecursive);this.addDimensionsToGenericHeavyViewNode_(treeViewChildNode,heavyViewChildNode,currentDimension+1,recursionDepthTrackers,currentOrPreviousDimensionsRecursive,this.addDimensionToTopDownHeavyViewNode_.bind(this));for(var treeViewGrandChildNode of treeViewChildNode.children[currentDimension].values()){recursionDepthTracker.push(treeViewGrandChildNode);this.addDimensionToTopDownHeavyViewNodeRecursively_(treeViewGrandChildNode,heavyViewChildNode,currentDimension,recursionDepthTrackers,previousDimensionsRecursive,subTreeDepth+1);recursionDepthTracker.pop();}},addDimensionToBottomUpHeavyViewNode_:function(treeViewChildNode,heavyViewParentNode,currentDimension,recursionDepthTrackers,previousDimensionsRecursive){var recursionDepthTracker=recursionDepthTrackers[currentDimension];var bottomIndex=recursionDepthTracker.bottomIndex;var topIndex=recursionDepthTracker.topIndex;var firstNonRecursiveIndex=bottomIndex+recursionDepthTracker.recursionDepth;var viewNodePath=recursionDepthTracker.viewNodePath;var trackerAncestorNode=recursionDepthTracker.trackerAncestorNode;var heavyViewDescendantNode=heavyViewParentNode;for(var i=bottomIndex;i<topIndex;i++){var treeViewAncestorNode=viewNodePath[i];var dimensionTitle=treeViewAncestorNode.title[currentDimension];heavyViewDescendantNode=this.getOrCreateChildNode_(heavyViewDescendantNode,currentDimension,dimensionTitle);var currentDimensionRecursive=i<firstNonRecursiveIndex;var currentOrPreviousDimensionsRecursive=currentDimensionRecursive||previousDimensionsRecursive;this.addNodeValues_(treeViewChildNode,heavyViewDescendantNode,!currentOrPreviousDimensionsRecursive);this.addDimensionsToGenericHeavyViewNode_(treeViewChildNode,heavyViewDescendantNode,currentDimension+1,recursionDepthTrackers,currentOrPreviousDimensionsRecursive,this.addDimensionToBottomUpHeavyViewNode_.bind(this));}},addNodeValues_:function(sourceNode,targetNode,addTotal){var targetNodeValues=targetNode.values;var sourceNodeValues=sourceNode.values;for(var v=0;v<this.valueCount_;v++){var targetNodeValue=targetNodeValues[v];var sourceNodeValue=sourceNodeValues[v];targetNodeValue.self+=sourceNodeValue.self;if(addTotal){targetNodeValue.total+=sourceNodeValue.total;if(sourceNodeValue.totalState>NOT_PROVIDED){targetNodeValue.totalState=Math.max(targetNodeValue.totalState,LOWER_BOUND);}}}}};function RecursionDepthTracker(maxDepth,dimension){this.titlePath=new Array(maxDepth);this.viewNodePath=new Array(maxDepth);this.bottomIndex=this.topIndex=maxDepth;this.dimension_=dimension;this.currentTrackerNode_=this.createNode_(0,undefined);}RecursionDepthTracker.prototype={push:function(viewNode){if(this.bottomIndex===0)throw new Error('Cannot push to a full tracker');var title=viewNode.title[this.dimension_];this.bottomIndex--;this.titlePath[this.bottomIndex]=title;this.viewNodePath[this.bottomIndex]=viewNode;var childTrackerNode=this.currentTrackerNode_.children.get(title);if(childTrackerNode!==undefined){this.currentTrackerNode_=childTrackerNode;return;}var maxLengths=zFunction(this.titlePath,this.bottomIndex);var recursionDepth=0;for(var i=0;i<maxLengths.length;i++)recursionDepth=Math.max(recursionDepth,maxLengths[i]);childTrackerNode=this.createNode_(recursionDepth,this.currentTrackerNode_);this.currentTrackerNode_.children.set(title,childTrackerNode);this.currentTrackerNode_=childTrackerNode;},pop:function(){if(this.bottomIndex===this.topIndex)throw new Error('Cannot pop from an empty tracker');this.titlePath[this.bottomIndex]=undefined;this.viewNodePath[this.bottomIndex]=undefined;this.bottomIndex++;this.currentTrackerNode_=this.currentTrackerNode_.parent;},get recursionDepth(){return this.currentTrackerNode_.recursionDepth;},createNode_:function(recursionDepth,parent){return{recursionDepth:recursionDepth,parent:parent,children:new Map()};}};function zFunction(list,startIndex){var n=list.length-startIndex;if(n===0)return[];var z=new Array(n);z[0]=0;for(var i=1,left=0,right=0;i<n;++i){var maxLength;if(i<=right)maxLength=Math.min(right-i+1,z[i-left]);else maxLength=0;while(i+maxLength<n&&list[startIndex+maxLength]===list[startIndex+i+maxLength]){++maxLength;}if(i+maxLength-1>right){left=i;right=i+maxLength-1;}z[i]=maxLength;}return z;}return{MultiDimensionalViewBuilder:MultiDimensionalViewBuilder,MultiDimensionalViewNode:MultiDimensionalViewNode,RecursionDepthTracker:RecursionDepthTracker,zFunction:zFunction};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./base.js":33}],49:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./base.js":34}],50:[function(require,module,exports){
+(function (global){
 "use strict";require("./base.js");'use strict';global.tr.exportTo('tr.b',function(){var PERCENTILE_PRECISION=1e-7;function PiecewiseLinearFunction(){this.pieces=[];}PiecewiseLinearFunction.prototype={push:function(x1,y1,x2,y2){if(x1>=x2)throw new Error('Invalid segment');if(this.pieces.length>0&&this.pieces[this.pieces.length-1].x2>x1){throw new Error('Potentially overlapping segments');}if(x1<x2)this.pieces.push(new Piece(x1,y1,x2,y2));},partBelow:function(y){return this.pieces.reduce((acc,p)=>acc+p.partBelow(y),0);},get min(){return this.pieces.reduce((acc,p)=>Math.min(acc,p.min),Infinity);},get max(){return this.pieces.reduce((acc,p)=>Math.max(acc,p.max),-Infinity);},get average(){var weightedSum=0;var totalWeight=0;this.pieces.forEach(function(piece){weightedSum+=piece.width*piece.average;totalWeight+=piece.width;});if(totalWeight===0)return 0;return weightedSum/totalWeight;},percentile:function(percent){if(!(percent>=0&&percent<=1))throw new Error('percent must be [0,1]');var lower=this.min;var upper=this.max;var total=this.partBelow(upper);if(total===0)return 0;while(upper-lower>PERCENTILE_PRECISION){var middle=(lower+upper)/2;var below=this.partBelow(middle);if(below/total<percent)lower=middle;else upper=middle;}return(lower+upper)/2;}};function Piece(x1,y1,x2,y2){this.x1=x1;this.y1=y1;this.x2=x2;this.y2=y2;}Piece.prototype={partBelow:function(y){var width=this.width;if(width===0)return 0;var minY=this.min;var maxY=this.max;if(y>=maxY)return width;if(y<minY)return 0;return(y-minY)/(maxY-minY)*width;},get min(){return Math.min(this.y1,this.y2);},get max(){return Math.max(this.y1,this.y2);},get average(){return(this.y1+this.y2)/2;},get width(){return this.x2-this.x1;}};return{PiecewiseLinearFunction:PiecewiseLinearFunction};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./base.js":33}],50:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./base.js":34}],51:[function(require,module,exports){
+(function (global){
 "use strict";require("./base.js");require("./math.js");'use strict';global.tr.exportTo('tr.b',function(){var tmpVec2s=[];for(var i=0;i<8;i++)tmpVec2s[i]=vec2.create();var tmpVec2a=vec4.create();var tmpVec4a=vec4.create();var tmpVec4b=vec4.create();var tmpMat4=mat4.create();var tmpMat4b=mat4.create();var p00=vec2.createXY(0,0);var p10=vec2.createXY(1,0);var p01=vec2.createXY(0,1);var p11=vec2.createXY(1,1);var lerpingVecA=vec2.create();var lerpingVecB=vec2.create();function lerpVec2(out,a,b,amt){vec2.scale(lerpingVecA,a,amt);vec2.scale(lerpingVecB,b,1-amt);vec2.add(out,lerpingVecA,lerpingVecB);vec2.normalize(out,out);return out;}function Quad(){this.p1=vec2.create();this.p2=vec2.create();this.p3=vec2.create();this.p4=vec2.create();}Quad.fromXYWH=function(x,y,w,h){var q=new Quad();vec2.set(q.p1,x,y);vec2.set(q.p2,x+w,y);vec2.set(q.p3,x+w,y+h);vec2.set(q.p4,x,y+h);return q;};Quad.fromRect=function(r){return new Quad.fromXYWH(r.x,r.y,r.width,r.height);};Quad.from4Vecs=function(p1,p2,p3,p4){var q=new Quad();vec2.set(q.p1,p1[0],p1[1]);vec2.set(q.p2,p2[0],p2[1]);vec2.set(q.p3,p3[0],p3[1]);vec2.set(q.p4,p4[0],p4[1]);return q;};Quad.from8Array=function(arr){if(arr.length!=8)throw new Error('Array must be 8 long');var q=new Quad();q.p1[0]=arr[0];q.p1[1]=arr[1];q.p2[0]=arr[2];q.p2[1]=arr[3];q.p3[0]=arr[4];q.p3[1]=arr[5];q.p4[0]=arr[6];q.p4[1]=arr[7];return q;};Quad.prototype={pointInside:function(point){return pointInImplicitQuad(point,this.p1,this.p2,this.p3,this.p4);},boundingRect:function(){var x0=Math.min(this.p1[0],this.p2[0],this.p3[0],this.p4[0]);var y0=Math.min(this.p1[1],this.p2[1],this.p3[1],this.p4[1]);var x1=Math.max(this.p1[0],this.p2[0],this.p3[0],this.p4[0]);var y1=Math.max(this.p1[1],this.p2[1],this.p3[1],this.p4[1]);return new tr.b.Rect.fromXYWH(x0,y0,x1-x0,y1-y0);},clone:function(){var q=new Quad();vec2.copy(q.p1,this.p1);vec2.copy(q.p2,this.p2);vec2.copy(q.p3,this.p3);vec2.copy(q.p4,this.p4);return q;},scale:function(s){var q=new Quad();this.scaleFast(q,s);return q;},scaleFast:function(dstQuad,s){vec2.copy(dstQuad.p1,this.p1,s);vec2.copy(dstQuad.p2,this.p2,s);vec2.copy(dstQuad.p3,this.p3,s);vec2.copy(dstQuad.p3,this.p3,s);},isRectangle:function(){var bounds=this.boundingRect();return bounds.x==this.p1[0]&&bounds.y==this.p1[1]&&bounds.width==this.p2[0]-this.p1[0]&&bounds.y==this.p2[1]&&bounds.width==this.p3[0]-this.p1[0]&&bounds.height==this.p3[1]-this.p2[1]&&bounds.x==this.p4[0]&&bounds.height==this.p4[1]-this.p2[1];},projectUnitRect:function(rect){var q=new Quad();this.projectUnitRectFast(q,rect);return q;},projectUnitRectFast:function(dstQuad,rect){var v12=tmpVec2s[0];var v14=tmpVec2s[1];var v23=tmpVec2s[2];var v43=tmpVec2s[3];var l12,l14,l23,l43;vec2.sub(v12,this.p2,this.p1);l12=vec2.length(v12);vec2.scale(v12,v12,1/l12);vec2.sub(v14,this.p4,this.p1);l14=vec2.length(v14);vec2.scale(v14,v14,1/l14);vec2.sub(v23,this.p3,this.p2);l23=vec2.length(v23);vec2.scale(v23,v23,1/l23);vec2.sub(v43,this.p3,this.p4);l43=vec2.length(v43);vec2.scale(v43,v43,1/l43);var b12=tmpVec2s[0];var b14=tmpVec2s[1];var b23=tmpVec2s[2];var b43=tmpVec2s[3];lerpVec2(b12,v12,v43,rect.y);lerpVec2(b43,v12,v43,1-rect.bottom);lerpVec2(b14,v14,v23,rect.x);lerpVec2(b23,v14,v23,1-rect.right);vec2.addTwoScaledUnitVectors(tmpVec2a,b12,l12*rect.x,b14,l14*rect.y);vec2.add(dstQuad.p1,this.p1,tmpVec2a);vec2.addTwoScaledUnitVectors(tmpVec2a,b12,l12*-(1.0-rect.right),b23,l23*rect.y);vec2.add(dstQuad.p2,this.p2,tmpVec2a);vec2.addTwoScaledUnitVectors(tmpVec2a,b43,l43*-(1.0-rect.right),b23,l23*-(1.0-rect.bottom));vec2.add(dstQuad.p3,this.p3,tmpVec2a);vec2.addTwoScaledUnitVectors(tmpVec2a,b43,l43*rect.left,b14,l14*-(1.0-rect.bottom));vec2.add(dstQuad.p4,this.p4,tmpVec2a);},toString:function(){return'Quad('+vec2.toString(this.p1)+', '+vec2.toString(this.p2)+', '+vec2.toString(this.p3)+', '+vec2.toString(this.p4)+')';}};function sign(p1,p2,p3){return(p1[0]-p3[0])*(p2[1]-p3[1])-(p2[0]-p3[0])*(p1[1]-p3[1]);}function pointInTriangle2(pt,p1,p2,p3){var b1=sign(pt,p1,p2)<0.0;var b2=sign(pt,p2,p3)<0.0;var b3=sign(pt,p3,p1)<0.0;return b1==b2&&b2==b3;}function pointInImplicitQuad(point,p1,p2,p3,p4){return pointInTriangle2(point,p1,p2,p3)||pointInTriangle2(point,p1,p3,p4);}return{pointInTriangle2:pointInTriangle2,pointInImplicitQuad:pointInImplicitQuad,Quad:Quad};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./base.js":33,"./math.js":47}],51:[function(require,module,exports){
-(function(global){
-"use strict";require("./utils.js");'use strict';global.tr.exportTo('tr.b',function(){var ESTIMATED_IDLE_PERIOD_LENGTH_MILLISECONDS=10;var REQUEST_IDLE_CALLBACK_TIMEOUT_MILLISECONDS=100;var recordRAFStacks=false;var pendingPreAFs=[];var pendingRAFs=[];var pendingIdleCallbacks=[];var currentRAFDispatchList=undefined;var rafScheduled=false;var idleWorkScheduled=false;function scheduleRAF(){if(rafScheduled)return;rafScheduled=true;if(tr.isHeadless){Promise.resolve().then(function(){processRequests(false,0);},function(e){console.log(e.stack);throw e;});}else{if(window.requestAnimationFrame){window.requestAnimationFrame(processRequests.bind(this,false));}else{var delta=Date.now()-window.performance.now();window.webkitRequestAnimationFrame(function(domTimeStamp){processRequests(false,domTimeStamp-delta);});}}}function nativeRequestIdleCallbackSupported(){return!tr.isHeadless&&window.requestIdleCallback;}function scheduleIdleWork(){if(idleWorkScheduled)return;if(!nativeRequestIdleCallbackSupported()){scheduleRAF();return;}idleWorkScheduled=true;window.requestIdleCallback(function(deadline,didTimeout){processIdleWork(false,deadline);},{timeout:REQUEST_IDLE_CALLBACK_TIMEOUT_MILLISECONDS});}function onAnimationFrameError(e,opt_stack){console.log(e.stack);if(tr.isHeadless)throw e;if(opt_stack)console.log(opt_stack);if(e.message)console.error(e.message,e.stack);else console.error(e);}function runTask(task,frameBeginTime){try{task.callback.call(task.context,frameBeginTime);}catch(e){tr.b.onAnimationFrameError(e,task.stack);}}function processRequests(forceAllTasksToRun,frameBeginTime){rafScheduled=false;var currentPreAFs=pendingPreAFs;currentRAFDispatchList=pendingRAFs;pendingPreAFs=[];pendingRAFs=[];var hasRAFTasks=currentPreAFs.length||currentRAFDispatchList.length;for(var i=0;i<currentPreAFs.length;i++)runTask(currentPreAFs[i],frameBeginTime);while(currentRAFDispatchList.length>0)runTask(currentRAFDispatchList.shift(),frameBeginTime);currentRAFDispatchList=undefined;if(!hasRAFTasks&&!nativeRequestIdleCallbackSupported()||forceAllTasksToRun){var rafCompletionDeadline=frameBeginTime+ESTIMATED_IDLE_PERIOD_LENGTH_MILLISECONDS;processIdleWork(forceAllTasksToRun,{timeRemaining:function(){return rafCompletionDeadline-window.performance.now();}});}if(pendingIdleCallbacks.length>0)scheduleIdleWork();}function processIdleWork(forceAllTasksToRun,deadline){idleWorkScheduled=false;while(pendingIdleCallbacks.length>0){runTask(pendingIdleCallbacks.shift());if(!forceAllTasksToRun&&(tr.isHeadless||deadline.timeRemaining()<=0)){break;}}if(pendingIdleCallbacks.length>0)scheduleIdleWork();}function getStack_(){if(!recordRAFStacks)return'';var stackLines=tr.b.stackTrace();stackLines.shift();return stackLines.join('\n');}function requestPreAnimationFrame(callback,opt_this){pendingPreAFs.push({callback:callback,context:opt_this||global,stack:getStack_()});scheduleRAF();}function requestAnimationFrameInThisFrameIfPossible(callback,opt_this){if(!currentRAFDispatchList){requestAnimationFrame(callback,opt_this);return;}currentRAFDispatchList.push({callback:callback,context:opt_this||global,stack:getStack_()});return;}function requestAnimationFrame(callback,opt_this){pendingRAFs.push({callback:callback,context:opt_this||global,stack:getStack_()});scheduleRAF();}function requestIdleCallback(callback,opt_this){pendingIdleCallbacks.push({callback:callback,context:opt_this||global,stack:getStack_()});scheduleIdleWork();}function forcePendingRAFTasksToRun(frameBeginTime){if(!rafScheduled)return;processRequests(false,frameBeginTime);}function forceAllPendingTasksToRunForTest(){if(!rafScheduled&&!idleWorkScheduled)return;processRequests(true,0);}return{onAnimationFrameError:onAnimationFrameError,requestPreAnimationFrame:requestPreAnimationFrame,requestAnimationFrame:requestAnimationFrame,requestAnimationFrameInThisFrameIfPossible:requestAnimationFrameInThisFrameIfPossible,requestIdleCallback:requestIdleCallback,forcePendingRAFTasksToRun:forcePendingRAFTasksToRun,forceAllPendingTasksToRunForTest:forceAllPendingTasksToRunForTest};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./utils.js":64}],52:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./base.js":34,"./math.js":48}],52:[function(require,module,exports){
+(function (global){
+"use strict";require("./utils.js");'use strict';global.tr.exportTo('tr.b',function(){var ESTIMATED_IDLE_PERIOD_LENGTH_MILLISECONDS=10;var REQUEST_IDLE_CALLBACK_TIMEOUT_MILLISECONDS=100;var recordRAFStacks=false;var pendingPreAFs=[];var pendingRAFs=[];var pendingIdleCallbacks=[];var currentRAFDispatchList=undefined;var rafScheduled=false;var idleWorkScheduled=false;function scheduleRAF(){if(rafScheduled)return;rafScheduled=true;if(tr.isHeadless){Promise.resolve().then(function(){processRequests(false,0);},function(e){console.log(e.stack);throw e;});}else{if(window.requestAnimationFrame){window.requestAnimationFrame(processRequests.bind(this,false));}else{var delta=Date.now()-window.performance.now();window.webkitRequestAnimationFrame(function(domTimeStamp){processRequests(false,domTimeStamp-delta);});}}}function nativeRequestIdleCallbackSupported(){return!tr.isHeadless&&window.requestIdleCallback;}function scheduleIdleWork(){if(idleWorkScheduled)return;if(!nativeRequestIdleCallbackSupported()){scheduleRAF();return;}idleWorkScheduled=true;window.requestIdleCallback(function(deadline,didTimeout){processIdleWork(false,deadline);},{timeout:REQUEST_IDLE_CALLBACK_TIMEOUT_MILLISECONDS});}function onAnimationFrameError(e,opt_stack){console.log(e.stack);if(tr.isHeadless)throw e;if(opt_stack)console.log(opt_stack);if(e.message)console.assert(true,e.message,e.stack);else console.assert(true,e);}function runTask(task,frameBeginTime){try{task.callback.call(task.context,frameBeginTime);}catch(e){tr.b.onAnimationFrameError(e,task.stack);}}function processRequests(forceAllTasksToRun,frameBeginTime){rafScheduled=false;var currentPreAFs=pendingPreAFs;currentRAFDispatchList=pendingRAFs;pendingPreAFs=[];pendingRAFs=[];var hasRAFTasks=currentPreAFs.length||currentRAFDispatchList.length;for(var i=0;i<currentPreAFs.length;i++)runTask(currentPreAFs[i],frameBeginTime);while(currentRAFDispatchList.length>0)runTask(currentRAFDispatchList.shift(),frameBeginTime);currentRAFDispatchList=undefined;if(!hasRAFTasks&&!nativeRequestIdleCallbackSupported()||forceAllTasksToRun){var rafCompletionDeadline=frameBeginTime+ESTIMATED_IDLE_PERIOD_LENGTH_MILLISECONDS;processIdleWork(forceAllTasksToRun,{timeRemaining:function(){return rafCompletionDeadline-window.performance.now();}});}if(pendingIdleCallbacks.length>0)scheduleIdleWork();}function processIdleWork(forceAllTasksToRun,deadline){idleWorkScheduled=false;while(pendingIdleCallbacks.length>0){runTask(pendingIdleCallbacks.shift());if(!forceAllTasksToRun&&(tr.isHeadless||deadline.timeRemaining()<=0)){break;}}if(pendingIdleCallbacks.length>0)scheduleIdleWork();}function getStack_(){if(!recordRAFStacks)return'';var stackLines=tr.b.stackTrace();stackLines.shift();return stackLines.join('\n');}function requestPreAnimationFrame(callback,opt_this){pendingPreAFs.push({callback:callback,context:opt_this||global,stack:getStack_()});scheduleRAF();}function requestAnimationFrameInThisFrameIfPossible(callback,opt_this){if(!currentRAFDispatchList){requestAnimationFrame(callback,opt_this);return;}currentRAFDispatchList.push({callback:callback,context:opt_this||global,stack:getStack_()});return;}function requestAnimationFrame(callback,opt_this){pendingRAFs.push({callback:callback,context:opt_this||global,stack:getStack_()});scheduleRAF();}function requestIdleCallback(callback,opt_this){pendingIdleCallbacks.push({callback:callback,context:opt_this||global,stack:getStack_()});scheduleIdleWork();}function forcePendingRAFTasksToRun(frameBeginTime){if(!rafScheduled)return;processRequests(false,frameBeginTime);}function forceAllPendingTasksToRunForTest(){if(!rafScheduled&&!idleWorkScheduled)return;processRequests(true,0);}return{onAnimationFrameError:onAnimationFrameError,requestPreAnimationFrame:requestPreAnimationFrame,requestAnimationFrame:requestAnimationFrame,requestAnimationFrameInThisFrameIfPossible:requestAnimationFrameInThisFrameIfPossible,requestIdleCallback:requestIdleCallback,forcePendingRAFTasksToRun:forcePendingRAFTasksToRun,forceAllPendingTasksToRunForTest:forceAllPendingTasksToRunForTest};});
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./utils.js":65}],53:[function(require,module,exports){
+(function (global){
 "use strict";require("./base.js");require("./iteration_helpers.js");require("./math.js");'use strict';global.tr.exportTo('tr.b',function(){function Range(){this.isEmpty_=true;this.min_=undefined;this.max_=undefined;}Range.prototype={__proto__:Object.prototype,reset:function(){this.isEmpty_=true;this.min_=undefined;this.max_=undefined;},get isEmpty(){return this.isEmpty_;},addRange:function(range){if(range.isEmpty)return;this.addValue(range.min);this.addValue(range.max);},addValue:function(value){if(this.isEmpty_){this.max_=value;this.min_=value;this.isEmpty_=false;return;}this.max_=Math.max(this.max_,value);this.min_=Math.min(this.min_,value);},set min(min){this.isEmpty_=false;this.min_=min;},get min(){if(this.isEmpty_)return undefined;return this.min_;},get max(){if(this.isEmpty_)return undefined;return this.max_;},set max(max){this.isEmpty_=false;this.max_=max;},get range(){if(this.isEmpty_)return undefined;return this.max_-this.min_;},get center(){return(this.min_+this.max_)*0.5;},get duration(){if(this.isEmpty_)return 0;return this.max_-this.min_;},normalize:function(x){return tr.b.normalize(x,this.min,this.max);},lerp:function(x){return tr.b.lerp(x,this.min,this.max);},equals:function(that){if(this.isEmpty&&that.isEmpty)return true;if(this.isEmpty!=that.isEmpty)return false;return tr.b.approximately(this.min,that.min)&&tr.b.approximately(this.max,that.max);},containsExplicitRangeInclusive:function(min,max){if(this.isEmpty)return false;return this.min_<=min&&max<=this.max_;},containsExplicitRangeExclusive:function(min,max){if(this.isEmpty)return false;return this.min_<min&&max<this.max_;},intersectsExplicitRangeInclusive:function(min,max){if(this.isEmpty)return false;return this.min_<=max&&min<=this.max_;},intersectsExplicitRangeExclusive:function(min,max){if(this.isEmpty)return false;return this.min_<max&&min<this.max_;},containsRangeInclusive:function(range){if(range.isEmpty)return false;return this.containsExplicitRangeInclusive(range.min_,range.max_);},containsRangeExclusive:function(range){if(range.isEmpty)return false;return this.containsExplicitRangeExclusive(range.min_,range.max_);},intersectsRangeInclusive:function(range){if(range.isEmpty)return false;return this.intersectsExplicitRangeInclusive(range.min_,range.max_);},intersectsRangeExclusive:function(range){if(range.isEmpty)return false;return this.intersectsExplicitRangeExclusive(range.min_,range.max_);},findExplicitIntersectionDuration:function(min,max){var min=Math.max(this.min,min);var max=Math.min(this.max,max);if(max<min)return 0;return max-min;},findIntersection:function(range){if(this.isEmpty||range.isEmpty)return new Range();var min=Math.max(this.min,range.min);var max=Math.min(this.max,range.max);if(max<min)return new Range();return Range.fromExplicitRange(min,max);},toJSON:function(){if(this.isEmpty_)return{isEmpty:true};return{isEmpty:false,max:this.max,min:this.min};},filterArray:function(array,opt_keyFunc,opt_this){if(this.isEmpty_)return[];function binSearch(test){var i0=0;var i1=array.length;while(i0<i1){var i=Math.trunc((i0+i1)/2);if(test(i))i1=i;else i0=i+1;}return i1;}var keyFunc=opt_keyFunc||tr.b.identity;function getValue(index){return keyFunc.call(opt_this,array[index]);}var first=binSearch(function(i){return this.min_===undefined||this.min_<=getValue(i);}.bind(this));var last=binSearch(function(i){return this.max_!==undefined&&this.max_<getValue(i);}.bind(this));return array.slice(first,last);}};Range.fromDict=function(d){if(d.isEmpty===true){return new Range();}else if(d.isEmpty===false){var range=new Range();range.min=d.min;range.max=d.max;return range;}else{throw new Error('Not a range');}};Range.fromExplicitRange=function(min,max){var range=new Range();range.min=min;range.max=max;return range;};Range.compareByMinTimes=function(a,b){if(!a.isEmpty&&!b.isEmpty)return a.min_-b.min_;if(a.isEmpty&&!b.isEmpty)return-1;if(!a.isEmpty&&b.isEmpty)return 1;return 0;};Range.PERCENT_RANGE=Range.fromExplicitRange(0,1);Object.freeze(Range.PERCENT_RANGE);return{Range:Range};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./base.js":33,"./iteration_helpers.js":46,"./math.js":47}],53:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./base.js":34,"./iteration_helpers.js":47,"./math.js":48}],54:[function(require,module,exports){
+(function (global){
 "use strict";require("./base.js");require("./iteration_helpers.js");'use strict';global.tr.exportTo('tr.b',function(){function convertEventsToRanges(events){return events.map(function(event){return tr.b.Range.fromExplicitRange(event.start,event.end);});}function mergeRanges(inRanges,mergeThreshold,mergeFunction){var remainingEvents=inRanges.slice();remainingEvents.sort(function(x,y){return x.min-y.min;});if(remainingEvents.length<=1){var merged=[];if(remainingEvents.length==1){merged.push(mergeFunction(remainingEvents));}return merged;}var mergedEvents=[];var currentMergeBuffer=[];var rightEdge;function beginMerging(){currentMergeBuffer.push(remainingEvents[0]);remainingEvents.splice(0,1);rightEdge=currentMergeBuffer[0].max;}function flushCurrentMergeBuffer(){if(currentMergeBuffer.length==0)return;mergedEvents.push(mergeFunction(currentMergeBuffer));currentMergeBuffer=[];if(remainingEvents.length!=0)beginMerging();}beginMerging();while(remainingEvents.length){var currentEvent=remainingEvents[0];var distanceFromRightEdge=currentEvent.min-rightEdge;if(distanceFromRightEdge<mergeThreshold){rightEdge=Math.max(rightEdge,currentEvent.max);remainingEvents.splice(0,1);currentMergeBuffer.push(currentEvent);continue;}flushCurrentMergeBuffer();}flushCurrentMergeBuffer();return mergedEvents;}function findEmptyRangesBetweenRanges(inRanges,opt_totalRange){if(opt_totalRange&&opt_totalRange.isEmpty)opt_totalRange=undefined;var emptyRanges=[];if(!inRanges.length){if(opt_totalRange)emptyRanges.push(opt_totalRange);return emptyRanges;}inRanges=inRanges.slice();inRanges.sort(function(x,y){return x.min-y.min;});if(opt_totalRange&&opt_totalRange.min<inRanges[0].min){emptyRanges.push(tr.b.Range.fromExplicitRange(opt_totalRange.min,inRanges[0].min));}inRanges.forEach(function(range,index){for(var otherIndex=0;otherIndex<inRanges.length;++otherIndex){if(index===otherIndex)continue;var other=inRanges[otherIndex];if(other.min>range.max){emptyRanges.push(tr.b.Range.fromExplicitRange(range.max,other.min));return;}if(other.max>range.max){return;}}if(opt_totalRange&&range.max<opt_totalRange.max){emptyRanges.push(tr.b.Range.fromExplicitRange(range.max,opt_totalRange.max));}});return emptyRanges;}return{convertEventsToRanges:convertEventsToRanges,findEmptyRangesBetweenRanges:findEmptyRangesBetweenRanges,mergeRanges:mergeRanges};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./base.js":33,"./iteration_helpers.js":46}],54:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./base.js":34,"./iteration_helpers.js":47}],55:[function(require,module,exports){
+(function (global){
 "use strict";require("./base.js");require("./math.js");'use strict';global.tr.exportTo('tr.b',function(){function Rect(){this.x=0;this.y=0;this.width=0;this.height=0;};Rect.fromXYWH=function(x,y,w,h){var rect=new Rect();rect.x=x;rect.y=y;rect.width=w;rect.height=h;return rect;};Rect.fromArray=function(ary){if(ary.length!=4)throw new Error('ary.length must be 4');var rect=new Rect();rect.x=ary[0];rect.y=ary[1];rect.width=ary[2];rect.height=ary[3];return rect;};Rect.prototype={__proto__:Object.prototype,get left(){return this.x;},get top(){return this.y;},get right(){return this.x+this.width;},get bottom(){return this.y+this.height;},toString:function(){return'Rect('+this.x+', '+this.y+', '+this.width+', '+this.height+')';},toArray:function(){return[this.x,this.y,this.width,this.height];},clone:function(){var rect=new Rect();rect.x=this.x;rect.y=this.y;rect.width=this.width;rect.height=this.height;return rect;},enlarge:function(pad){var rect=new Rect();this.enlargeFast(rect,pad);return rect;},enlargeFast:function(out,pad){out.x=this.x-pad;out.y=this.y-pad;out.width=this.width+2*pad;out.height=this.height+2*pad;return out;},size:function(){return{width:this.width,height:this.height};},scale:function(s){var rect=new Rect();this.scaleFast(rect,s);return rect;},scaleSize:function(s){return Rect.fromXYWH(this.x,this.y,this.width*s,this.height*s);},scaleFast:function(out,s){out.x=this.x*s;out.y=this.y*s;out.width=this.width*s;out.height=this.height*s;return out;},translate:function(v){var rect=new Rect();this.translateFast(rect,v);return rect;},translateFast:function(out,v){out.x=this.x+v[0];out.y=this.x+v[1];out.width=this.width;out.height=this.height;return out;},asUVRectInside:function(containingRect){var rect=new Rect();rect.x=(this.x-containingRect.x)/containingRect.width;rect.y=(this.y-containingRect.y)/containingRect.height;rect.width=this.width/containingRect.width;rect.height=this.height/containingRect.height;return rect;},intersects:function(that){var ok=true;ok&=this.x<that.right;ok&=this.right>that.x;ok&=this.y<that.bottom;ok&=this.bottom>that.y;return ok;},equalTo:function(rect){return rect&&this.x===rect.x&&this.y===rect.y&&this.width===rect.width&&this.height===rect.height;}};return{Rect:Rect};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./base.js":33,"./math.js":47}],55:[function(require,module,exports){
-(function(global){
-"use strict";var _slicedToArray=function(){function sliceIterator(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n=(_s=_i.next()).done);_n=true){_arr.push(_s.value);if(i&&_arr.length===i)break;}}catch(err){_d=true;_e=err;}finally{try{if(!_n&&_i["return"])_i["return"]();}finally{if(_d)throw _e;}}return _arr;}return function(arr,i){if(Array.isArray(arr)){return arr;}else if(Symbol.iterator in Object(arr)){return sliceIterator(arr,i);}else{throw new TypeError("Invalid attempt to destructure non-iterable instance");}};}();require("./base.js");'use strict';global.tr.exportTo('tr.b',function(){class RunningStatistics{constructor(){this.mean_=0;this.count_=0;this.max_=-Infinity;this.min_=Infinity;this.sum_=0;this.variance_=0;this.meanlogs_=0;}get count(){return this.count_;}get geometricMean(){if(this.meanlogs_===undefined)return 0;return Math.exp(this.meanlogs_);}get mean(){if(this.count_==0)return undefined;return this.mean_;}get max(){return this.max_;}get min(){return this.min_;}get sum(){return this.sum_;}get variance(){if(this.count_==0)return undefined;if(this.count_==1)return 0;return this.variance_/(this.count_-1);}get stddev(){if(this.count_==0)return undefined;return Math.sqrt(this.variance);}add(x){this.count_++;this.max_=Math.max(this.max_,x);this.min_=Math.min(this.min_,x);this.sum_+=x;if(x<=0)this.meanlogs_=undefined;else if(this.meanlogs_!==undefined)this.meanlogs_+=(Math.log(Math.abs(x))-this.meanlogs_)/this.count;if(this.count_===1){this.mean_=x;this.variance_=0;}else{var oldMean=this.mean_;var oldVariance=this.variance_;if(oldMean===Infinity||oldMean===-Infinity){this.mean_=this.sum_/this.count_;}else{this.mean_=oldMean+(x-oldMean)/this.count_;}this.variance_=oldVariance+(x-oldMean)*(x-this.mean_);}}merge(other){var result=new RunningStatistics();result.count_=this.count_+other.count_;result.sum_=this.sum_+other.sum_;result.min_=Math.min(this.min_,other.min_);result.max_=Math.max(this.max_,other.max_);if(result.count===0){result.mean_=0;result.variance_=0;result.meanlogs_=0;}else{result.mean_=result.sum/result.count;var deltaMean=(this.mean||0)-(other.mean||0);result.variance_=this.variance_+other.variance_+this.count*other.count*deltaMean*deltaMean/result.count;if(this.meanlogs_===undefined||other.meanlogs_===undefined){result.meanlogs_=undefined;}else{result.meanlogs_=(this.count*this.meanlogs_+other.count*other.meanlogs_)/result.count;}}return result;}asDict(){if(!this.count){return[];}return[this.count_,this.max_,this.meanlogs_,this.mean_,this.min_,this.sum_,this.variance_];}static fromDict(dict){var result=new RunningStatistics();if(dict.length!=7){return result;}var _dict=_slicedToArray(dict,7);result.count_=_dict[0];result.max_=_dict[1];result.meanlogs_=_dict[2];result.mean_=_dict[3];result.min_=_dict[4];result.sum_=_dict[5];result.variance_=_dict[6];return result;}}return{RunningStatistics:RunningStatistics};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./base.js":33}],56:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./base.js":34,"./math.js":48}],56:[function(require,module,exports){
+(function (global){
+"use strict";require("./base.js");'use strict';global.tr.exportTo('tr.b',function(){class RunningStatistics{constructor(){this.mean_=0;this.count_=0;this.max_=-Infinity;this.min_=Infinity;this.sum_=0;this.variance_=0;this.meanlogs_=0;}get count(){return this.count_;}get geometricMean(){if(this.meanlogs_===undefined)return 0;return Math.exp(this.meanlogs_);}get mean(){if(this.count_==0)return undefined;return this.mean_;}get max(){return this.max_;}get min(){return this.min_;}get sum(){return this.sum_;}get variance(){if(this.count_==0)return undefined;if(this.count_==1)return 0;return this.variance_/(this.count_-1);}get stddev(){if(this.count_==0)return undefined;return Math.sqrt(this.variance);}add(x){this.count_++;this.max_=Math.max(this.max_,x);this.min_=Math.min(this.min_,x);this.sum_+=x;if(x<=0)this.meanlogs_=undefined;else if(this.meanlogs_!==undefined)this.meanlogs_+=(Math.log(Math.abs(x))-this.meanlogs_)/this.count;if(this.count_===1){this.mean_=x;this.variance_=0;}else{var oldMean=this.mean_;var oldVariance=this.variance_;if(oldMean===Infinity||oldMean===-Infinity){this.mean_=this.sum_/this.count_;}else{this.mean_=oldMean+(x-oldMean)/this.count_;}this.variance_=oldVariance+(x-oldMean)*(x-this.mean_);}}merge(other){var result=new RunningStatistics();result.count_=this.count_+other.count_;result.sum_=this.sum_+other.sum_;result.min_=Math.min(this.min_,other.min_);result.max_=Math.max(this.max_,other.max_);if(result.count===0){result.mean_=0;result.variance_=0;result.meanlogs_=0;}else{result.mean_=result.sum/result.count;var deltaMean=(this.mean||0)-(other.mean||0);result.variance_=this.variance_+other.variance_+this.count*other.count*deltaMean*deltaMean/result.count;if(this.meanlogs_===undefined||other.meanlogs_===undefined){result.meanlogs_=undefined;}else{result.meanlogs_=(this.count*this.meanlogs_+other.count*other.meanlogs_)/result.count;}}return result;}asDict(){if(!this.count){return[];}return[this.count_,this.max_,this.meanlogs_,this.mean_,this.min_,this.sum_,this.variance_];}static fromDict(dict){var result=new RunningStatistics();if(dict.length!=7){return result;}[result.count_,result.max_,result.meanlogs_,result.mean_,result.min_,result.sum_,result.variance_]=dict;return result;}}return{RunningStatistics:RunningStatistics};});
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./base.js":34}],57:[function(require,module,exports){
+(function (global){
 "use strict";require("./color.js");require("./iteration_helpers.js");require("./math.js");'use strict';global.tr.exportTo('tr.b',function(){function SinebowColorGenerator(opt_a,opt_brightness){this.a_=opt_a===undefined?1:opt_a;this.brightness_=opt_brightness===undefined?1:opt_brightness;this.colorIndex_=0;this.keyToColor={};}SinebowColorGenerator.prototype={colorForKey:function(key){if(!this.keyToColor[key])this.keyToColor[key]=this.nextColor();return this.keyToColor[key];},nextColor:function(){var components=SinebowColorGenerator.nthColor(this.colorIndex_++);return tr.b.Color.fromString(SinebowColorGenerator.calculateColor(components[0],components[1],components[2],this.a_,this.brightness_));}};SinebowColorGenerator.PHI=(1+Math.sqrt(5))/2;SinebowColorGenerator.sinebow_=function(h){h+=0.5;h=-h;var r=Math.sin(Math.PI*h);var g=Math.sin(Math.PI*(h+1/3));var b=Math.sin(Math.PI*(h+2/3));r*=r;g*=g;b*=b;var y=2*(0.2989*r+0.5870*g+0.1140*b);r/=y;g/=y;b/=y;return[256*r,256*g,256*b];};SinebowColorGenerator.nthColor=function(n){return SinebowColorGenerator.sinebow_(n*this.PHI);};SinebowColorGenerator.calculateColor=function(r,g,b,a,brightness){if(brightness<=1){r*=brightness;g*=brightness;b*=brightness;}else{r=tr.b.lerp(tr.b.normalize(brightness,1,2),r,255);g=tr.b.lerp(tr.b.normalize(brightness,1,2),g,255);b=tr.b.lerp(tr.b.normalize(brightness,1,2),b,255);}r=Math.round(r);g=Math.round(g);b=Math.round(b);return'rgba('+r+','+g+','+b+', '+a+')';};return{SinebowColorGenerator:SinebowColorGenerator};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./color.js":36,"./iteration_helpers.js":46,"./math.js":47}],57:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./color.js":37,"./iteration_helpers.js":47,"./math.js":48}],58:[function(require,module,exports){
+(function (global){
 "use strict";require("./base.js");'use strict';global.tr.exportTo('tr.b',function(){function findLowIndexInSortedArray(ary,mapFn,loVal){if(ary.length==0)return 1;var low=0;var high=ary.length-1;var i,comparison;var hitPos=-1;while(low<=high){i=Math.floor((low+high)/2);comparison=mapFn(ary[i])-loVal;if(comparison<0){low=i+1;continue;}else if(comparison>0){high=i-1;continue;}else{hitPos=i;high=i-1;}}return hitPos!=-1?hitPos:low;}function findHighIndexInSortedArray(ary,mapFn,loVal,hiVal){var lo=loVal||0;var hi=hiVal!==undefined?hiVal:ary.length;while(lo<hi){var mid=lo+hi>>1;if(mapFn(ary[mid])>=0)lo=mid+1;else hi=mid;}return hi;}function findIndexInSortedIntervals(ary,mapLoFn,mapWidthFn,loVal){var first=findLowIndexInSortedArray(ary,mapLoFn,loVal);if(first==0){if(loVal>=mapLoFn(ary[0])&&loVal<mapLoFn(ary[0])+mapWidthFn(ary[0],0)){return 0;}else{return-1;}}else if(first<ary.length){if(loVal>=mapLoFn(ary[first])&&loVal<mapLoFn(ary[first])+mapWidthFn(ary[first],first)){return first;}else if(loVal>=mapLoFn(ary[first-1])&&loVal<mapLoFn(ary[first-1])+mapWidthFn(ary[first-1],first-1)){return first-1;}else{return ary.length;}}else if(first==ary.length){if(loVal>=mapLoFn(ary[first-1])&&loVal<mapLoFn(ary[first-1])+mapWidthFn(ary[first-1],first-1)){return first-1;}else{return ary.length;}}else{return ary.length;}}function findIndexInSortedClosedIntervals(ary,mapLoFn,mapHiFn,val){var i=findLowIndexInSortedArray(ary,mapLoFn,val);if(i===0){if(val>=mapLoFn(ary[0],0)&&val<=mapHiFn(ary[0],0)){return 0;}else{return-1;}}else if(i<ary.length){if(val>=mapLoFn(ary[i-1],i-1)&&val<=mapHiFn(ary[i-1],i-1)){return i-1;}else if(val>=mapLoFn(ary[i],i)&&val<=mapHiFn(ary[i],i)){return i;}else{return ary.length;}}else if(i==ary.length){if(val>=mapLoFn(ary[i-1],i-1)&&val<=mapHiFn(ary[i-1],i-1)){return i-1;}else{return ary.length;}}else{return ary.length;}}function iterateOverIntersectingIntervals(ary,mapLoFn,mapWidthFn,loVal,hiVal,cb){if(ary.length==0)return;if(loVal>hiVal)return;var i=findLowIndexInSortedArray(ary,mapLoFn,loVal);if(i==-1){return;}if(i>0){var hi=mapLoFn(ary[i-1])+mapWidthFn(ary[i-1],i-1);if(hi>=loVal){cb(ary[i-1],i-1);}}if(i==ary.length){return;}for(var n=ary.length;i<n;i++){var lo=mapLoFn(ary[i]);if(lo>=hiVal)break;cb(ary[i],i);}}function getIntersectingIntervals(ary,mapLoFn,mapWidthFn,loVal,hiVal){var tmp=[];iterateOverIntersectingIntervals(ary,mapLoFn,mapWidthFn,loVal,hiVal,function(d){tmp.push(d);});return tmp;}function findClosestElementInSortedArray(ary,mapFn,val,maxDiff){if(ary.length===0)return null;var aftIdx=findLowIndexInSortedArray(ary,mapFn,val);var befIdx=aftIdx>0?aftIdx-1:0;if(aftIdx===ary.length)aftIdx-=1;var befDiff=Math.abs(val-mapFn(ary[befIdx]));var aftDiff=Math.abs(val-mapFn(ary[aftIdx]));if(befDiff>maxDiff&&aftDiff>maxDiff)return null;var idx=befDiff<aftDiff?befIdx:aftIdx;return ary[idx];}function findClosestIntervalInSortedIntervals(ary,mapLoFn,mapHiFn,val,maxDiff){if(ary.length===0)return null;var idx=findLowIndexInSortedArray(ary,mapLoFn,val);if(idx>0)idx-=1;var hiInt=ary[idx];var loInt=hiInt;if(val>mapHiFn(hiInt)&&idx+1<ary.length)loInt=ary[idx+1];var loDiff=Math.abs(val-mapLoFn(loInt));var hiDiff=Math.abs(val-mapHiFn(hiInt));if(loDiff>maxDiff&&hiDiff>maxDiff)return null;if(loDiff<hiDiff)return loInt;else return hiInt;}return{findLowIndexInSortedArray:findLowIndexInSortedArray,findHighIndexInSortedArray:findHighIndexInSortedArray,findIndexInSortedIntervals:findIndexInSortedIntervals,findIndexInSortedClosedIntervals:findIndexInSortedClosedIntervals,iterateOverIntersectingIntervals:iterateOverIntersectingIntervals,getIntersectingIntervals:getIntersectingIntervals,findClosestElementInSortedArray:findClosestElementInSortedArray,findClosestIntervalInSortedIntervals:findClosestIntervalInSortedIntervals};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./base.js":33}],58:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./base.js":34}],59:[function(require,module,exports){
+(function (global){
 "use strict";require("./math.js");require("./range.js");'use strict';(function(){if(tr.isNode){var mwuAbsPath=HTMLImportsLoader.hrefToAbsolutePath('/mannwhitneyu.js');var mwuModule=require(mwuAbsPath);for(var exportName in mwuModule){global[exportName]=mwuModule[exportName];}}})(this);'use strict';global.tr.exportTo('tr.b',function(){var identity=x=>x;var Statistics={};Statistics.divideIfPossibleOrZero=function(numerator,denominator){if(denominator===0)return 0;return numerator/denominator;};Statistics.sum=function(ary,opt_func,opt_this){var func=opt_func||identity;var ret=0;var i=0;for(var elt of ary)ret+=func.call(opt_this,elt,i++);return ret;};Statistics.mean=function(ary,opt_func,opt_this){var func=opt_func||identity;var sum=0;var i=0;for(var elt of ary)sum+=func.call(opt_this,elt,i++);if(i===0)return undefined;return sum/i;};Statistics.geometricMean=function(ary,opt_func,opt_this){var func=opt_func||identity;var i=0;var logsum=0;for(var elt of ary){var x=func.call(opt_this,elt,i++);if(x<=0)return 0;logsum+=Math.log(Math.abs(x));}if(i===0)return 1;return Math.exp(logsum/i);};Statistics.weightedMean=function(ary,weightCallback,opt_valueCallback,opt_this){var valueCallback=opt_valueCallback||identity;var numerator=0;var denominator=0;var i=-1;for(var elt of ary){i++;var value=valueCallback.call(opt_this,elt,i);if(value===undefined)continue;var weight=weightCallback.call(opt_this,elt,i,value);numerator+=weight*value;denominator+=weight;}if(denominator===0)return undefined;return numerator/denominator;};Statistics.variance=function(ary,opt_func,opt_this){if(ary.length===0)return undefined;if(ary.length===1)return 0;var func=opt_func||identity;var mean=Statistics.mean(ary,func,opt_this);var sumOfSquaredDistances=Statistics.sum(ary,function(d,i){var v=func.call(this,d,i)-mean;return v*v;},opt_this);return sumOfSquaredDistances/(ary.length-1);};Statistics.stddev=function(ary,opt_func,opt_this){if(ary.length==0)return undefined;return Math.sqrt(Statistics.variance(ary,opt_func,opt_this));};Statistics.max=function(ary,opt_func,opt_this){var func=opt_func||identity;var ret=-Infinity;var i=0;for(var elt of ary)ret=Math.max(ret,func.call(opt_this,elt,i++));return ret;};Statistics.min=function(ary,opt_func,opt_this){var func=opt_func||identity;var ret=Infinity;var i=0;for(var elt of ary)ret=Math.min(ret,func.call(opt_this,elt,i++));return ret;};Statistics.range=function(ary,opt_func,opt_this){var func=opt_func||identity;var ret=new tr.b.Range();var i=0;for(var elt of ary)ret.addValue(func.call(opt_this,elt,i++));return ret;};Statistics.percentile=function(ary,percent,opt_func,opt_this){if(!(percent>=0&&percent<=1))throw new Error('percent must be [0,1]');var func=opt_func||identity;var tmp=new Array(ary.length);var i=0;for(var elt of ary)tmp[i]=func.call(opt_this,elt,i++);tmp.sort((a,b)=>a-b);var idx=Math.floor((ary.length-1)*percent);return tmp[idx];};Statistics.normalizeSamples=function(samples){if(samples.length===0){return{normalized_samples:samples,scale:1.0};}samples=samples.slice().sort(function(a,b){return a-b;});var low=Math.min.apply(null,samples);var high=Math.max.apply(null,samples);var newLow=0.5/samples.length;var newHigh=(samples.length-0.5)/samples.length;if(high-low===0.0){samples=Array.apply(null,new Array(samples.length)).map(function(){return 0.5;});return{normalized_samples:samples,scale:1.0};}var scale=(newHigh-newLow)/(high-low);for(var i=0;i<samples.length;i++){samples[i]=(samples[i]-low)*scale+newLow;}return{normalized_samples:samples,scale:scale};};Statistics.discrepancy=function(samples,opt_locationCount){if(samples.length===0)return 0.0;var maxLocalDiscrepancy=0;var invSampleCount=1.0/samples.length;var locations=[];var countLess=[];var countLessEqual=[];if(opt_locationCount!==undefined){var sampleIndex=0;for(var i=0;i<opt_locationCount;i++){var location=i/(opt_locationCount-1);locations.push(location);while(sampleIndex<samples.length&&samples[sampleIndex]<location){sampleIndex+=1;}countLess.push(sampleIndex);while(sampleIndex<samples.length&&samples[sampleIndex]<=location){sampleIndex+=1;}countLessEqual.push(sampleIndex);}}else{if(samples[0]>0.0){locations.push(0.0);countLess.push(0);countLessEqual.push(0);}for(var i=0;i<samples.length;i++){locations.push(samples[i]);countLess.push(i);countLessEqual.push(i+1);}if(samples[-1]<1.0){locations.push(1.0);countLess.push(samples.length);countLessEqual.push(samples.length);}}var maxDiff=0;var minDiff=0;for(var i=1;i<locations.length;i++){var length=locations[i]-locations[i-1];var countClosed=countLessEqual[i]-countLess[i-1];var countOpen=countLess[i]-countLessEqual[i-1];var countClosedIncrement=countLessEqual[i]-countLessEqual[i-1];var countOpenIncrement=countLess[i]-countLess[i-1];maxDiff=Math.max(countClosedIncrement*invSampleCount-length+maxDiff,countClosed*invSampleCount-length);minDiff=Math.min(countOpenIncrement*invSampleCount-length+minDiff,countOpen*invSampleCount-length);maxLocalDiscrepancy=Math.max(maxDiff,-minDiff,maxLocalDiscrepancy);}return maxLocalDiscrepancy;};Statistics.timestampsDiscrepancy=function(timestamps,opt_absolute,opt_locationCount){if(timestamps.length===0)return 0.0;if(opt_absolute===undefined)opt_absolute=true;if(Array.isArray(timestamps[0])){var rangeDiscrepancies=timestamps.map(function(r){return Statistics.timestampsDiscrepancy(r);});return Math.max.apply(null,rangeDiscrepancies);}var s=Statistics.normalizeSamples(timestamps);var samples=s.normalized_samples;var sampleScale=s.scale;var discrepancy=Statistics.discrepancy(samples,opt_locationCount);var invSampleCount=1.0/samples.length;if(opt_absolute===true){discrepancy/=sampleScale;}else{discrepancy=tr.b.clamp((discrepancy-invSampleCount)/(1.0-invSampleCount),0.0,1.0);}return discrepancy;};Statistics.durationsDiscrepancy=function(durations,opt_absolute,opt_locationCount){if(durations.length===0)return 0.0;var timestamps=durations.reduce(function(prev,curr,index,array){prev.push(prev[prev.length-1]+curr);return prev;},[0]);return Statistics.timestampsDiscrepancy(timestamps,opt_absolute,opt_locationCount);};Statistics.uniformlySampleArray=function(samples,count){if(samples.length<=count){return samples;}while(samples.length>count){var i=parseInt(Math.random()*samples.length);samples.splice(i,1);}return samples;};Statistics.uniformlySampleStream=function(samples,streamLength,newElement,numSamples){if(streamLength<=numSamples){if(samples.length>=streamLength)samples[streamLength-1]=newElement;else samples.push(newElement);return;}var probToKeep=numSamples/streamLength;if(Math.random()>probToKeep)return;var index=Math.floor(Math.random()*numSamples);samples[index]=newElement;};Statistics.mergeSampledStreams=function(samplesA,streamLengthA,samplesB,streamLengthB,numSamples){if(streamLengthB<numSamples){var nbElements=Math.min(streamLengthB,samplesB.length);for(var i=0;i<nbElements;++i){Statistics.uniformlySampleStream(samplesA,streamLengthA+i+1,samplesB[i],numSamples);}return;}if(streamLengthA<numSamples){var nbElements=Math.min(streamLengthA,samplesA.length);var tempSamples=samplesB.slice();for(var i=0;i<nbElements;++i){Statistics.uniformlySampleStream(tempSamples,streamLengthB+i+1,samplesA[i],numSamples);}for(var i=0;i<tempSamples.length;++i){samplesA[i]=tempSamples[i];}return;}var nbElements=Math.min(numSamples,samplesB.length);var probOfSwapping=streamLengthB/(streamLengthA+streamLengthB);for(var i=0;i<nbElements;++i){if(Math.random()<probOfSwapping){samplesA[i]=samplesB[i];}}};function Distribution(){}Distribution.prototype={computeDensity:function(x){throw Error('Not implemented');},computePercentile:function(x){throw Error('Not implemented');},computeComplementaryPercentile:function(x){return 1-this.computePercentile(x);},get mean(){throw Error('Not implemented');},get mode(){throw Error('Not implemented');},get median(){throw Error('Not implemented');},get standardDeviation(){throw Error('Not implemented');},get variance(){throw Error('Not implemented');}};Statistics.UniformDistribution=function(opt_range){if(!opt_range)opt_range=tr.b.Range.fromExplicitRange(0,1);this.range=opt_range;};Statistics.UniformDistribution.prototype={__proto__:Distribution.prototype,computeDensity:function(x){return 1/this.range.range;},computePercentile:function(x){return tr.b.normalize(x,this.range.min,this.range.max);},get mean(){return this.range.center;},get mode(){return undefined;},get median(){return this.mean;},get standardDeviation(){return Math.sqrt(this.variance);},get variance(){return Math.pow(this.range.range,2)/12;}};Statistics.NormalDistribution=function(opt_mean,opt_variance){this.mean_=opt_mean||0;this.variance_=opt_variance||1;this.standardDeviation_=Math.sqrt(this.variance_);};Statistics.NormalDistribution.prototype={__proto__:Distribution.prototype,computeDensity:function(x){var scale=1.0/(this.standardDeviation*Math.sqrt(2.0*Math.PI));var exponent=-Math.pow(x-this.mean,2)/(2.0*this.variance);return scale*Math.exp(exponent);},computePercentile:function(x){var standardizedX=(x-this.mean)/Math.sqrt(2.0*this.variance);return(1.0+tr.b.erf(standardizedX))/2.0;},get mean(){return this.mean_;},get median(){return this.mean;},get mode(){return this.mean;},get standardDeviation(){return this.standardDeviation_;},get variance(){return this.variance_;}};Statistics.LogNormalDistribution=function(opt_location,opt_shape){this.normalDistribution_=new Statistics.NormalDistribution(opt_location,Math.pow(opt_shape||1,2));};Statistics.LogNormalDistribution.prototype={__proto__:Statistics.NormalDistribution.prototype,computeDensity:function(x){return this.normalDistribution_.computeDensity(Math.log(x))/x;},computePercentile:function(x){return this.normalDistribution_.computePercentile(Math.log(x));},get mean(){return Math.exp(this.normalDistribution_.mean+this.normalDistribution_.variance/2);},get variance(){var nm=this.normalDistribution_.mean;var nv=this.normalDistribution_.variance;return Math.exp(2*(nm+nv))-Math.exp(2*nm+nv);},get standardDeviation(){return Math.sqrt(this.variance);},get median(){return Math.exp(this.normalDistribution_.mean);},get mode(){return Math.exp(this.normalDistribution_.mean-this.normalDistribution_.variance);}};Statistics.LogNormalDistribution.fromMedianAndDiminishingReturns=function(median,diminishingReturns){diminishingReturns=Math.log(diminishingReturns/median);var shape=Math.sqrt(1-3*diminishingReturns-Math.sqrt(Math.pow(diminishingReturns-3,2)-8))/2;var location=Math.log(median);return new Statistics.LogNormalDistribution(location,shape);};Statistics.DEFAULT_ALPHA=0.05;Statistics.Significance={INSIGNIFICANT:-1,DONT_CARE:0,SIGNIFICANT:1};Statistics.mwu=function(a,b,opt_alpha){var result=mannwhitneyu.test(a,b);var alpha=opt_alpha||Statistics.DEFAULT_ALPHA;result.significance=result.p<alpha?Statistics.Significance.SIGNIFICANT:Statistics.Significance.INSIGNIFICANT;return result;};return{Statistics:Statistics};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./math.js":47,"./range.js":52}],59:[function(require,module,exports){
-(function(global){
-"use strict";require("./raf.js");require("./timing.js");'use strict';global.tr.exportTo('tr.b',function(){var Timing=tr.b.Timing;function Task(runCb,thisArg){if(runCb!==undefined&&thisArg===undefined)throw new Error('Almost certainly, you meant to pass a thisArg.');this.runCb_=runCb;this.thisArg_=thisArg;this.afterTask_=undefined;this.subTasks_=[];}Task.prototype={get name(){return this.runCb_.name;},subTask:function(cb,thisArg){if(cb instanceof Task)this.subTasks_.push(cb);else this.subTasks_.push(new Task(cb,thisArg));return this.subTasks_[this.subTasks_.length-1];},run:function(){if(this.runCb_!==undefined)this.runCb_.call(this.thisArg_,this);var subTasks=this.subTasks_;this.subTasks_=undefined;if(!subTasks.length)return this.afterTask_;for(var i=1;i<subTasks.length;i++)subTasks[i-1].afterTask_=subTasks[i];subTasks[subTasks.length-1].afterTask_=this.afterTask_;return subTasks[0];},after:function(cb,thisArg){if(this.afterTask_)throw new Error('Has an after task already');if(cb instanceof Task)this.afterTask_=cb;else this.afterTask_=new Task(cb,thisArg);return this.afterTask_;},timedAfter:function(groupName,cb,thisArg,opt_args){if(cb.name==='')throw new Error('Anonymous Task is not allowed');return this.namedTimedAfter(groupName,cb.name,cb,thisArg,opt_args);},namedTimedAfter:function(groupName,name,cb,thisArg,opt_args){if(this.afterTask_)throw new Error('Has an after task already');var realTask;if(cb instanceof Task)realTask=cb;else realTask=new Task(cb,thisArg);this.afterTask_=new Task(function(task){var markedTask=Timing.mark(groupName,name,opt_args);task.subTask(realTask,thisArg);task.subTask(function(){markedTask.end();},thisArg);},thisArg);return this.afterTask_;},enqueue:function(cb,thisArg){var lastTask=this;while(lastTask.afterTask_)lastTask=lastTask.afterTask_;return lastTask.after(cb,thisArg);}};Task.RunSynchronously=function(task){var curTask=task;while(curTask)curTask=curTask.run();};Task.RunWhenIdle=function(task){return new Promise(function(resolve,reject){var curTask=task;function runAnother(){try{curTask=curTask.run();}catch(e){reject(e);console.error(e.stack);return;}if(curTask){tr.b.requestIdleCallback(runAnother);return;}resolve();}tr.b.requestIdleCallback(runAnother);});};return{Task:Task};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./raf.js":51,"./timing.js":61}],60:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./math.js":48,"./range.js":53}],60:[function(require,module,exports){
+(function (global){
+"use strict";require("./raf.js");require("./timing.js");'use strict';global.tr.exportTo('tr.b',function(){var Timing=tr.b.Timing;function Task(runCb,thisArg){if(runCb!==undefined&&thisArg===undefined)throw new Error('Almost certainly, you meant to pass a thisArg.');this.runCb_=runCb;this.thisArg_=thisArg;this.afterTask_=undefined;this.subTasks_=[];}Task.prototype={get name(){return this.runCb_.name;},subTask:function(cb,thisArg){if(cb instanceof Task)this.subTasks_.push(cb);else this.subTasks_.push(new Task(cb,thisArg));return this.subTasks_[this.subTasks_.length-1];},run:function(){if(this.runCb_!==undefined)this.runCb_.call(this.thisArg_,this);var subTasks=this.subTasks_;this.subTasks_=undefined;if(!subTasks.length)return this.afterTask_;for(var i=1;i<subTasks.length;i++)subTasks[i-1].afterTask_=subTasks[i];subTasks[subTasks.length-1].afterTask_=this.afterTask_;return subTasks[0];},after:function(cb,thisArg){if(this.afterTask_)throw new Error('Has an after task already');if(cb instanceof Task)this.afterTask_=cb;else this.afterTask_=new Task(cb,thisArg);return this.afterTask_;},timedAfter:function(groupName,cb,thisArg,opt_args){if(cb.name==='')throw new Error('Anonymous Task is not allowed');return this.namedTimedAfter(groupName,cb.name,cb,thisArg,opt_args);},namedTimedAfter:function(groupName,name,cb,thisArg,opt_args){if(this.afterTask_)throw new Error('Has an after task already');var realTask;if(cb instanceof Task)realTask=cb;else realTask=new Task(cb,thisArg);this.afterTask_=new Task(function(task){var markedTask=Timing.mark(groupName,name,opt_args);task.subTask(realTask,thisArg);task.subTask(function(){markedTask.end();},thisArg);},thisArg);return this.afterTask_;},enqueue:function(cb,thisArg){var lastTask=this;while(lastTask.afterTask_)lastTask=lastTask.afterTask_;return lastTask.after(cb,thisArg);}};Task.RunSynchronously=function(task){var curTask=task;while(curTask)curTask=curTask.run();};Task.RunWhenIdle=function(task){return new Promise(function(resolve,reject){var curTask=task;function runAnother(){try{curTask=curTask.run();}catch(e){reject(e);console.assert(true,e.stack);return;}if(curTask){tr.b.requestIdleCallback(runAnother);return;}resolve();}tr.b.requestIdleCallback(runAnother);});};return{Task:Task};});
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./raf.js":52,"./timing.js":62}],61:[function(require,module,exports){
+(function (global){
 "use strict";require("./unit_scale.js");'use strict';global.tr.exportTo('tr.b',function(){var msDisplayMode={scale:1e-3,suffix:'ms',roundedLess:function(a,b){return Math.round(a*1000)<Math.round(b*1000);},formatSpec:{unit:'s',unitPrefix:tr.b.UnitScale.Metric.MILLI,minimumFractionDigits:3}};var nsDisplayMode={scale:1e-9,suffix:'ns',roundedLess:function(a,b){return Math.round(a*1000000)<Math.round(b*1000000);},formatSpec:{unit:'s',unitPrefix:tr.b.UnitScale.Metric.NANO,maximumFractionDigits:0}};var TimeDisplayModes={ns:nsDisplayMode,ms:msDisplayMode};return{TimeDisplayModes:TimeDisplayModes};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./unit_scale.js":63}],61:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./unit_scale.js":64}],62:[function(require,module,exports){
+(function (global){
 "use strict";require("./base.js");require("./base64.js");'use strict';global.tr.exportTo('tr.b',function(){var Base64=tr.b.Base64;function computeUserTimingMarkName(groupName,functionName,opt_args){if(groupName===undefined)throw new Error('getMeasureString should have group name');if(functionName===undefined)throw new Error('getMeasureString should have function name');var userTimingMarkName=groupName+':'+functionName;if(opt_args!==undefined){userTimingMarkName+='/';userTimingMarkName+=Base64.btoa(JSON.stringify(opt_args));}return userTimingMarkName;}function Timing(){}Timing.nextMarkNumber=0;Timing.mark=function(groupName,functionName,opt_args){if(tr.isHeadless){return{end:function(){}};}var userTimingMarkName=computeUserTimingMarkName(groupName,functionName,opt_args);var markBeginName='tvcm.mark'+Timing.nextMarkNumber++;var markEndName='tvcm.mark'+Timing.nextMarkNumber++;window.performance.mark(markBeginName);return{end:function(){window.performance.mark(markEndName);window.performance.measure(userTimingMarkName,markBeginName,markEndName);}};};Timing.wrap=function(groupName,callback,opt_args){if(groupName===undefined)throw new Error('Timing.wrap should have group name');if(callback.name==='')throw new Error('Anonymous function is not allowed');return Timing.wrapNamedFunction(groupName,callback.name,callback,opt_args);};Timing.wrapNamedFunction=function(groupName,functionName,callback,opt_args){function timedNamedFunction(){var markedTime=Timing.mark(groupName,functionName,opt_args);try{callback.apply(this,arguments);}finally{markedTime.end();}}return timedNamedFunction;};function TimedNamedPromise(groupName,name,executor,opt_args){var markedTime=Timing.mark(groupName,name,opt_args);var promise=new Promise(executor);promise.then(function(result){markedTime.end();return result;},function(e){markedTime.end();throw e;});return promise;}return{_computeUserTimingMarkName:computeUserTimingMarkName,TimedNamedPromise:TimedNamedPromise,Timing:Timing};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./base.js":33,"./base64.js":34}],62:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./base.js":34,"./base64.js":35}],63:[function(require,module,exports){
+(function (global){
 "use strict";require("./event.js");require("./event_target.js");require("./iteration_helpers.js");require("./time_display_modes.js");require("./unit_scale.js");'use strict';global.tr.exportTo('tr.b',function(){var TimeDisplayModes=tr.b.TimeDisplayModes;var PLUS_MINUS_SIGN=String.fromCharCode(177);function max(a,b){if(a===undefined)return b;if(b===undefined)return a;return a.scale>b.scale?a:b;}var ImprovementDirection={DONT_CARE:0,BIGGER_IS_BETTER:1,SMALLER_IS_BETTER:2};function Unit(unitName,jsonName,basePrefix,isDelta,improvementDirection,formatSpec){this.unitName=unitName;this.jsonName=jsonName;this.basePrefix=basePrefix;this.isDelta=isDelta;this.improvementDirection=improvementDirection;this.formatSpec_=formatSpec;this.baseUnit=undefined;this.correspondingDeltaUnit=undefined;}Unit.prototype={asJSON:function(){return this.jsonName;},get unitString(){var formatSpec=this.formatSpec_;if(typeof formatSpec==='function')formatSpec=formatSpec();if(!formatSpec.unit){return'';}var unitString='';var unitPrefix=formatSpec.unitPrefix;if(unitPrefix!==undefined){var selectedPrefix;if(unitPrefix instanceof Array){selectedPrefix=unitPrefix[0];}else{selectedPrefix=unitPrefix;}unitString+=selectedPrefix.symbol||'';}unitString+=formatSpec.unit;return unitString;},format:function(value,opt_context){var context=opt_context||{};var formatSpec=this.formatSpec_;if(typeof formatSpec==='function')formatSpec=formatSpec();function resolveProperty(propertyName){if(propertyName in context)return context[propertyName];else if(propertyName in formatSpec)return formatSpec[propertyName];else return undefined;}var signString='';if(value<0){signString='-';value=-value;}else if(this.isDelta){signString=value===0?PLUS_MINUS_SIGN:'+';}var unitString='';if(formatSpec.unit){if(formatSpec.unitHasPrecedingSpace!==false)unitString+=' ';var unitPrefix=resolveProperty('unitPrefix');if(unitPrefix!==undefined){var selectedPrefix;if(unitPrefix instanceof Array){var i=0;while(i<unitPrefix.length-1&&value/unitPrefix[i+1].value>=1){i++;}selectedPrefix=unitPrefix[i];}else{selectedPrefix=unitPrefix;}unitString+=selectedPrefix.symbol||'';value=tr.b.convertUnit(value,this.basePrefix,selectedPrefix);}else{value=tr.b.convertUnit(value,this.basePrefix,tr.b.UnitScale.Metric.NONE);}unitString+=formatSpec.unit;}var minimumFractionDigits=resolveProperty('minimumFractionDigits');var maximumFractionDigits=resolveProperty('maximumFractionDigits');if(minimumFractionDigits>maximumFractionDigits){if('minimumFractionDigits'in context&&!('maximumFractionDigits'in context)){maximumFractionDigits=minimumFractionDigits;}else if('maximumFractionDigits'in context&&!('minimumFractionDigits'in context)){minimumFractionDigits=maximumFractionDigits;}}var numberString=value.toLocaleString(undefined,{minimumFractionDigits:minimumFractionDigits,maximumFractionDigits:maximumFractionDigits});return signString+numberString+unitString;}};Unit.reset=function(){Unit.currentTimeDisplayMode=TimeDisplayModes.ms;};Unit.timestampFromUs=function(us){return tr.b.convertUnit(us,tr.b.UnitScale.Metric.MICRO,tr.b.UnitScale.Metric.MILLI);};Object.defineProperty(Unit,'currentTimeDisplayMode',{get:function(){return Unit.currentTimeDisplayMode_;},set:function(value){if(Unit.currentTimeDisplayMode_===value)return;Unit.currentTimeDisplayMode_=value;Unit.dispatchEvent(new tr.b.Event('display-mode-changed'));}});Unit.didPreferredTimeDisplayUnitChange=function(){var largest=undefined;var els=tr.b.findDeepElementsMatching(document.body,'tr-v-ui-preferred-display-unit');els.forEach(function(el){largest=max(largest,el.preferredTimeDisplayMode);});Unit.currentDisplayUnit=largest===undefined?TimeDisplayModes.ms:largest;};Unit.byName={};Unit.byJSONName={};Unit.fromJSON=function(object){var u=Unit.byJSONName[object];if(u){return u;}throw new Error('Unrecognized unit');};Unit.define=function(params){var definedUnits=[];tr.b.iterItems(ImprovementDirection,function(_,improvementDirection){var regularUnit=Unit.defineUnitVariant_(params,false,improvementDirection);var deltaUnit=Unit.defineUnitVariant_(params,true,improvementDirection);regularUnit.correspondingDeltaUnit=deltaUnit;deltaUnit.correspondingDeltaUnit=deltaUnit;definedUnits.push(regularUnit,deltaUnit);});var baseUnit=Unit.byName[params.baseUnitName];definedUnits.forEach(u=>u.baseUnit=baseUnit);};Unit.nameSuffixForImprovementDirection=function(improvementDirection){switch(improvementDirection){case ImprovementDirection.DONT_CARE:return'';case ImprovementDirection.BIGGER_IS_BETTER:return'_biggerIsBetter';case ImprovementDirection.SMALLER_IS_BETTER:return'_smallerIsBetter';default:throw new Error('Unknown improvement direction: '+improvementDirection);}};Unit.defineUnitVariant_=function(params,isDelta,improvementDirection){var nameSuffix=isDelta?'Delta':'';nameSuffix+=Unit.nameSuffixForImprovementDirection(improvementDirection);var unitName=params.baseUnitName+nameSuffix;var jsonName=params.baseJsonName+nameSuffix;if(Unit.byName[unitName]!==undefined)throw new Error('Unit \''+unitName+'\' already exists');if(Unit.byJSONName[jsonName]!==undefined)throw new Error('JSON unit \''+jsonName+'\' alread exists');var basePrefix=params.basePrefix?params.basePrefix:tr.b.UnitScale.Metric.NONE;var unit=new Unit(unitName,jsonName,basePrefix,isDelta,improvementDirection,params.formatSpec);Unit.byName[unitName]=unit;Unit.byJSONName[jsonName]=unit;return unit;};tr.b.EventTarget.decorate(Unit);Unit.reset();Unit.define({baseUnitName:'timeDurationInMs',baseJsonName:'ms',basePrefix:tr.b.UnitScale.Metric.MILLI,formatSpec:function(){return Unit.currentTimeDisplayMode_.formatSpec;}});Unit.define({baseUnitName:'timeStampInMs',baseJsonName:'tsMs',basePrefix:tr.b.UnitScale.Metric.MILLI,formatSpec:function(){return Unit.currentTimeDisplayMode_.formatSpec;}});Unit.define({baseUnitName:'normalizedPercentage',baseJsonName:'n%',formatSpec:{unit:'%',unitPrefix:{value:0.01},unitHasPrecedingSpace:false,minimumFractionDigits:3,maximumFractionDigits:3}});Unit.define({baseUnitName:'sizeInBytes',baseJsonName:'sizeInBytes',formatSpec:{unit:'B',unitPrefix:tr.b.UnitScale.Binary.AUTO,minimumFractionDigits:1,maximumFractionDigits:1}});Unit.define({baseUnitName:'energyInJoules',baseJsonName:'J',formatSpec:{unit:'J',minimumFractionDigits:3}});Unit.define({baseUnitName:'powerInWatts',baseJsonName:'W',formatSpec:{unit:'W',minimumFractionDigits:3}});Unit.define({baseUnitName:'unitlessNumber',baseJsonName:'unitless',formatSpec:{minimumFractionDigits:3,maximumFractionDigits:3}});Unit.define({baseUnitName:'count',baseJsonName:'count',formatSpec:{minimumFractionDigits:0,maximumFractionDigits:0}});Unit.define({baseUnitName:'sigma',baseJsonName:'sigma',formatSpec:{unit:String.fromCharCode(963),minimumFractionDigits:1,maximumFractionDigits:1}});return{ImprovementDirection:ImprovementDirection,Unit:Unit};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./event.js":38,"./event_target.js":39,"./iteration_helpers.js":46,"./time_display_modes.js":60,"./unit_scale.js":63}],63:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./event.js":39,"./event_target.js":40,"./iteration_helpers.js":47,"./time_display_modes.js":61,"./unit_scale.js":64}],64:[function(require,module,exports){
+(function (global){
 "use strict";require("./iteration_helpers.js");'use strict';var GREEK_SMALL_LETTER_MU=String.fromCharCode(956);global.tr.exportTo('tr.b',function(){var UnitScale={};function defineUnitScale(name,prefixes){if(UnitScale[name]!==undefined)throw new Error('Unit scale \''+name+'\' already exists');if(prefixes.AUTO!==undefined){throw new Error('\'AUTO\' unit prefix will be added automatically '+'for unit scale \''+name+'\'');}prefixes.AUTO=tr.b.dictionaryValues(prefixes);prefixes.AUTO.sort((a,b)=>a.value-b.value);UnitScale[name]=prefixes;}function convertUnit(value,fromPrefix,toPrefix){if(value===undefined)return undefined;return value*(fromPrefix.value/toPrefix.value);}defineUnitScale('Binary',{NONE:{value:Math.pow(1024,0),symbol:''},KIBI:{value:Math.pow(1024,1),symbol:'Ki'},MEBI:{value:Math.pow(1024,2),symbol:'Mi'},GIBI:{value:Math.pow(1024,3),symbol:'Gi'},TEBI:{value:Math.pow(1024,4),symbol:'Ti'}});defineUnitScale('Metric',{NANO:{value:1e-9,symbol:'n'},MICRO:{value:1e-6,symbol:GREEK_SMALL_LETTER_MU},MILLI:{value:1e-3,symbol:'m'},NONE:{value:1,symbol:''},KILO:{value:1e3,symbol:'k'},MEGA:{value:1e6,symbol:'M'},GIGA:{value:1e9,symbol:'G'}});return{UnitScale:UnitScale,convertUnit:convertUnit};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./iteration_helpers.js":46}],64:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./iteration_helpers.js":47}],65:[function(require,module,exports){
+(function (global){
 "use strict";require("./base.js");'use strict';global.tr.exportTo('tr.b',function(){function addSingletonGetter(ctor){ctor.getInstance=function(){return ctor.instance_||(ctor.instance_=new ctor());};}function deepCopy(value){if(!(value instanceof Object)){if(value===undefined||value===null)return value;if(typeof value=='string')return value.substring();if(typeof value=='boolean')return value;if(typeof value=='number')return value;throw new Error('Unrecognized: '+typeof value);}var object=value;if(object instanceof Array){var res=new Array(object.length);for(var i=0;i<object.length;i++)res[i]=deepCopy(object[i]);return res;}if(object.__proto__!=Object.prototype)throw new Error('Can only clone simple types');var res={};for(var key in object){res[key]=deepCopy(object[key]);}return res;}function normalizeException(e){if(e===undefined||e===null){return{typeName:'UndefinedError',message:'Unknown: null or undefined exception',stack:'Unknown'};}if(typeof e=='string'){return{typeName:'StringError',message:e,stack:[e]};}var typeName;if(e.name){typeName=e.name;}else if(e.constructor){if(e.constructor.name){typeName=e.constructor.name;}else{typeName='AnonymousError';}}else{typeName='ErrorWithNoConstructor';}var msg=e.message?e.message:'Unknown';return{typeName:typeName,message:msg,stack:e.stack?e.stack:[msg]};}function stackTraceAsString(){return new Error().stack+'';}function stackTrace(){var stack=stackTraceAsString();stack=stack.split('\n');return stack.slice(2);}function getUsingPath(path,fromDict){var parts=path.split('.');var cur=fromDict;for(var part;parts.length&&(part=parts.shift());){if(!parts.length){return cur[part];}else if(part in cur){cur=cur[part];}else{return undefined;}}return undefined;}function formatDate(date){return date.toISOString().replace('T',' ').slice(0,19);}return{addSingletonGetter:addSingletonGetter,deepCopy:deepCopy,normalizeException:normalizeException,stackTrace:stackTrace,stackTraceAsString:stackTraceAsString,formatDate:formatDate,getUsingPath:getUsingPath};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./base.js":33}],65:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./base.js":34}],66:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/base.js");require("../base/extension_registry.js");'use strict';global.tr.exportTo('tr.c',function(){function Auditor(model){this.model_=model;}Auditor.prototype={__proto__:Object.prototype,get model(){return this.model_;},runAnnotate:function(){},installUserFriendlyCategoryDriverIfNeeded:function(){},runAudit:function(){}};var options=new tr.b.ExtensionRegistryOptions(tr.b.BASIC_REGISTRY_MODE);options.defaultMetadata={};options.mandatoryBaseClass=Auditor;tr.b.decorateExtensionRegistry(Auditor,options);return{Auditor:Auditor};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/base.js":33,"../base/extension_registry.js":40}],66:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/base.js":34,"../base/extension_registry.js":41}],67:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/base.js");'use strict';global.tr.exportTo('tr.c',function(){function makeCaseInsensitiveRegex(pattern){pattern=pattern.replace(/[.*+?^${}()|[\]\\]/g,'\\$&');return new RegExp(pattern,'i');}function Filter(){}Filter.prototype={__proto__:Object.prototype,matchCounter:function(counter){return true;},matchCpu:function(cpu){return true;},matchProcess:function(process){return true;},matchSlice:function(slice){return true;},matchThread:function(thread){return true;}};function TitleOrCategoryFilter(text){Filter.call(this);this.regex_=makeCaseInsensitiveRegex(text);if(!text.length)throw new Error('Filter text is empty.');}TitleOrCategoryFilter.prototype={__proto__:Filter.prototype,matchSlice:function(slice){if(slice.title===undefined&&slice.category===undefined)return false;return this.regex_.test(slice.title)||!!slice.category&&this.regex_.test(slice.category);}};function ExactTitleFilter(text){Filter.call(this);this.text_=text;if(!text.length)throw new Error('Filter text is empty.');}ExactTitleFilter.prototype={__proto__:Filter.prototype,matchSlice:function(slice){return slice.title===this.text_;}};function FullTextFilter(text){Filter.call(this);this.regex_=makeCaseInsensitiveRegex(text);this.titleOrCategoryFilter_=new TitleOrCategoryFilter(text);}FullTextFilter.prototype={__proto__:Filter.prototype,matchObject_:function(obj){for(var key in obj){if(!obj.hasOwnProperty(key))continue;if(this.regex_.test(key))return true;if(this.regex_.test(obj[key]))return true;}return false;},matchSlice:function(slice){if(this.titleOrCategoryFilter_.matchSlice(slice))return true;return this.matchObject_(slice.args);}};return{Filter:Filter,TitleOrCategoryFilter:TitleOrCategoryFilter,ExactTitleFilter:ExactTitleFilter,FullTextFilter:FullTextFilter};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/base.js":33}],67:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/base.js":34}],68:[function(require,module,exports){
+(function (global){
 "use strict";require("../../../model/async_slice.js");require("../../../model/event_set.js");require("../../../model/helpers/chrome_model_helper.js");'use strict';global.tr.exportTo('tr.e.cc',function(){var AsyncSlice=tr.model.AsyncSlice;var EventSet=tr.model.EventSet;var UI_COMP_NAME='INPUT_EVENT_LATENCY_UI_COMPONENT';var ORIGINAL_COMP_NAME='INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT';var BEGIN_COMP_NAME='INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT';var END_COMP_NAME='INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT';var MAIN_RENDERER_THREAD_NAME='CrRendererMain';var COMPOSITOR_THREAD_NAME='Compositor';var POSTTASK_FLOW_EVENT='disabled-by-default-toplevel.flow';var IPC_FLOW_EVENT='disabled-by-default-ipc.flow';var INPUT_EVENT_TYPE_NAMES={CHAR:'Char',CLICK:'GestureClick',CONTEXT_MENU:'ContextMenu',FLING_CANCEL:'GestureFlingCancel',FLING_START:'GestureFlingStart',KEY_DOWN:'KeyDown',KEY_DOWN_RAW:'RawKeyDown',KEY_UP:'KeyUp',LATENCY_SCROLL_UPDATE:'ScrollUpdate',MOUSE_DOWN:'MouseDown',MOUSE_ENTER:'MouseEnter',MOUSE_LEAVE:'MouseLeave',MOUSE_MOVE:'MouseMove',MOUSE_UP:'MouseUp',MOUSE_WHEEL:'MouseWheel',PINCH_BEGIN:'GesturePinchBegin',PINCH_END:'GesturePinchEnd',PINCH_UPDATE:'GesturePinchUpdate',SCROLL_BEGIN:'GestureScrollBegin',SCROLL_END:'GestureScrollEnd',SCROLL_UPDATE:'GestureScrollUpdate',SCROLL_UPDATE_RENDERER:'ScrollUpdate',SHOW_PRESS:'GestureShowPress',TAP:'GestureTap',TAP_CANCEL:'GestureTapCancel',TAP_DOWN:'GestureTapDown',TOUCH_CANCEL:'TouchCancel',TOUCH_END:'TouchEnd',TOUCH_MOVE:'TouchMove',TOUCH_START:'TouchStart',UNKNOWN:'UNKNOWN'};function InputLatencyAsyncSlice(){AsyncSlice.apply(this,arguments);this.associatedEvents_=new EventSet();this.typeName_=undefined;if(!this.isLegacyEvent)this.determineModernTypeName_();}InputLatencyAsyncSlice.prototype={__proto__:AsyncSlice.prototype,get isLegacyEvent(){return this.title==='InputLatency';},get typeName(){if(!this.typeName_)this.determineLegacyTypeName_();return this.typeName_;},checkTypeName_:function(){if(!this.typeName_)throw'Unable to determine typeName';var found=false;for(var typeName in INPUT_EVENT_TYPE_NAMES){if(this.typeName===INPUT_EVENT_TYPE_NAMES[typeName]){found=true;break;}}if(!found)this.typeName_=INPUT_EVENT_TYPE_NAMES.UNKNOWN;},determineModernTypeName_:function(){var lastColonIndex=this.title.lastIndexOf(':');if(lastColonIndex<0)return;var characterAfterLastColonIndex=lastColonIndex+1;this.typeName_=this.title.slice(characterAfterLastColonIndex);this.checkTypeName_();},determineLegacyTypeName_:function(){for(var subSlice of this.enumerateAllDescendents()){var subSliceIsAInputLatencyAsyncSlice=subSlice instanceof InputLatencyAsyncSlice;if(!subSliceIsAInputLatencyAsyncSlice)continue;if(!subSlice.typeName)continue;if(this.typeName_&&subSlice.typeName_){var subSliceHasDifferentTypeName=this.typeName_!==subSlice.typeName_;if(subSliceHasDifferentTypeName){throw'InputLatencyAsyncSlice.determineLegacyTypeName_() '+' found multiple typeNames';}}this.typeName_=subSlice.typeName_;}if(!this.typeName_)throw'InputLatencyAsyncSlice.determineLegacyTypeName_() failed';this.checkTypeName_();},getRendererHelper:function(sourceSlices){var traceModel=this.startThread.parent.model;var modelHelper=traceModel.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(!modelHelper)return undefined;var mainThread=undefined;var compositorThread=undefined;for(var i in sourceSlices){if(sourceSlices[i].parentContainer.name===MAIN_RENDERER_THREAD_NAME)mainThread=sourceSlices[i].parentContainer;else if(sourceSlices[i].parentContainer.name===COMPOSITOR_THREAD_NAME)compositorThread=sourceSlices[i].parentContainer;if(mainThread&&compositorThread)break;}var rendererHelpers=modelHelper.rendererHelpers;var pids=Object.keys(rendererHelpers);for(var i=0;i<pids.length;i++){var pid=pids[i];var rendererHelper=rendererHelpers[pid];if(rendererHelper.mainThread===mainThread||rendererHelper.compositorThread===compositorThread)return rendererHelper;}return undefined;},addEntireSliceHierarchy:function(slice){this.associatedEvents_.push(slice);slice.iterateAllSubsequentSlices(function(subsequentSlice){this.associatedEvents_.push(subsequentSlice);},this);},addDirectlyAssociatedEvents:function(flowEvents){var slices=[];flowEvents.forEach(function(flowEvent){this.associatedEvents_.push(flowEvent);var newSource=flowEvent.startSlice.mostTopLevelSlice;if(slices.indexOf(newSource)===-1)slices.push(newSource);},this);var lastFlowEvent=flowEvents[flowEvents.length-1];var lastSource=lastFlowEvent.endSlice.mostTopLevelSlice;if(slices.indexOf(lastSource)===-1)slices.push(lastSource);return slices;},addScrollUpdateEvents:function(rendererHelper){if(!rendererHelper||!rendererHelper.compositorThread)return;var compositorThread=rendererHelper.compositorThread;var gestureScrollUpdateStart=this.start;var gestureScrollUpdateEnd=this.end;var allCompositorAsyncSlices=compositorThread.asyncSliceGroup.slices;for(var i in allCompositorAsyncSlices){var slice=allCompositorAsyncSlices[i];if(slice.title!=='Latency::ScrollUpdate')continue;var parentId=slice.args.data.INPUT_EVENT_LATENCY_FORWARD_SCROLL_UPDATE_TO_MAIN_COMPONENT.sequence_number;if(parentId===undefined){if(slice.start<gestureScrollUpdateStart||slice.start>=gestureScrollUpdateEnd)continue;}else{if(parseInt(parentId)!==parseInt(this.id))continue;}slice.associatedEvents.forEach(function(event){this.associatedEvents_.push(event);},this);break;}},belongToOtherInputs:function(slice,flowEvents){var fromOtherInputs=false;slice.iterateEntireHierarchy(function(subsequentSlice){if(fromOtherInputs)return;subsequentSlice.inFlowEvents.forEach(function(inflow){if(fromOtherInputs)return;if(inflow.category.indexOf('input')>-1){if(flowEvents.indexOf(inflow)===-1)fromOtherInputs=true;}},this);},this);return fromOtherInputs;},triggerOtherInputs:function(event,flowEvents){if(event.outFlowEvents===undefined||event.outFlowEvents.length===0)return false;var flow=event.outFlowEvents[0];if(flow.category!==POSTTASK_FLOW_EVENT||!flow.endSlice)return false;var endSlice=flow.endSlice;if(this.belongToOtherInputs(endSlice.mostTopLevelSlice,flowEvents))return true;return false;},followSubsequentSlices:function(event,queue,visited,flowEvents){var stopFollowing=false;var inputAck=false;event.iterateAllSubsequentSlices(function(slice){if(stopFollowing)return;if(slice.title==='TaskQueueManager::RunTask')return;if(slice.title==='ThreadProxy::ScheduledActionSendBeginMainFrame')return;if(slice.title==='Scheduler::ScheduleBeginImplFrameDeadline'){if(this.triggerOtherInputs(slice,flowEvents))return;}if(slice.title==='CompositorImpl::PostComposite'){if(this.triggerOtherInputs(slice,flowEvents))return;}if(slice.title==='InputRouterImpl::ProcessInputEventAck')inputAck=true;if(inputAck&&slice.title==='InputRouterImpl::FilterAndSendWebInputEvent')stopFollowing=true;this.followCurrentSlice(slice,queue,visited);},this);},followCurrentSlice:function(event,queue,visited){event.outFlowEvents.forEach(function(outflow){if((outflow.category===POSTTASK_FLOW_EVENT||outflow.category===IPC_FLOW_EVENT)&&outflow.endSlice){this.associatedEvents_.push(outflow);var nextEvent=outflow.endSlice.mostTopLevelSlice;if(!visited.contains(nextEvent)){visited.push(nextEvent);queue.push(nextEvent);}}},this);},backtraceFromDraw:function(beginImplFrame,visited){var pendingEventQueue=[];pendingEventQueue.push(beginImplFrame.mostTopLevelSlice);while(pendingEventQueue.length!==0){var event=pendingEventQueue.pop();this.addEntireSliceHierarchy(event);event.inFlowEvents.forEach(function(inflow){if(inflow.category===POSTTASK_FLOW_EVENT&&inflow.startSlice){var nextEvent=inflow.startSlice.mostTopLevelSlice;if(!visited.contains(nextEvent)){visited.push(nextEvent);pendingEventQueue.push(nextEvent);}}},this);}},sortRasterizerSlices:function(rasterWorkerThreads,sortedRasterizerSlices){rasterWorkerThreads.forEach(function(rasterizer){Array.prototype.push.apply(sortedRasterizerSlices,rasterizer.sliceGroup.slices);},this);sortedRasterizerSlices.sort(function(a,b){if(a.start!==b.start)return a.start-b.start;return a.guid-b.guid;});},addRasterizationEvents:function(prepareTiles,rendererHelper,visited,flowEvents,sortedRasterizerSlices){if(!prepareTiles.args.prepare_tiles_id)return;if(!rendererHelper||!rendererHelper.rasterWorkerThreads)return;var rasterWorkerThreads=rendererHelper.rasterWorkerThreads;var prepareTileId=prepareTiles.args.prepare_tiles_id;var pendingEventQueue=[];if(sortedRasterizerSlices.length===0)this.sortRasterizerSlices(rasterWorkerThreads,sortedRasterizerSlices);var numFinishedTasks=0;var RASTER_TASK_TITLE='RasterizerTaskImpl::RunOnWorkerThread';var IMAGEDECODE_TASK_TITLE='ImageDecodeTaskImpl::RunOnWorkerThread';var FINISHED_TASK_TITLE='TaskSetFinishedTaskImpl::RunOnWorkerThread';for(var i=0;i<sortedRasterizerSlices.length;i++){var task=sortedRasterizerSlices[i];if(task.title===RASTER_TASK_TITLE||task.title===IMAGEDECODE_TASK_TITLE){if(task.args.source_prepare_tiles_id===prepareTileId)this.addEntireSliceHierarchy(task.mostTopLevelSlice);}else if(task.title===FINISHED_TASK_TITLE){if(task.start>prepareTiles.start){pendingEventQueue.push(task.mostTopLevelSlice);if(++numFinishedTasks===3)break;}}}while(pendingEventQueue.length!=0){var event=pendingEventQueue.pop();this.addEntireSliceHierarchy(event);this.followSubsequentSlices(event,pendingEventQueue,visited,flowEvents);}},addOtherCausallyRelatedEvents:function(rendererHelper,sourceSlices,flowEvents,sortedRasterizerSlices){var pendingEventQueue=[];var visitedEvents=new EventSet();var beginImplFrame=undefined;var prepareTiles=undefined;var sortedRasterizerSlices=[];sourceSlices.forEach(function(sourceSlice){if(!visitedEvents.contains(sourceSlice)){visitedEvents.push(sourceSlice);pendingEventQueue.push(sourceSlice);}},this);while(pendingEventQueue.length!=0){var event=pendingEventQueue.pop();this.addEntireSliceHierarchy(event);this.followCurrentSlice(event,pendingEventQueue,visitedEvents);this.followSubsequentSlices(event,pendingEventQueue,visitedEvents,flowEvents);var COMPOSITOR_PREPARE_TILES='TileManager::PrepareTiles';prepareTiles=event.findDescendentSlice(COMPOSITOR_PREPARE_TILES);if(prepareTiles)this.addRasterizationEvents(prepareTiles,rendererHelper,visitedEvents,flowEvents,sortedRasterizerSlices);var COMPOSITOR_ON_BIFD='Scheduler::OnBeginImplFrameDeadline';beginImplFrame=event.findDescendentSlice(COMPOSITOR_ON_BIFD);if(beginImplFrame)this.backtraceFromDraw(beginImplFrame,visitedEvents);}var INPUT_GSU='InputLatency::GestureScrollUpdate';if(this.title===INPUT_GSU)this.addScrollUpdateEvents(rendererHelper);},get associatedEvents(){if(this.associatedEvents_.length!==0)return this.associatedEvents_;var modelIndices=this.startThread.parent.model.modelIndices;var flowEvents=modelIndices.getFlowEventsWithId(this.id);if(flowEvents.length===0)return this.associatedEvents_;var sourceSlices=this.addDirectlyAssociatedEvents(flowEvents);var rendererHelper=this.getRendererHelper(sourceSlices);this.addOtherCausallyRelatedEvents(rendererHelper,sourceSlices,flowEvents);return this.associatedEvents_;},get inputLatency(){if(!('data'in this.args))return undefined;var data=this.args.data;if(!(END_COMP_NAME in data))return undefined;var latency=0;var endTime=data[END_COMP_NAME].time;if(ORIGINAL_COMP_NAME in data){latency=endTime-data[ORIGINAL_COMP_NAME].time;}else if(UI_COMP_NAME in data){latency=endTime-data[UI_COMP_NAME].time;}else if(BEGIN_COMP_NAME in data){latency=endTime-data[BEGIN_COMP_NAME].time;}else{throw new Error('No valid begin latency component');}return latency;}};var eventTypeNames=['Char','ContextMenu','GestureClick','GestureFlingCancel','GestureFlingStart','GestureScrollBegin','GestureScrollEnd','GestureScrollUpdate','GestureShowPress','GestureTap','GestureTapCancel','GestureTapDown','GesturePinchBegin','GesturePinchEnd','GesturePinchUpdate','KeyDown','KeyUp','MouseDown','MouseEnter','MouseLeave','MouseMove','MouseUp','MouseWheel','RawKeyDown','ScrollUpdate','TouchCancel','TouchEnd','TouchMove','TouchStart'];var allTypeNames=['InputLatency'];eventTypeNames.forEach(function(eventTypeName){allTypeNames.push('InputLatency:'+eventTypeName);allTypeNames.push('InputLatency::'+eventTypeName);});AsyncSlice.subTypes.register(InputLatencyAsyncSlice,{typeNames:allTypeNames,categoryParts:['latencyInfo']});return{InputLatencyAsyncSlice:InputLatencyAsyncSlice,INPUT_EVENT_TYPE_NAMES:INPUT_EVENT_TYPE_NAMES};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../../model/async_slice.js":108,"../../../model/event_set.js":125,"../../../model/helpers/chrome_model_helper.js":132}],68:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../../model/async_slice.js":109,"../../../model/event_set.js":126,"../../../model/helpers/chrome_model_helper.js":133}],69:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/event.js");require("../../base/iteration_helpers.js");require("../../base/sinebow_color_generator.js");'use strict';global.tr.exportTo('tr.e.chrome',function(){var SAME_AS_PARENT='same-as-parent';var TITLES_FOR_USER_FRIENDLY_CATEGORY={composite:['CompositingInputsUpdater::update','ThreadProxy::SetNeedsUpdateLayers','LayerTreeHost::UpdateLayers::CalcDrawProps','UpdateLayerTree'],gc:['minorGC','majorGC','MajorGC','MinorGC','V8.GCScavenger','V8.GCIncrementalMarking','V8.GCIdleNotification','V8.GCContext','V8.GCCompactor','V8GCController::traceDOMWrappers'],iframe_creation:['WebLocalFrameImpl::createChildframe'],imageDecode:['Decode Image','ImageFrameGenerator::decode','ImageFrameGenerator::decodeAndScale'],input:['HitTest','ScrollableArea::scrollPositionChanged','EventHandler::handleMouseMoveEvent'],layout:['FrameView::invalidateTree','FrameView::layout','FrameView::performLayout','FrameView::performPostLayoutTasks','FrameView::performPreLayoutTasks','Layer::updateLayerPositionsAfterLayout','Layout','LayoutView::hitTest','ResourceLoadPriorityOptimizer::updateAllImageResourcePriorities','WebViewImpl::layout'],parseHTML:['ParseHTML','HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser','HTMLDocumentParser::processParsedChunkFromBackgroundParser'],raster:['DisplayListRasterSource::PerformSolidColorAnalysis','Picture::Raster','RasterBufferImpl::Playback','RasterTask','RasterizerTaskImpl::RunOnWorkerThread','SkCanvas::drawImageRect()','SkCanvas::drawPicture()','SkCanvas::drawTextBlob()','TileTaskWorkerPool::PlaybackToMemory'],record:['ContentLayerDelegate::paintContents','DeprecatedPaintLayerCompositor::updateIfNeededRecursive','DeprecatedPaintLayerCompositor::updateLayerPositionsAfterLayout','Paint','Picture::Record','PictureLayer::Update','RenderLayer::updateLayerPositionsAfterLayout'],style:['CSSParserImpl::parseStyleSheet.parse','CSSParserImpl::parseStyleSheet.tokenize','Document::updateStyle','Document::updateStyleInvalidationIfNeeded','ParseAuthorStyleSheet','RuleSet::addRulesFromSheet','StyleElement::processStyleSheet','StyleEngine::createResolver','StyleSheetContents::parseAuthorStyleSheet','UpdateLayoutTree'],script_parse_and_compile:['v8.parseOnBackground','V8.ScriptCompiler'],script_execute:['V8.Execute','WindowProxy::initialize'],resource_loading:['ResourceFetcher::requestResource','ResourceDispatcher::OnReceivedData','ResourceDispatcher::OnRequestComplete','ResourceDispatcher::OnReceivedResponse','Resource::appendData'],renderer_misc:['DecodeFont','ThreadState::completeSweep'],v8_runtime:[],[SAME_AS_PARENT]:['SyncChannel::Send']};var COLOR_FOR_USER_FRIENDLY_CATEGORY=new tr.b.SinebowColorGenerator();var USER_FRIENDLY_CATEGORY_FOR_TITLE=new Map();for(var category in TITLES_FOR_USER_FRIENDLY_CATEGORY){TITLES_FOR_USER_FRIENDLY_CATEGORY[category].forEach(function(title){USER_FRIENDLY_CATEGORY_FOR_TITLE.set(title,category);});}var USER_FRIENDLY_CATEGORY_FOR_EVENT_CATEGORY={netlog:'net',overhead:'overhead',startup:'startup',gpu:'gpu'};function ChromeUserFriendlyCategoryDriver(){}ChromeUserFriendlyCategoryDriver.fromEvent=function(event){var userFriendlyCategory=USER_FRIENDLY_CATEGORY_FOR_TITLE.get(event.title);if(userFriendlyCategory){if(userFriendlyCategory==SAME_AS_PARENT){if(event.parentSlice)return ChromeUserFriendlyCategoryDriver.fromEvent(event.parentSlice);}else{return userFriendlyCategory;}}var eventCategoryParts=tr.b.getCategoryParts(event.category);for(var i=0;i<eventCategoryParts.length;++i){var eventCategory=eventCategoryParts[i];userFriendlyCategory=USER_FRIENDLY_CATEGORY_FOR_EVENT_CATEGORY[eventCategory];if(userFriendlyCategory)return userFriendlyCategory;}return'other';};ChromeUserFriendlyCategoryDriver.getColor=function(ufc){return COLOR_FOR_USER_FRIENDLY_CATEGORY.colorForKey(ufc);};ChromeUserFriendlyCategoryDriver.ALL_TITLES=['other'];for(var category in TITLES_FOR_USER_FRIENDLY_CATEGORY){if(category===SAME_AS_PARENT)continue;ChromeUserFriendlyCategoryDriver.ALL_TITLES.push(category);}for(var category of tr.b.dictionaryValues(USER_FRIENDLY_CATEGORY_FOR_EVENT_CATEGORY)){ChromeUserFriendlyCategoryDriver.ALL_TITLES.push(category);}ChromeUserFriendlyCategoryDriver.ALL_TITLES.sort();for(var category of ChromeUserFriendlyCategoryDriver.ALL_TITLES)ChromeUserFriendlyCategoryDriver.getColor(category);return{ChromeUserFriendlyCategoryDriver:ChromeUserFriendlyCategoryDriver};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/event.js":38,"../../base/iteration_helpers.js":46,"../../base/sinebow_color_generator.js":56}],69:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/event.js":39,"../../base/iteration_helpers.js":47,"../../base/sinebow_color_generator.js":57}],70:[function(require,module,exports){
+(function (global){
 "use strict";require("../../model/source_info/js_source_info.js");'use strict';global.tr.exportTo('tr.e.importer',function(){function TraceCodeEntry(address,size,name,scriptId){this.id_=tr.b.GUID.allocateSimple();this.address_=address;this.size_=size;var rePrefix=/^(\w*:)?([*~]?)(.*)$/m;var tokens=rePrefix.exec(name);var prefix=tokens[1];var state=tokens[2];var body=tokens[3];if(state==='*'){state=tr.model.source_info.JSSourceState.OPTIMIZED;}else if(state==='~'){state=tr.model.source_info.JSSourceState.OPTIMIZABLE;}else if(state===''){state=tr.model.source_info.JSSourceState.COMPILED;}else{console.warning('Unknown v8 code state '+state);state=tr.model.source_info.JSSourceState.UNKNOWN;}var rawName;var rawUrl;if(prefix==='Script:'){rawName='';rawUrl=body;}else{var spacePos=body.lastIndexOf(' ');rawName=spacePos!==-1?body.substr(0,spacePos):body;rawUrl=spacePos!==-1?body.substr(spacePos+1):'';}function splitLineAndColumn(url){var lineColumnRegEx=/(?::(\d+))?(?::(\d+))?$/;var lineColumnMatch=lineColumnRegEx.exec(url);var lineNumber;var columnNumber;if(typeof lineColumnMatch[1]==='string'){lineNumber=parseInt(lineColumnMatch[1],10);lineNumber=isNaN(lineNumber)?undefined:lineNumber-1;}if(typeof lineColumnMatch[2]==='string'){columnNumber=parseInt(lineColumnMatch[2],10);columnNumber=isNaN(columnNumber)?undefined:columnNumber-1;}return{url:url.substring(0,url.length-lineColumnMatch[0].length),lineNumber:lineNumber,columnNumber:columnNumber};}var nativeSuffix=' native';var isNative=rawName.endsWith(nativeSuffix);this.name_=isNative?rawName.slice(0,-nativeSuffix.length):rawName;var urlData=splitLineAndColumn(rawUrl);var url=urlData.url||'';var line=urlData.lineNumber||0;var column=urlData.columnNumber||0;this.sourceInfo_=new tr.model.source_info.JSSourceInfo(url,line,column,isNative,scriptId,state);};TraceCodeEntry.prototype={get id(){return this.id_;},get sourceInfo(){return this.sourceInfo_;},get name(){return this.name_;},set address(address){this.address_=address;},get address(){return this.address_;},set size(size){this.size_=size;},get size(){return this.size_;}};return{TraceCodeEntry:TraceCodeEntry};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../model/source_info/js_source_info.js":158}],70:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../model/source_info/js_source_info.js":159}],71:[function(require,module,exports){
+(function (global){
 "use strict";require("./trace_code_entry.js");'use strict';global.tr.exportTo('tr.e.importer',function(){function TraceCodeMap(){this.banks_=new Map();}TraceCodeMap.prototype={addEntry:function(addressHex,size,name,scriptId){var entry=new tr.e.importer.TraceCodeEntry(this.getAddress_(addressHex),size,name,scriptId);this.addEntry_(addressHex,entry);},moveEntry:function(oldAddressHex,newAddressHex,size){var entry=this.getBank_(oldAddressHex).removeEntry(this.getAddress_(oldAddressHex));if(!entry)return;entry.address=this.getAddress_(newAddressHex);entry.size=size;this.addEntry_(newAddressHex,entry);},lookupEntry:function(addressHex){return this.getBank_(addressHex).lookupEntry(this.getAddress_(addressHex));},addEntry_:function(addressHex,entry){this.getBank_(addressHex).addEntry(entry);},getAddress_:function(addressHex){var bankSizeHexDigits=13;addressHex=addressHex.slice(2);return parseInt(addressHex.slice(-bankSizeHexDigits),16);},getBank_:function(addressHex){addressHex=addressHex.slice(2);var bankSizeHexDigits=13;var maxHexDigits=16;var bankName=addressHex.slice(-maxHexDigits,-bankSizeHexDigits);var bank=this.banks_.get(bankName);if(!bank){bank=new TraceCodeBank();this.banks_.set(bankName,bank);}return bank;}};function TraceCodeBank(){this.entries_=[];}TraceCodeBank.prototype={removeEntry:function(address){if(this.entries_.length===0)return undefined;var index=tr.b.findLowIndexInSortedArray(this.entries_,function(entry){return entry.address;},address);var entry=this.entries_[index];if(!entry||entry.address!==address)return undefined;this.entries_.splice(index,1);return entry;},lookupEntry:function(address){var index=tr.b.findHighIndexInSortedArray(this.entries_,function(e){return address-e.address;})-1;var entry=this.entries_[index];return entry&&address<entry.address+entry.size?entry:undefined;},addEntry:function(newEntry){if(this.entries_.length===0)this.entries_.push(newEntry);var endAddress=newEntry.address+newEntry.size;var lastIndex=tr.b.findLowIndexInSortedArray(this.entries_,function(entry){return entry.address;},endAddress);var index;for(index=lastIndex-1;index>=0;--index){var entry=this.entries_[index];var entryEndAddress=entry.address+entry.size;if(entryEndAddress<=newEntry.address)break;}++index;this.entries_.splice(index,lastIndex-index,newEntry);}};return{TraceCodeMap:TraceCodeMap};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./trace_code_entry.js":69}],71:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./trace_code_entry.js":70}],72:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/base64.js");require("../../base/color_scheme.js");require("../../base/range.js");require("../../base/unit.js");require("../../base/utils.js");require("./trace_code_entry.js");require("./trace_code_map.js");require("./v8/codemap.js");require("../../importer/context_processor.js");require("../../importer/importer.js");require("../../model/comment_box_annotation.js");require("../../model/constants.js");require("../../model/container_memory_dump.js");require("../../model/counter_series.js");require("../../model/flow_event.js");require("../../model/global_memory_dump.js");require("../../model/heap_dump.js");require("../../model/instant_event.js");require("../../model/memory_allocator_dump.js");require("../../model/model.js");require("../../model/process_memory_dump.js");require("../../model/rect_annotation.js");require("../../model/scoped_id.js");require("../../model/slice_group.js");require("../../model/vm_region.js");require("../../model/x_marker_annotation.js");require("../../value/numeric.js");'use strict';global.tr.exportTo('tr.e.importer',function(){var Base64=tr.b.Base64;var deepCopy=tr.b.deepCopy;var ColorScheme=tr.b.ColorScheme;function getEventColor(event,opt_customName){if(event.cname)return ColorScheme.getColorIdForReservedName(event.cname);else if(opt_customName||event.name){return ColorScheme.getColorIdForGeneralPurposeString(opt_customName||event.name);}}var PRODUCER='producer';var CONSUMER='consumer';var STEP='step';var BACKGROUND=tr.model.ContainerMemoryDump.LevelOfDetail.BACKGROUND;var LIGHT=tr.model.ContainerMemoryDump.LevelOfDetail.LIGHT;var DETAILED=tr.model.ContainerMemoryDump.LevelOfDetail.DETAILED;var MEMORY_DUMP_LEVEL_OF_DETAIL_ORDER=[undefined,BACKGROUND,LIGHT,DETAILED];var GLOBAL_MEMORY_ALLOCATOR_DUMP_PREFIX='global/';var ASYNC_CLOCK_SYNC_EVENT_TITLE_PREFIX='ClockSyncEvent.';var BYTE_STAT_NAME_MAP={'pc':'privateCleanResident','pd':'privateDirtyResident','sc':'sharedCleanResident','sd':'sharedDirtyResident','pss':'proportionalResident','sw':'swapped'};var WEAK_MEMORY_ALLOCATOR_DUMP_FLAG=1<<0;var OBJECT_TYPE_NAME_PATTERNS=[{prefix:'const char *WTF::getStringWithTypeName() [T = ',suffix:']'},{prefix:'const char* WTF::getStringWithTypeName() [with T = ',suffix:']'},{prefix:'const char *__cdecl WTF::getStringWithTypeName<',suffix:'>(void)'}];var SUBTRACE_FIELDS=new Set(['powerTraceAsString','systemTraceEvents']);var NON_METADATA_FIELDS=new Set(['samples','stackFrames','traceAnnotations','traceEvents']);for(var subtraceField in SUBTRACE_FIELDS)NON_METADATA_FIELDS.add(subtraceField);function TraceEventImporter(model,eventData){this.importPriority=1;this.model_=model;this.events_=undefined;this.sampleEvents_=undefined;this.stackFrameEvents_=undefined;this.subtraces_=[];this.eventsWereFromString_=false;this.softwareMeasuredCpuCount_=undefined;this.allAsyncEvents_=[];this.allFlowEvents_=[];this.allObjectEvents_=[];this.contextProcessorPerThread={};this.traceEventSampleStackFramesByName_={};this.v8ProcessCodeMaps_={};this.v8ProcessRootStackFrame_={};this.v8SamplingData_=[];this.asyncClockSyncStart_=undefined;this.asyncClockSyncFinish_=undefined;this.allMemoryDumpEvents_={};this.objectTypeNameMap_={};this.clockDomainId_=tr.model.ClockDomainId.UNKNOWN_CHROME_LEGACY;this.toModelTime_=undefined;if(typeof eventData==='string'||eventData instanceof String){eventData=eventData.trim();if(eventData[0]==='['){eventData=eventData.replace(/\s*,\s*$/,'');if(eventData[eventData.length-1]!==']')eventData=eventData+']';}this.events_=JSON.parse(eventData);this.eventsWereFromString_=true;}else{this.events_=eventData;}this.traceAnnotations_=this.events_.traceAnnotations;if(this.events_.traceEvents){var container=this.events_;this.events_=this.events_.traceEvents;for(var subtraceField of SUBTRACE_FIELDS)if(container[subtraceField])this.subtraces_.push(container[subtraceField]);this.sampleEvents_=container.samples;this.stackFrameEvents_=container.stackFrames;if(container.displayTimeUnit){var unitName=container.displayTimeUnit;var unit=tr.b.TimeDisplayModes[unitName];if(unit===undefined){throw new Error('Unit '+unitName+' is not supported.');}this.model_.intrinsicTimeUnit=unit;}for(var fieldName in container){if(NON_METADATA_FIELDS.has(fieldName))continue;this.model_.metadata.push({name:fieldName,value:container[fieldName]});if(fieldName==='metadata'){var metadata=container[fieldName];if(metadata['highres-ticks'])this.model_.isTimeHighResolution=metadata['highres-ticks'];if(metadata['clock-domain'])this.clockDomainId_=metadata['clock-domain'];}}}}TraceEventImporter.canImport=function(eventData){if(typeof eventData==='string'||eventData instanceof String){eventData=eventData.trim();return eventData[0]==='{'||eventData[0]==='[';}if(eventData instanceof Array&&eventData.length&&eventData[0].ph)return true;if(eventData.traceEvents){if(eventData.traceEvents instanceof Array){if(eventData.traceEvents.length&&eventData.traceEvents[0].ph)return true;if(eventData.samples.length&&eventData.stackFrames!==undefined)return true;}}return false;};TraceEventImporter.prototype={__proto__:tr.importer.Importer.prototype,get importerName(){return'TraceEventImporter';},extractSubtraces:function(){var subtraces=this.subtraces_;this.subtraces_=[];return subtraces;},deepCopyIfNeeded_:function(obj){if(obj===undefined)obj={};if(this.eventsWereFromString_)return obj;return deepCopy(obj);},deepCopyAlways_:function(obj){if(obj===undefined)obj={};return deepCopy(obj);},processAsyncEvent:function(event){var thread=this.model_.getOrCreateProcess(event.pid).getOrCreateThread(event.tid);this.allAsyncEvents_.push({sequenceNumber:this.allAsyncEvents_.length,event:event,thread:thread});},processFlowEvent:function(event,opt_slice){var thread=this.model_.getOrCreateProcess(event.pid).getOrCreateThread(event.tid);this.allFlowEvents_.push({refGuid:tr.b.GUID.getLastSimpleGuid(),sequenceNumber:this.allFlowEvents_.length,event:event,slice:opt_slice,thread:thread});},processCounterEvent:function(event){var ctrName;if(event.id!==undefined)ctrName=event.name+'['+event.id+']';else ctrName=event.name;var ctr=this.model_.getOrCreateProcess(event.pid).getOrCreateCounter(event.cat,ctrName);var reservedColorId=event.cname?getEventColor(event):undefined;if(ctr.numSeries===0){for(var seriesName in event.args){var colorId=reservedColorId||getEventColor(event,ctr.name+'.'+seriesName);ctr.addSeries(new tr.model.CounterSeries(seriesName,colorId));}if(ctr.numSeries===0){this.model_.importWarning({type:'counter_parse_error',message:'Expected counter '+event.name+' to have at least one argument to use as a value.'});delete ctr.parent.counters[ctr.name];return;}}var ts=this.toModelTimeFromUs_(event.ts);ctr.series.forEach(function(series){var val=event.args[series.name]?event.args[series.name]:0;series.addCounterSample(ts,val);});},scopedIdForEvent_:function(event){return new tr.model.ScopedId(event.scope||tr.model.OBJECT_DEFAULT_SCOPE,event.id);},processObjectEvent:function(event){var thread=this.model_.getOrCreateProcess(event.pid).getOrCreateThread(event.tid);this.allObjectEvents_.push({sequenceNumber:this.allObjectEvents_.length,event:event,thread:thread});if(thread.guid in this.contextProcessorPerThread){var processor=this.contextProcessorPerThread[thread.guid];var scopedId=this.scopedIdForEvent_(event);if(event.ph==='D')processor.destroyContext(scopedId);processor.invalidateContextCacheForSnapshot(scopedId);}},processContextEvent:function(event){var thread=this.model_.getOrCreateProcess(event.pid).getOrCreateThread(event.tid);if(!(thread.guid in this.contextProcessorPerThread)){this.contextProcessorPerThread[thread.guid]=new tr.importer.ContextProcessor(this.model_);}var scopedId=this.scopedIdForEvent_(event);var contextType=event.name;var processor=this.contextProcessorPerThread[thread.guid];if(event.ph==='('){processor.enterContext(contextType,scopedId);}else if(event.ph===')'){processor.leaveContext(contextType,scopedId);}else{this.model_.importWarning({type:'unknown_context_phase',message:'Unknown context event phase: '+event.ph+'.'});}},setContextsFromThread_:function(thread,slice){if(thread.guid in this.contextProcessorPerThread){slice.contexts=this.contextProcessorPerThread[thread.guid].activeContexts;}},processDurationEvent:function(event){var thread=this.model_.getOrCreateProcess(event.pid).getOrCreateThread(event.tid);var ts=this.toModelTimeFromUs_(event.ts);if(!thread.sliceGroup.isTimestampValidForBeginOrEnd(ts)){this.model_.importWarning({type:'duration_parse_error',message:'Timestamps are moving backward.'});return;}if(event.ph==='B'){var slice=thread.sliceGroup.beginSlice(event.cat,event.name,this.toModelTimeFromUs_(event.ts),this.deepCopyIfNeeded_(event.args),this.toModelTimeFromUs_(event.tts),event.argsStripped,getEventColor(event));slice.startStackFrame=this.getStackFrameForEvent_(event);this.setContextsFromThread_(thread,slice);}else if(event.ph==='I'||event.ph==='i'||event.ph==='R'){if(event.s!==undefined&&event.s!=='t')throw new Error('This should never happen');thread.sliceGroup.beginSlice(event.cat,event.name,this.toModelTimeFromUs_(event.ts),this.deepCopyIfNeeded_(event.args),this.toModelTimeFromUs_(event.tts),event.argsStripped,getEventColor(event));var slice=thread.sliceGroup.endSlice(this.toModelTimeFromUs_(event.ts),this.toModelTimeFromUs_(event.tts));slice.startStackFrame=this.getStackFrameForEvent_(event);slice.endStackFrame=undefined;}else{if(!thread.sliceGroup.openSliceCount){this.model_.importWarning({type:'duration_parse_error',message:'E phase event without a matching B phase event.'});return;}var slice=thread.sliceGroup.endSlice(this.toModelTimeFromUs_(event.ts),this.toModelTimeFromUs_(event.tts),getEventColor(event));if(event.name&&slice.title!=event.name){this.model_.importWarning({type:'title_match_error',message:'Titles do not match. Title is '+slice.title+' in openSlice, and is '+event.name+' in endSlice'});}slice.endStackFrame=this.getStackFrameForEvent_(event);this.mergeArgsInto_(slice.args,event.args,slice.title);}},mergeArgsInto_:function(dstArgs,srcArgs,eventName){for(var arg in srcArgs){if(dstArgs[arg]!==undefined){this.model_.importWarning({type:'arg_merge_error',message:'Different phases of '+eventName+' provided values for argument '+arg+'.'+' The last provided value will be used.'});}dstArgs[arg]=this.deepCopyIfNeeded_(srcArgs[arg]);}},processCompleteEvent:function(event){if(event.cat!==undefined&&event.cat.indexOf('trace_event_overhead')>-1)return undefined;var thread=this.model_.getOrCreateProcess(event.pid).getOrCreateThread(event.tid);if(event.flow_out){if(event.flow_in)event.flowPhase=STEP;else event.flowPhase=PRODUCER;}else if(event.flow_in){event.flowPhase=CONSUMER;}var slice=thread.sliceGroup.pushCompleteSlice(event.cat,event.name,this.toModelTimeFromUs_(event.ts),this.maybeToModelTimeFromUs_(event.dur),this.maybeToModelTimeFromUs_(event.tts),this.maybeToModelTimeFromUs_(event.tdur),this.deepCopyIfNeeded_(event.args),event.argsStripped,getEventColor(event),event.bind_id);slice.startStackFrame=this.getStackFrameForEvent_(event);slice.endStackFrame=this.getStackFrameForEvent_(event,true);this.setContextsFromThread_(thread,slice);return slice;},processJitCodeEvent:function(event){if(this.v8ProcessCodeMaps_[event.pid]===undefined)this.v8ProcessCodeMaps_[event.pid]=new tr.e.importer.TraceCodeMap();var map=this.v8ProcessCodeMaps_[event.pid];var data=event.args.data;if(event.name==='JitCodeMoved')map.moveEntry(data.code_start,data.new_code_start,data.code_len);else map.addEntry(data.code_start,data.code_len,data.name,data.script_id);},processMetadataEvent:function(event){if(event.name==='JitCodeAdded'||event.name==='JitCodeMoved'){this.v8SamplingData_.push(event);return;}if(event.argsStripped)return;if(event.name==='process_name'){var process=this.model_.getOrCreateProcess(event.pid);process.name=event.args.name;}else if(event.name==='process_labels'){var process=this.model_.getOrCreateProcess(event.pid);var labels=event.args.labels.split(',');for(var i=0;i<labels.length;i++)process.addLabelIfNeeded(labels[i]);}else if(event.name==='process_sort_index'){var process=this.model_.getOrCreateProcess(event.pid);process.sortIndex=event.args.sort_index;}else if(event.name==='thread_name'){var thread=this.model_.getOrCreateProcess(event.pid).getOrCreateThread(event.tid);thread.name=event.args.name;}else if(event.name==='thread_sort_index'){var thread=this.model_.getOrCreateProcess(event.pid).getOrCreateThread(event.tid);thread.sortIndex=event.args.sort_index;}else if(event.name==='num_cpus'){var n=event.args.number;if(this.softwareMeasuredCpuCount_!==undefined)n=Math.max(n,this.softwareMeasuredCpuCount_);this.softwareMeasuredCpuCount_=n;}else if(event.name==='stackFrames'){var stackFrames=event.args.stackFrames;if(stackFrames===undefined){this.model_.importWarning({type:'metadata_parse_error',message:'No stack frames found in a \''+event.name+'\' metadata event'});}else{this.importStackFrames_(stackFrames,'p'+event.pid+':');}}else if(event.name==='typeNames'){var objectTypeNameMap=event.args.typeNames;if(objectTypeNameMap===undefined){this.model_.importWarning({type:'metadata_parse_error',message:'No mapping from object type IDs to names found in a \''+event.name+'\' metadata event'});}else{this.importObjectTypeNameMap_(objectTypeNameMap,event.pid);}}else if(event.name==='TraceConfig'){this.model_.metadata.push({name:'TraceConfig',value:event.args.value});}else{this.model_.importWarning({type:'metadata_parse_error',message:'Unrecognized metadata name: '+event.name});}},processInstantEvent:function(event){if(event.name==='JitCodeAdded'||event.name==='JitCodeMoved'){this.v8SamplingData_.push(event);return;}if(event.s==='t'||event.s===undefined){this.processDurationEvent(event);return;}var constructor;switch(event.s){case'g':constructor=tr.model.GlobalInstantEvent;break;case'p':constructor=tr.model.ProcessInstantEvent;break;default:this.model_.importWarning({type:'instant_parse_error',message:'I phase event with unknown "s" field value.'});return;}var instantEvent=new constructor(event.cat,event.name,getEventColor(event),this.toModelTimeFromUs_(event.ts),this.deepCopyIfNeeded_(event.args));switch(instantEvent.type){case tr.model.InstantEventType.GLOBAL:this.model_.instantEvents.push(instantEvent);break;case tr.model.InstantEventType.PROCESS:var process=this.model_.getOrCreateProcess(event.pid);process.instantEvents.push(instantEvent);break;default:throw new Error('Unknown instant event type: '+event.s);}},processV8Sample:function(event){var data=event.args.data;if(data.vm_state==='js'&&!data.stack.length)return;var rootStackFrame=this.v8ProcessRootStackFrame_[event.pid];if(!rootStackFrame){rootStackFrame=new tr.model.StackFrame(undefined,'v8-root-stack-frame','v8-root-stack-frame',0);this.v8ProcessRootStackFrame_[event.pid]=rootStackFrame;}function findChildWithEntryID(stackFrame,entryID){return tr.b.findFirstInArray(stackFrame.children,function(child){return child.entryID===entryID;});}var model=this.model_;function addStackFrame(lastStackFrame,entry){var childFrame=findChildWithEntryID(lastStackFrame,entry.id);if(childFrame)return childFrame;var frame=new tr.model.StackFrame(lastStackFrame,tr.b.GUID.allocateSimple(),entry.name,ColorScheme.getColorIdForGeneralPurposeString(entry.name),entry.sourceInfo);frame.entryID=entry.id;model.addStackFrame(frame);return frame;}var lastStackFrame=rootStackFrame;if(data.stack.length>0&&this.v8ProcessCodeMaps_[event.pid]){var map=this.v8ProcessCodeMaps_[event.pid];data.stack.reverse();for(var i=0;i<data.stack.length;i++){var entry=map.lookupEntry(data.stack[i]);if(entry===undefined){entry={id:'unknown',name:'unknown',sourceInfo:undefined};}lastStackFrame=addStackFrame(lastStackFrame,entry);}}else{var entry={id:data.vm_state,name:data.vm_state,sourceInfo:undefined};lastStackFrame=addStackFrame(lastStackFrame,entry);}var thread=this.model_.getOrCreateProcess(event.pid).getOrCreateThread(event.tid);var sample=new tr.model.Sample(undefined,thread,'V8 Sample',this.toModelTimeFromUs_(event.ts),lastStackFrame,1,this.deepCopyIfNeeded_(event.args));this.model_.samples.push(sample);},processTraceSampleEvent:function(event){if(event.name==='V8Sample'){this.v8SamplingData_.push(event);return;}var stackFrame=this.getStackFrameForEvent_(event);if(stackFrame===undefined){stackFrame=this.traceEventSampleStackFramesByName_[event.name];}if(stackFrame===undefined){var id='te-'+tr.b.GUID.allocateSimple();stackFrame=new tr.model.StackFrame(undefined,id,event.name,ColorScheme.getColorIdForGeneralPurposeString(event.name));this.model_.addStackFrame(stackFrame);this.traceEventSampleStackFramesByName_[event.name]=stackFrame;}var thread=this.model_.getOrCreateProcess(event.pid).getOrCreateThread(event.tid);var sample=new tr.model.Sample(undefined,thread,'Trace Event Sample',this.toModelTimeFromUs_(event.ts),stackFrame,1,this.deepCopyIfNeeded_(event.args));this.setContextsFromThread_(thread,sample);this.model_.samples.push(sample);},processMemoryDumpEvent:function(event){if(event.ph!=='v')throw new Error('Invalid memory dump event phase "'+event.ph+'".');var dumpId=event.id;if(dumpId===undefined){this.model_.importWarning({type:'memory_dump_parse_error',message:'Memory dump event (phase \''+event.ph+'\') without a dump ID.'});return;}var pid=event.pid;if(pid===undefined){this.model_.importWarning({type:'memory_dump_parse_error',message:'Memory dump event (phase\''+event.ph+'\', dump ID \''+dumpId+'\') without a PID.'});return;}var allEvents=this.allMemoryDumpEvents_;var dumpIdEvents=allEvents[dumpId];if(dumpIdEvents===undefined)allEvents[dumpId]=dumpIdEvents={};var processEvents=dumpIdEvents[pid];if(processEvents===undefined)dumpIdEvents[pid]=processEvents=[];processEvents.push(event);},processClockSyncEvent:function(event){if(event.ph!=='c')throw new Error('Invalid clock sync event phase "'+event.ph+'".');var syncId=event.args.sync_id;if(syncId===undefined){this.model_.importWarning({type:'clock_sync_parse_error',message:'Clock sync at time '+event.ts+' without an ID.'});return;}if(event.args&&event.args.issue_ts!==undefined){this.model_.clockSyncManager.addClockSyncMarker(this.clockDomainId_,syncId,tr.b.Unit.timestampFromUs(event.args.issue_ts),tr.b.Unit.timestampFromUs(event.ts));}else{this.model_.clockSyncManager.addClockSyncMarker(this.clockDomainId_,syncId,tr.b.Unit.timestampFromUs(event.ts));}},processV8Events:function(){this.v8SamplingData_.sort(function(a,b){if(a.ts!==b.ts)return a.ts-b.ts;if(a.ph==='M'||a.ph==='I')return-1;else if(b.ph==='M'||b.ph==='I')return 1;return 0;});var length=this.v8SamplingData_.length;for(var i=0;i<length;++i){var event=this.v8SamplingData_[i];if(event.ph==='M'||event.ph==='I'){this.processJitCodeEvent(event);}else if(event.ph==='P'){this.processV8Sample(event);}}},initBackcompatClockSyncEventTracker_:function(event){if(event.name!==undefined&&event.name.startsWith(ASYNC_CLOCK_SYNC_EVENT_TITLE_PREFIX)&&event.ph==='S')this.asyncClockSyncStart_=event;if(event.name!==undefined&&event.name.startsWith(ASYNC_CLOCK_SYNC_EVENT_TITLE_PREFIX)&&event.ph==='F')this.asyncClockSyncFinish_=event;if(this.asyncClockSyncStart_==undefined||this.asyncClockSyncFinish_==undefined)return;var syncId=this.asyncClockSyncStart_.name.substring(ASYNC_CLOCK_SYNC_EVENT_TITLE_PREFIX.length);if(syncId!==this.asyncClockSyncFinish_.name.substring(ASYNC_CLOCK_SYNC_EVENT_TITLE_PREFIX.length)){throw new Error('Inconsistent clock sync id of async clock sync '+'events.');}var clockSyncEvent={ph:'c',args:{sync_id:syncId,issue_ts:this.asyncClockSyncStart_.ts},ts:this.asyncClockSyncFinish_.ts};this.asyncClockSyncStart_=undefined;this.asyncClockSyncFinish_=undefined;return clockSyncEvent;},importClockSyncMarkers:function(){var asyncClockSyncStart,asyncClockSyncFinish;for(var i=0;i<this.events_.length;i++){var event=this.events_[i];var possibleBackCompatClockSyncEvent=this.initBackcompatClockSyncEventTracker_(event);if(possibleBackCompatClockSyncEvent)this.processClockSyncEvent(possibleBackCompatClockSyncEvent);if(event.ph!=='c')continue;var eventSizeInBytes=this.model_.importOptions.trackDetailedModelStats?JSON.stringify(event).length:undefined;this.model_.stats.willProcessBasicTraceEvent('clock_sync',event.cat,event.name,event.ts,eventSizeInBytes);this.processClockSyncEvent(event);}},importEvents:function(){if(this.stackFrameEvents_)this.importStackFrames_(this.stackFrameEvents_,'g');if(this.traceAnnotations_)this.importAnnotations_();var importOptions=this.model_.importOptions;var trackDetailedModelStats=importOptions.trackDetailedModelStats;var modelStats=this.model_.stats;var events=this.events_;for(var eI=0;eI<events.length;eI++){var event=events[eI];if(event.args==='__stripped__'){event.argsStripped=true;event.args=undefined;}var eventSizeInBytes;if(trackDetailedModelStats)eventSizeInBytes=JSON.stringify(event).length;else eventSizeInBytes=undefined;if(event.ph==='B'||event.ph==='E'){modelStats.willProcessBasicTraceEvent('begin_end (non-compact)',event.cat,event.name,event.ts,eventSizeInBytes);this.processDurationEvent(event);}else if(event.ph==='X'){modelStats.willProcessBasicTraceEvent('begin_end (compact)',event.cat,event.name,event.ts,eventSizeInBytes);var slice=this.processCompleteEvent(event);if(slice!==undefined&&event.bind_id!==undefined)this.processFlowEvent(event,slice);}else if(event.ph==='b'||event.ph==='e'||event.ph==='n'||event.ph==='S'||event.ph==='F'||event.ph==='T'||event.ph==='p'){modelStats.willProcessBasicTraceEvent('async',event.cat,event.name,event.ts,eventSizeInBytes);this.processAsyncEvent(event);}else if(event.ph==='I'||event.ph==='i'||event.ph==='R'){modelStats.willProcessBasicTraceEvent('instant',event.cat,event.name,event.ts,eventSizeInBytes);this.processInstantEvent(event);}else if(event.ph==='P'){modelStats.willProcessBasicTraceEvent('samples',event.cat,event.name,event.ts,eventSizeInBytes);this.processTraceSampleEvent(event);}else if(event.ph==='C'){modelStats.willProcessBasicTraceEvent('counters',event.cat,event.name,event.ts,eventSizeInBytes);this.processCounterEvent(event);}else if(event.ph==='M'){modelStats.willProcessBasicTraceEvent('metadata',event.cat,event.name,event.ts,eventSizeInBytes);this.processMetadataEvent(event);}else if(event.ph==='N'||event.ph==='D'||event.ph==='O'){modelStats.willProcessBasicTraceEvent('objects',event.cat,event.name,event.ts,eventSizeInBytes);this.processObjectEvent(event);}else if(event.ph==='s'||event.ph==='t'||event.ph==='f'){modelStats.willProcessBasicTraceEvent('flows',event.cat,event.name,event.ts,eventSizeInBytes);this.processFlowEvent(event);}else if(event.ph==='v'){modelStats.willProcessBasicTraceEvent('memory_dumps',event.cat,event.name,event.ts,eventSizeInBytes);this.processMemoryDumpEvent(event);}else if(event.ph==='('||event.ph===')'){this.processContextEvent(event);}else if(event.ph==='c'){}else{modelStats.willProcessBasicTraceEvent('unknown',event.cat,event.name,event.ts,eventSizeInBytes);this.model_.importWarning({type:'parse_error',message:'Unrecognized event phase: '+event.ph+' ('+event.name+')'});}}this.processV8Events();tr.b.iterItems(this.v8ProcessRootStackFrame_,function(name,frame){frame.removeAllChildren();});},importStackFrames_:function(rawStackFrames,idPrefix){var model=this.model_;for(var id in rawStackFrames){var rawStackFrame=rawStackFrames[id];var fullId=idPrefix+id;var textForColor=rawStackFrame.category?rawStackFrame.category:rawStackFrame.name;var stackFrame=new tr.model.StackFrame(undefined,fullId,rawStackFrame.name,ColorScheme.getColorIdForGeneralPurposeString(textForColor));model.addStackFrame(stackFrame);}for(var id in rawStackFrames){var fullId=idPrefix+id;var stackFrame=model.stackFrames[fullId];if(stackFrame===undefined)throw new Error('Internal error');var rawStackFrame=rawStackFrames[id];var parentId=rawStackFrame.parent;var parentStackFrame;if(parentId===undefined){parentStackFrame=undefined;}else{var parentFullId=idPrefix+parentId;parentStackFrame=model.stackFrames[parentFullId];if(parentStackFrame===undefined){this.model_.importWarning({type:'metadata_parse_error',message:'Missing parent frame with ID '+parentFullId+' for stack frame \''+stackFrame.name+'\' (ID '+fullId+').'});}}stackFrame.parentFrame=parentStackFrame;}},importObjectTypeNameMap_:function(rawObjectTypeNameMap,pid){if(pid in this.objectTypeNameMap_){this.model_.importWarning({type:'metadata_parse_error',message:'Mapping from object type IDs to names provided for pid='+pid+' multiple times.'});return;}var objectTypeNamePrefix=undefined;var objectTypeNameSuffix=undefined;var objectTypeNameMap={};for(var objectTypeId in rawObjectTypeNameMap){var rawObjectTypeName=rawObjectTypeNameMap[objectTypeId];if(objectTypeNamePrefix===undefined){for(var i=0;i<OBJECT_TYPE_NAME_PATTERNS.length;i++){var pattern=OBJECT_TYPE_NAME_PATTERNS[i];if(rawObjectTypeName.startsWith(pattern.prefix)&&rawObjectTypeName.endsWith(pattern.suffix)){objectTypeNamePrefix=pattern.prefix;objectTypeNameSuffix=pattern.suffix;break;}}}if(objectTypeNamePrefix!==undefined&&rawObjectTypeName.startsWith(objectTypeNamePrefix)&&rawObjectTypeName.endsWith(objectTypeNameSuffix)){objectTypeNameMap[objectTypeId]=rawObjectTypeName.substring(objectTypeNamePrefix.length,rawObjectTypeName.length-objectTypeNameSuffix.length);}else{objectTypeNameMap[objectTypeId]=rawObjectTypeName;}}this.objectTypeNameMap_[pid]=objectTypeNameMap;},importAnnotations_:function(){for(var id in this.traceAnnotations_){var annotation=tr.model.Annotation.fromDictIfPossible(this.traceAnnotations_[id]);if(!annotation){this.model_.importWarning({type:'annotation_warning',message:'Unrecognized traceAnnotation typeName \"'+this.traceAnnotations_[id].typeName+'\"'});continue;}this.model_.addAnnotation(annotation);}},finalizeImport:function(){if(this.softwareMeasuredCpuCount_!==undefined){this.model_.kernel.softwareMeasuredCpuCount=this.softwareMeasuredCpuCount_;}this.createAsyncSlices_();this.createFlowSlices_();this.createExplicitObjects_();this.createImplicitObjects_();this.createMemoryDumps_();},getStackFrameForEvent_:function(event,opt_lookForEndEvent){var sf;var stack;if(opt_lookForEndEvent){sf=event.esf;stack=event.estack;}else{sf=event.sf;stack=event.stack;}if(stack!==undefined&&sf!==undefined){this.model_.importWarning({type:'stack_frame_and_stack_error',message:'Event at '+event.ts+' cannot have both a stack and a stackframe.'});return undefined;}if(stack!==undefined)return this.model_.resolveStackToStackFrame_(event.pid,stack);if(sf===undefined)return undefined;var stackFrame=this.model_.stackFrames['g'+sf];if(stackFrame===undefined){this.model_.importWarning({type:'sample_import_error',message:'No frame for '+sf});return;}return stackFrame;},resolveStackToStackFrame_:function(pid,stack){return undefined;},importSampleData:function(){if(!this.sampleEvents_)return;var m=this.model_;var events=this.sampleEvents_;if(this.events_.length===0){for(var i=0;i<events.length;i++){var event=events[i];m.getOrCreateProcess(event.tid).getOrCreateThread(event.tid);}}var threadsByTid={};m.getAllThreads().forEach(function(t){threadsByTid[t.tid]=t;});for(var i=0;i<events.length;i++){var event=events[i];var thread=threadsByTid[event.tid];if(thread===undefined){m.importWarning({type:'sample_import_error',message:'Thread '+events.tid+'not found'});continue;}var cpu;if(event.cpu!==undefined)cpu=m.kernel.getOrCreateCpu(event.cpu);var stackFrame=this.getStackFrameForEvent_(event);var sample=new tr.model.Sample(cpu,thread,event.name,this.toModelTimeFromUs_(event.ts),stackFrame,event.weight);m.samples.push(sample);}},createAsyncSlices_:function(){if(this.allAsyncEvents_.length===0)return;this.allAsyncEvents_.sort(function(x,y){var d=x.event.ts-y.event.ts;if(d!==0)return d;return x.sequenceNumber-y.sequenceNumber;});var legacyEvents=[];var nestableAsyncEventsByKey={};var nestableMeasureAsyncEventsByKey={};for(var i=0;i<this.allAsyncEvents_.length;i++){var asyncEventState=this.allAsyncEvents_[i];var event=asyncEventState.event;if(event.ph==='S'||event.ph==='F'||event.ph==='T'||event.ph==='p'){legacyEvents.push(asyncEventState);continue;}if(event.cat===undefined){this.model_.importWarning({type:'async_slice_parse_error',message:'Nestable async events (ph: b, e, or n) require a '+'cat parameter.'});continue;}if(event.name===undefined){this.model_.importWarning({type:'async_slice_parse_error',message:'Nestable async events (ph: b, e, or n) require a '+'name parameter.'});continue;}if(event.id===undefined){this.model_.importWarning({type:'async_slice_parse_error',message:'Nestable async events (ph: b, e, or n) require an '+'id parameter.'});continue;}if(event.cat==='blink.user_timing'){var matched=/([^\/:]+):([^\/:]+)\/?(.*)/.exec(event.name);if(matched!==null){var key=matched[1]+':'+event.cat;event.args=JSON.parse(Base64.atob(matched[3])||'{}');if(nestableMeasureAsyncEventsByKey[key]===undefined)nestableMeasureAsyncEventsByKey[key]=[];nestableMeasureAsyncEventsByKey[key].push(asyncEventState);continue;}}var key=event.cat+':'+event.id;if(nestableAsyncEventsByKey[key]===undefined)nestableAsyncEventsByKey[key]=[];nestableAsyncEventsByKey[key].push(asyncEventState);}this.createLegacyAsyncSlices_(legacyEvents);this.createNestableAsyncSlices_(nestableMeasureAsyncEventsByKey);this.createNestableAsyncSlices_(nestableAsyncEventsByKey);},createLegacyAsyncSlices_:function(legacyEvents){if(legacyEvents.length===0)return;legacyEvents.sort(function(x,y){var d=x.event.ts-y.event.ts;if(d!=0)return d;return x.sequenceNumber-y.sequenceNumber;});var asyncEventStatesByNameThenID={};for(var i=0;i<legacyEvents.length;i++){var asyncEventState=legacyEvents[i];var event=asyncEventState.event;var name=event.name;if(name===undefined){this.model_.importWarning({type:'async_slice_parse_error',message:'Async events (ph: S, T, p, or F) require a name '+' parameter.'});continue;}var id=event.id;if(id===undefined){this.model_.importWarning({type:'async_slice_parse_error',message:'Async events (ph: S, T, p, or F) require an id parameter.'});continue;}if(event.ph==='S'){if(asyncEventStatesByNameThenID[name]===undefined)asyncEventStatesByNameThenID[name]={};if(asyncEventStatesByNameThenID[name][id]){this.model_.importWarning({type:'async_slice_parse_error',message:'At '+event.ts+', a slice of the same id '+id+' was alrady open.'});continue;}asyncEventStatesByNameThenID[name][id]=[];asyncEventStatesByNameThenID[name][id].push(asyncEventState);}else{if(asyncEventStatesByNameThenID[name]===undefined){this.model_.importWarning({type:'async_slice_parse_error',message:'At '+event.ts+', no slice named '+name+' was open.'});continue;}if(asyncEventStatesByNameThenID[name][id]===undefined){this.model_.importWarning({type:'async_slice_parse_error',message:'At '+event.ts+', no slice named '+name+' with id='+id+' was open.'});continue;}var events=asyncEventStatesByNameThenID[name][id];events.push(asyncEventState);if(event.ph==='F'){var asyncSliceConstructor=tr.model.AsyncSlice.subTypes.getConstructor(events[0].event.cat,name);var slice=new asyncSliceConstructor(events[0].event.cat,name,getEventColor(events[0].event),this.toModelTimeFromUs_(events[0].event.ts),tr.b.concatenateObjects(events[0].event.args,events[events.length-1].event.args),this.toModelTimeFromUs_(event.ts-events[0].event.ts),true,undefined,undefined,events[0].event.argsStripped);slice.startThread=events[0].thread;slice.endThread=asyncEventState.thread;slice.id=id;var stepType=events[1].event.ph;var isValid=true;for(var j=1;j<events.length-1;++j){if(events[j].event.ph==='T'||events[j].event.ph==='p'){isValid=this.assertStepTypeMatches_(stepType,events[j]);if(!isValid)break;}if(events[j].event.ph==='S'){this.model_.importWarning({type:'async_slice_parse_error',message:'At '+event.event.ts+', a slice named '+event.event.name+' with id='+event.event.id+' had a step before the start event.'});continue;}if(events[j].event.ph==='F'){this.model_.importWarning({type:'async_slice_parse_error',message:'At '+event.event.ts+', a slice named '+event.event.name+' with id='+event.event.id+' had a step after the finish event.'});continue;}var startIndex=j+(stepType==='T'?0:-1);var endIndex=startIndex+1;var subName=events[j].event.name;if(!events[j].event.argsStripped&&(events[j].event.ph==='T'||events[j].event.ph==='p'))subName=subName+':'+events[j].event.args.step;var asyncSliceConstructor=tr.model.AsyncSlice.subTypes.getConstructor(events[0].event.cat,subName);var subSlice=new asyncSliceConstructor(events[0].event.cat,subName,getEventColor(event,subName+j),this.toModelTimeFromUs_(events[startIndex].event.ts),this.deepCopyIfNeeded_(events[j].event.args),this.toModelTimeFromUs_(events[endIndex].event.ts-events[startIndex].event.ts),undefined,undefined,events[startIndex].event.argsStripped);subSlice.startThread=events[startIndex].thread;subSlice.endThread=events[endIndex].thread;subSlice.id=id;slice.subSlices.push(subSlice);}if(isValid){slice.startThread.asyncSliceGroup.push(slice);}delete asyncEventStatesByNameThenID[name][id];}}}},createNestableAsyncSlices_:function(nestableEventsByKey){for(var key in nestableEventsByKey){var eventStateEntries=nestableEventsByKey[key];var parentStack=[];for(var i=0;i<eventStateEntries.length;++i){var eventStateEntry=eventStateEntries[i];if(eventStateEntry.event.ph==='e'){var parentIndex=-1;for(var k=parentStack.length-1;k>=0;--k){if(parentStack[k].event.name===eventStateEntry.event.name){parentIndex=k;break;}}if(parentIndex===-1){eventStateEntry.finished=false;}else{parentStack[parentIndex].end=eventStateEntry;while(parentIndex<parentStack.length){parentStack.pop();}}}if(parentStack.length>0)eventStateEntry.parentEntry=parentStack[parentStack.length-1];if(eventStateEntry.event.ph==='b'){parentStack.push(eventStateEntry);}}var topLevelSlices=[];for(var i=0;i<eventStateEntries.length;++i){var eventStateEntry=eventStateEntries[i];if(eventStateEntry.event.ph==='e'&&eventStateEntry.finished===undefined){continue;}var startState=undefined;var endState=undefined;var sliceArgs=eventStateEntry.event.args||{};var sliceError=undefined;if(eventStateEntry.event.ph==='n'){startState=eventStateEntry;endState=eventStateEntry;}else if(eventStateEntry.event.ph==='b'){if(eventStateEntry.end===undefined){eventStateEntry.end=eventStateEntries[eventStateEntries.length-1];sliceError='Slice has no matching END. End time has been adjusted.';this.model_.importWarning({type:'async_slice_parse_error',message:'Nestable async BEGIN event at '+eventStateEntry.event.ts+' with name='+eventStateEntry.event.name+' and id='+eventStateEntry.event.id+' was unmatched.'});}else{function concatenateArguments(args1,args2){if(args1.params===undefined||args2.params===undefined)return tr.b.concatenateObjects(args1,args2);var args3={};args3.params=tr.b.concatenateObjects(args1.params,args2.params);return tr.b.concatenateObjects(args1,args2,args3);}var endArgs=eventStateEntry.end.event.args||{};sliceArgs=concatenateArguments(sliceArgs,endArgs);}startState=eventStateEntry;endState=eventStateEntry.end;}else{sliceError='Slice has no matching BEGIN. Start time has been adjusted.';this.model_.importWarning({type:'async_slice_parse_error',message:'Nestable async END event at '+eventStateEntry.event.ts+' with name='+eventStateEntry.event.name+' and id='+eventStateEntry.event.id+' was unmatched.'});startState=eventStateEntries[0];endState=eventStateEntry;}var isTopLevel=eventStateEntry.parentEntry===undefined;var asyncSliceConstructor=tr.model.AsyncSlice.subTypes.getConstructor(eventStateEntry.event.cat,eventStateEntry.event.name);var threadStart=undefined;var threadDuration=undefined;if(startState.event.tts&&startState.event.use_async_tts){threadStart=this.toModelTimeFromUs_(startState.event.tts);if(endState.event.tts){var threadEnd=this.toModelTimeFromUs_(endState.event.tts);threadDuration=threadEnd-threadStart;}}var slice=new asyncSliceConstructor(eventStateEntry.event.cat,eventStateEntry.event.name,getEventColor(endState.event),this.toModelTimeFromUs_(startState.event.ts),sliceArgs,this.toModelTimeFromUs_(endState.event.ts-startState.event.ts),isTopLevel,threadStart,threadDuration,startState.event.argsStripped);slice.startThread=startState.thread;slice.endThread=endState.thread;slice.startStackFrame=this.getStackFrameForEvent_(startState.event);slice.endStackFrame=this.getStackFrameForEvent_(endState.event);slice.id=key;if(sliceError!==undefined)slice.error=sliceError;eventStateEntry.slice=slice;if(isTopLevel){topLevelSlices.push(slice);}else if(eventStateEntry.parentEntry.slice!==undefined){eventStateEntry.parentEntry.slice.subSlices.push(slice);}}for(var si=0;si<topLevelSlices.length;si++){topLevelSlices[si].startThread.asyncSliceGroup.push(topLevelSlices[si]);}}},assertStepTypeMatches_:function(stepType,event){if(stepType!=event.event.ph){this.model_.importWarning({type:'async_slice_parse_error',message:'At '+event.event.ts+', a slice named '+event.event.name+' with id='+event.event.id+' had both begin and end steps, which is not allowed.'});return false;}return true;},createFlowSlices_:function(){if(this.allFlowEvents_.length===0)return;var that=this;function validateFlowEvent(){if(event.name===undefined){that.model_.importWarning({type:'flow_slice_parse_error',message:'Flow events (ph: s, t or f) require a name parameter.'});return false;}if(event.ph==='s'||event.ph==='f'||event.ph==='t'){if(event.id===undefined){that.model_.importWarning({type:'flow_slice_parse_error',message:'Flow events (ph: s, t or f) require an id parameter.'});return false;}return true;}if(event.bind_id){if(event.flow_in===undefined&&event.flow_out===undefined){that.model_.importWarning({type:'flow_slice_parse_error',message:'Flow producer or consumer require flow_in or flow_out.'});return false;}return true;}return false;}var createFlowEvent=function(thread,event,opt_slice){var startSlice,flowId,flowStartTs;if(event.bind_id){startSlice=opt_slice;flowId=event.bind_id;flowStartTs=this.toModelTimeFromUs_(event.ts+event.dur);}else{var ts=this.toModelTimeFromUs_(event.ts);startSlice=thread.sliceGroup.findSliceAtTs(ts);if(startSlice===undefined)return undefined;flowId=event.id;flowStartTs=ts;}var flowEvent=new tr.model.FlowEvent(event.cat,flowId,event.name,getEventColor(event),flowStartTs,that.deepCopyAlways_(event.args));flowEvent.startSlice=startSlice;flowEvent.startStackFrame=that.getStackFrameForEvent_(event);flowEvent.endStackFrame=undefined;startSlice.outFlowEvents.push(flowEvent);return flowEvent;}.bind(this);var finishFlowEventWith=function(flowEvent,thread,event,refGuid,bindToParent,opt_slice){var endSlice;if(event.bind_id){endSlice=opt_slice;}else{var ts=this.toModelTimeFromUs_(event.ts);if(bindToParent){endSlice=thread.sliceGroup.findSliceAtTs(ts);}else{endSlice=thread.sliceGroup.findNextSliceAfter(ts,refGuid);}if(endSlice===undefined)return false;}endSlice.inFlowEvents.push(flowEvent);flowEvent.endSlice=endSlice;flowEvent.duration=this.toModelTimeFromUs_(event.ts)-flowEvent.start;flowEvent.endStackFrame=that.getStackFrameForEvent_(event);that.mergeArgsInto_(flowEvent.args,event.args,flowEvent.title);return true;}.bind(this);function processFlowConsumer(flowIdToEvent,sliceGuidToEvent,event,slice){var flowEvent=flowIdToEvent[event.bind_id];if(flowEvent===undefined){that.model_.importWarning({type:'flow_slice_ordering_error',message:'Flow consumer '+event.bind_id+' does not have '+'a flow producer'});return false;}else if(flowEvent.endSlice){var flowProducer=flowEvent.startSlice;flowEvent=createFlowEvent(undefined,sliceGuidToEvent[flowProducer.guid],flowProducer);}var ok=finishFlowEventWith(flowEvent,undefined,event,refGuid,undefined,slice);if(ok){that.model_.flowEvents.push(flowEvent);}else{that.model_.importWarning({type:'flow_slice_end_error',message:'Flow consumer '+event.bind_id+' does not end '+'at an actual slice, so cannot be created.'});return false;}return true;}function processFlowProducer(flowIdToEvent,flowStatus,event,slice){if(flowIdToEvent[event.bind_id]&&flowStatus[event.bind_id]){that.model_.importWarning({type:'flow_slice_start_error',message:'Flow producer '+event.bind_id+' already seen'});return false;}var flowEvent=createFlowEvent(undefined,event,slice);if(!flowEvent){that.model_.importWarning({type:'flow_slice_start_error',message:'Flow producer '+event.bind_id+' does not start'+'a flow'});return false;}flowIdToEvent[event.bind_id]=flowEvent;}this.allFlowEvents_.sort(function(x,y){var d=x.event.ts-y.event.ts;if(d!=0)return d;return x.sequenceNumber-y.sequenceNumber;});var flowIdToEvent={};var sliceGuidToEvent={};var flowStatus={};for(var i=0;i<this.allFlowEvents_.length;++i){var data=this.allFlowEvents_[i];var refGuid=data.refGuid;var event=data.event;var thread=data.thread;if(!validateFlowEvent(event))continue;if(event.bind_id){var slice=data.slice;sliceGuidToEvent[slice.guid]=event;if(event.flowPhase===PRODUCER){if(!processFlowProducer(flowIdToEvent,flowStatus,event,slice))continue;flowStatus[event.bind_id]=true;}else{if(!processFlowConsumer(flowIdToEvent,sliceGuidToEvent,event,slice))continue;flowStatus[event.bind_id]=false;if(event.flowPhase===STEP){if(!processFlowProducer(flowIdToEvent,flowStatus,event,slice))continue;flowStatus[event.bind_id]=true;}}continue;}var flowEvent;if(event.ph==='s'){if(flowIdToEvent[event.id]){this.model_.importWarning({type:'flow_slice_start_error',message:'event id '+event.id+' already seen when '+'encountering start of flow event.'});continue;}flowEvent=createFlowEvent(thread,event);if(!flowEvent){this.model_.importWarning({type:'flow_slice_start_error',message:'event id '+event.id+' does not start '+'at an actual slice, so cannot be created.'});continue;}flowIdToEvent[event.id]=flowEvent;}else if(event.ph==='t'||event.ph==='f'){flowEvent=flowIdToEvent[event.id];if(flowEvent===undefined){this.model_.importWarning({type:'flow_slice_ordering_error',message:'Found flow phase '+event.ph+' for id: '+event.id+' but no flow start found.'});continue;}var bindToParent=event.ph==='t';if(event.ph==='f'){if(event.bp===undefined){if(event.cat.indexOf('input')>-1)bindToParent=true;else if(event.cat.indexOf('ipc.flow')>-1)bindToParent=true;}else{if(event.bp!=='e'){this.model_.importWarning({type:'flow_slice_bind_point_error',message:'Flow event with invalid binding point (event.bp).'});continue;}bindToParent=true;}}var ok=finishFlowEventWith(flowEvent,thread,event,refGuid,bindToParent);if(ok){that.model_.flowEvents.push(flowEvent);}else{this.model_.importWarning({type:'flow_slice_end_error',message:'event id '+event.id+' does not end '+'at an actual slice, so cannot be created.'});}flowIdToEvent[event.id]=undefined;if(ok&&event.ph==='t'){flowEvent=createFlowEvent(thread,event);flowIdToEvent[event.id]=flowEvent;}}}},createExplicitObjects_:function(){if(this.allObjectEvents_.length===0)return;var processEvent=function(objectEventState){var event=objectEventState.event;var scopedId=this.scopedIdForEvent_(event);var thread=objectEventState.thread;if(event.name===undefined){this.model_.importWarning({type:'object_parse_error',message:'While processing '+JSON.stringify(event)+': '+'Object events require an name parameter.'});}if(scopedId.id===undefined){this.model_.importWarning({type:'object_parse_error',message:'While processing '+JSON.stringify(event)+': '+'Object events require an id parameter.'});}var process=thread.parent;var ts=this.toModelTimeFromUs_(event.ts);var instance;if(event.ph==='N'){try{instance=process.objects.idWasCreated(scopedId,event.cat,event.name,ts);}catch(e){this.model_.importWarning({type:'object_parse_error',message:'While processing create of '+scopedId+' at ts='+ts+': '+e});return;}}else if(event.ph==='O'){if(event.args.snapshot===undefined){this.model_.importWarning({type:'object_parse_error',message:'While processing '+scopedId+' at ts='+ts+': '+'Snapshots must have args: {snapshot: ...}'});return;}var snapshot;try{var args=this.deepCopyIfNeeded_(event.args.snapshot);var cat;if(args.cat){cat=args.cat;delete args.cat;}else{cat=event.cat;}var baseTypename;if(args.base_type){baseTypename=args.base_type;delete args.base_type;}else{baseTypename=undefined;}snapshot=process.objects.addSnapshot(scopedId,cat,event.name,ts,args,baseTypename);snapshot.snapshottedOnThread=thread;}catch(e){this.model_.importWarning({type:'object_parse_error',message:'While processing snapshot of '+scopedId+' at ts='+ts+': '+e});return;}instance=snapshot.objectInstance;}else if(event.ph==='D'){try{process.objects.idWasDeleted(scopedId,event.cat,event.name,ts);var instanceMap=process.objects.getOrCreateInstanceMap_(scopedId);instance=instanceMap.lastInstance;}catch(e){this.model_.importWarning({type:'object_parse_error',message:'While processing delete of '+scopedId+' at ts='+ts+': '+e});return;}}if(instance)instance.colorId=getEventColor(event,instance.typeName);}.bind(this);this.allObjectEvents_.sort(function(x,y){var d=x.event.ts-y.event.ts;if(d!=0)return d;return x.sequenceNumber-y.sequenceNumber;});var allObjectEvents=this.allObjectEvents_;for(var i=0;i<allObjectEvents.length;i++){var objectEventState=allObjectEvents[i];try{processEvent.call(this,objectEventState);}catch(e){this.model_.importWarning({type:'object_parse_error',message:e.message});}}},createImplicitObjects_:function(){tr.b.iterItems(this.model_.processes,function(pid,process){this.createImplicitObjectsForProcess_(process);},this);},createImplicitObjectsForProcess_:function(process){function processField(referencingObject,referencingObjectFieldName,referencingObjectFieldValue,containingSnapshot){if(!referencingObjectFieldValue)return;if(referencingObjectFieldValue instanceof tr.model.ObjectSnapshot)return null;if(referencingObjectFieldValue.id===undefined)return;var implicitSnapshot=referencingObjectFieldValue;var rawId=implicitSnapshot.id;var m=/(.+)\/(.+)/.exec(rawId);if(!m)throw new Error('Implicit snapshots must have names.');delete implicitSnapshot.id;var name=m[1];var id=m[2];var res;var cat;if(implicitSnapshot.cat!==undefined)cat=implicitSnapshot.cat;else cat=containingSnapshot.objectInstance.category;var baseTypename;if(implicitSnapshot.base_type)baseTypename=implicitSnapshot.base_type;else baseTypename=undefined;var scope=containingSnapshot.objectInstance.scopedId.scope;try{res=process.objects.addSnapshot(new tr.model.ScopedId(scope,id),cat,name,containingSnapshot.ts,implicitSnapshot,baseTypename);}catch(e){this.model_.importWarning({type:'object_snapshot_parse_error',message:'While processing implicit snapshot of '+rawId+' at ts='+containingSnapshot.ts+': '+e});return;}res.objectInstance.hasImplicitSnapshots=true;res.containingSnapshot=containingSnapshot;res.snapshottedOnThread=containingSnapshot.snapshottedOnThread;referencingObject[referencingObjectFieldName]=res;if(!(res instanceof tr.model.ObjectSnapshot))throw new Error('Created object must be instanceof snapshot');return res.args;}function iterObject(object,func,containingSnapshot,thisArg){if(!(object instanceof Object))return;if(object instanceof Array){for(var i=0;i<object.length;i++){var res=func.call(thisArg,object,i,object[i],containingSnapshot);if(res===null)continue;if(res)iterObject(res,func,containingSnapshot,thisArg);else iterObject(object[i],func,containingSnapshot,thisArg);}return;}for(var key in object){var res=func.call(thisArg,object,key,object[key],containingSnapshot);if(res===null)continue;if(res)iterObject(res,func,containingSnapshot,thisArg);else iterObject(object[key],func,containingSnapshot,thisArg);}}process.objects.iterObjectInstances(function(instance){instance.snapshots.forEach(function(snapshot){if(snapshot.args.id!==undefined)throw new Error('args cannot have an id field inside it');iterObject(snapshot.args,processField,snapshot,this);},this);},this);},createMemoryDumps_:function(){for(var dumpId in this.allMemoryDumpEvents_)this.createGlobalMemoryDump_(this.allMemoryDumpEvents_[dumpId],dumpId);},createGlobalMemoryDump_:function(dumpIdEvents,dumpId){var globalRange=new tr.b.Range();for(var pid in dumpIdEvents){var processEvents=dumpIdEvents[pid];for(var i=0;i<processEvents.length;i++)globalRange.addValue(this.toModelTimeFromUs_(processEvents[i].ts));}if(globalRange.isEmpty)throw new Error('Internal error: Global memory dump without events');var globalMemoryDump=new tr.model.GlobalMemoryDump(this.model_,globalRange.min);globalMemoryDump.duration=globalRange.range;this.model_.globalMemoryDumps.push(globalMemoryDump);var globalMemoryAllocatorDumpsByFullName={};var levelsOfDetail={};var allMemoryAllocatorDumpsByGuid={};for(var pid in dumpIdEvents){this.createProcessMemoryDump_(globalMemoryDump,globalMemoryAllocatorDumpsByFullName,levelsOfDetail,allMemoryAllocatorDumpsByGuid,dumpIdEvents[pid],pid,dumpId);}globalMemoryDump.levelOfDetail=levelsOfDetail.global;globalMemoryDump.memoryAllocatorDumps=this.inferMemoryAllocatorDumpTree_(globalMemoryAllocatorDumpsByFullName);this.parseMemoryDumpAllocatorEdges_(allMemoryAllocatorDumpsByGuid,dumpIdEvents,dumpId);},createProcessMemoryDump_:function(globalMemoryDump,globalMemoryAllocatorDumpsByFullName,levelsOfDetail,allMemoryAllocatorDumpsByGuid,processEvents,pid,dumpId){var processRange=new tr.b.Range();for(var i=0;i<processEvents.length;i++)processRange.addValue(this.toModelTimeFromUs_(processEvents[i].ts));if(processRange.isEmpty)throw new Error('Internal error: Process memory dump without events');var process=this.model_.getOrCreateProcess(pid);var processMemoryDump=new tr.model.ProcessMemoryDump(globalMemoryDump,process,processRange.min);processMemoryDump.duration=processRange.range;process.memoryDumps.push(processMemoryDump);globalMemoryDump.processMemoryDumps[pid]=processMemoryDump;var processMemoryAllocatorDumpsByFullName={};for(var i=0;i<processEvents.length;i++){var processEvent=processEvents[i];var dumps=processEvent.args.dumps;if(dumps===undefined){this.model_.importWarning({type:'memory_dump_parse_error',message:'\'dumps\' field not found in a process memory dump'+' event for PID='+pid+' and dump ID='+dumpId+'.'});continue;}this.parseMemoryDumpTotals_(processMemoryDump,dumps,pid,dumpId);this.parseMemoryDumpVmRegions_(processMemoryDump,dumps,pid,dumpId);this.parseMemoryDumpHeapDumps_(processMemoryDump,dumps,pid,dumpId);this.parseMemoryDumpLevelOfDetail_(levelsOfDetail,dumps,pid,dumpId);this.parseMemoryDumpAllocatorDumps_(processMemoryDump,globalMemoryDump,processMemoryAllocatorDumpsByFullName,globalMemoryAllocatorDumpsByFullName,allMemoryAllocatorDumpsByGuid,dumps,pid,dumpId);}if(levelsOfDetail.process===undefined){levelsOfDetail.process=processMemoryDump.vmRegions?DETAILED:LIGHT;}if(!this.updateMemoryDumpLevelOfDetail_(levelsOfDetail,'global',levelsOfDetail.process)){this.model_.importWarning({type:'memory_dump_parse_error',message:'diffent levels of detail provided for global memory'+' dump (dump ID='+dumpId+').'});}processMemoryDump.levelOfDetail=levelsOfDetail.process;delete levelsOfDetail.process;processMemoryDump.memoryAllocatorDumps=this.inferMemoryAllocatorDumpTree_(processMemoryAllocatorDumpsByFullName);},parseMemoryDumpTotals_:function(processMemoryDump,dumps,pid,dumpId){var rawTotals=dumps.process_totals;if(rawTotals===undefined)return;if(processMemoryDump.totals!==undefined){this.model_.importWarning({type:'memory_dump_parse_error',message:'Process totals provided multiple times for'+' process memory dump for PID='+pid+' and dump ID='+dumpId+'.'});return;}var totals={};var platformSpecificTotals=undefined;for(var rawTotalName in rawTotals){var rawTotalValue=rawTotals[rawTotalName];if(rawTotalValue===undefined)continue;if(rawTotalName==='resident_set_bytes'){totals.residentBytes=parseInt(rawTotalValue,16);continue;}if(rawTotalName==='peak_resident_set_bytes'){totals.peakResidentBytes=parseInt(rawTotalValue,16);continue;}if(rawTotalName==='is_peak_rss_resetable'){totals.arePeakResidentBytesResettable=!!rawTotalValue;continue;}if(platformSpecificTotals===undefined){platformSpecificTotals={};totals.platformSpecific=platformSpecificTotals;}platformSpecificTotals[rawTotalName]=parseInt(rawTotalValue,16);}if(totals.peakResidentBytes===undefined&&totals.arePeakResidentBytesResettable!==undefined){this.model_.importWarning({type:'memory_dump_parse_error',message:'Optional field peak_resident_set_bytes found'+' but is_peak_rss_resetable not found in'+' process memory dump for PID='+pid+' and dump ID='+dumpId+'.'});}if(totals.arePeakResidentBytesResettable!==undefined&&totals.peakResidentBytes===undefined){this.model_.importWarning({type:'memory_dump_parse_error',message:'Optional field is_peak_rss_resetable found'+' but peak_resident_set_bytes not found in'+' process memory dump for PID='+pid+' and dump ID='+dumpId+'.'});}processMemoryDump.totals=totals;},parseMemoryDumpVmRegions_:function(processMemoryDump,dumps,pid,dumpId){var rawProcessMmaps=dumps.process_mmaps;if(rawProcessMmaps===undefined)return;var rawVmRegions=rawProcessMmaps.vm_regions;if(rawVmRegions===undefined)return;if(processMemoryDump.vmRegions!==undefined){this.model_.importWarning({type:'memory_dump_parse_error',message:'VM regions provided multiple times for'+' process memory dump for PID='+pid+' and dump ID='+dumpId+'.'});return;}var vmRegions=new Array(rawVmRegions.length);for(var i=0;i<rawVmRegions.length;i++){var rawVmRegion=rawVmRegions[i];var byteStats={};var rawByteStats=rawVmRegion.bs;for(var rawByteStatName in rawByteStats){var rawByteStatValue=rawByteStats[rawByteStatName];if(rawByteStatValue===undefined){this.model_.importWarning({type:'memory_dump_parse_error',message:'Byte stat \''+rawByteStatName+'\' of VM region '+i+' ('+rawVmRegion.mf+') in process memory dump for '+'PID='+pid+' and dump ID='+dumpId+' does not have a value.'});continue;}var byteStatName=BYTE_STAT_NAME_MAP[rawByteStatName];if(byteStatName===undefined){this.model_.importWarning({type:'memory_dump_parse_error',message:'Unknown byte stat name \''+rawByteStatName+'\' ('+rawByteStatValue+') of VM region '+i+' ('+rawVmRegion.mf+') in process memory dump for PID='+pid+' and dump ID='+dumpId+'.'});continue;}byteStats[byteStatName]=parseInt(rawByteStatValue,16);}vmRegions[i]=new tr.model.VMRegion(parseInt(rawVmRegion.sa,16),parseInt(rawVmRegion.sz,16),rawVmRegion.pf,rawVmRegion.mf,byteStats);}processMemoryDump.vmRegions=tr.model.VMRegionClassificationNode.fromRegions(vmRegions);},parseMemoryDumpHeapDumps_:function(processMemoryDump,dumps,pid,dumpId){var rawHeapDumps=dumps.heaps;if(rawHeapDumps===undefined)return;if(processMemoryDump.heapDumps!==undefined){this.model_.importWarning({type:'memory_dump_parse_error',message:'Heap dumps provided multiple times for'+' process memory dump for PID='+pid+' and dump ID='+dumpId+'.'});return;}var model=this.model_;var idPrefix='p'+pid+':';var heapDumps={};var objectTypeNameMap=this.objectTypeNameMap_[pid];if(objectTypeNameMap===undefined){this.model_.importWarning({type:'memory_dump_parse_error',message:'Missing mapping from object type IDs to names.'});}for(var allocatorName in rawHeapDumps){var entries=rawHeapDumps[allocatorName].entries;if(entries===undefined||entries.length===0){this.model_.importWarning({type:'memory_dump_parse_error',message:'No heap entries in a '+allocatorName+' heap dump for PID='+pid+' and dump ID='+dumpId+'.'});continue;}var isOldFormat=entries[0].bt===undefined;if(!isOldFormat&&objectTypeNameMap===undefined){continue;}var heapDump=new tr.model.HeapDump(processMemoryDump,allocatorName);for(var i=0;i<entries.length;i++){var entry=entries[i];var leafStackFrameIndex=entry.bt;var leafStackFrame;if(isOldFormat){if(leafStackFrameIndex===undefined){leafStackFrame=undefined;}else{var leafStackFrameId=idPrefix+leafStackFrameIndex;if(leafStackFrameIndex===''){leafStackFrame=undefined;}else{leafStackFrame=model.stackFrames[leafStackFrameId];if(leafStackFrame===undefined){this.model_.importWarning({type:'memory_dump_parse_error',message:'Missing leaf stack frame (ID '+leafStackFrameId+') of heap entry '+i+' (size '+size+') in a '+allocatorName+' heap dump for PID='+pid+'.'});continue;}}leafStackFrameId+=':self';if(model.stackFrames[leafStackFrameId]!==undefined){leafStackFrame=model.stackFrames[leafStackFrameId];}else{leafStackFrame=new tr.model.StackFrame(leafStackFrame,leafStackFrameId,'<self>',undefined);model.addStackFrame(leafStackFrame);}}}else{if(leafStackFrameIndex===undefined){this.model_.importWarning({type:'memory_dump_parse_error',message:'Missing stack frame ID of heap entry '+i+' (size '+size+') in a '+allocatorName+' heap dump for PID='+pid+'.'});continue;}var leafStackFrameId=idPrefix+leafStackFrameIndex;if(leafStackFrameIndex===''){leafStackFrame=undefined;}else{leafStackFrame=model.stackFrames[leafStackFrameId];if(leafStackFrame===undefined){this.model_.importWarning({type:'memory_dump_parse_error',message:'Missing leaf stack frame (ID '+leafStackFrameId+') of heap entry '+i+' (size '+size+') in a '+allocatorName+' heap dump for PID='+pid+'.'});continue;}}}var objectTypeId=entry.type;var objectTypeName;if(objectTypeId===undefined){objectTypeName=undefined;}else if(objectTypeNameMap===undefined){continue;}else{objectTypeName=objectTypeNameMap[objectTypeId];if(objectTypeName===undefined){this.model_.importWarning({type:'memory_dump_parse_error',message:'Missing object type name (ID '+objectTypeId+') of heap entry '+i+' (size '+size+') in a '+allocatorName+' heap dump for pid='+pid+'.'});continue;}}var size=parseInt(entry.size,16);var count=entry.count===undefined?undefined:parseInt(entry.count,16);heapDump.addEntry(leafStackFrame,objectTypeName,size,count);}if(heapDump.entries.length>0)heapDumps[allocatorName]=heapDump;}if(Object.keys(heapDumps).length>0)processMemoryDump.heapDumps=heapDumps;},parseMemoryDumpLevelOfDetail_:function(levelsOfDetail,dumps,pid,dumpId){var rawLevelOfDetail=dumps.level_of_detail;var level;switch(rawLevelOfDetail){case'background':level=BACKGROUND;break;case'light':level=LIGHT;break;case'detailed':level=DETAILED;break;case undefined:level=undefined;break;default:this.model_.importWarning({type:'memory_dump_parse_error',message:'unknown raw level of detail \''+rawLevelOfDetail+'\' of process memory dump for PID='+pid+' and dump ID='+dumpId+'.'});return;}if(!this.updateMemoryDumpLevelOfDetail_(levelsOfDetail,'process',level)){this.model_.importWarning({type:'memory_dump_parse_error',message:'diffent levels of detail provided for process memory'+' dump for PID='+pid+' (dump ID='+dumpId+').'});}},updateMemoryDumpLevelOfDetail_:function(levelsOfDetail,scope,level){if(!(scope in levelsOfDetail)||level===levelsOfDetail[scope]){levelsOfDetail[scope]=level;return true;}if(MEMORY_DUMP_LEVEL_OF_DETAIL_ORDER.indexOf(level)>MEMORY_DUMP_LEVEL_OF_DETAIL_ORDER.indexOf(levelsOfDetail[scope])){levelsOfDetail[scope]=level;}return false;},parseMemoryDumpAllocatorDumps_:function(processMemoryDump,globalMemoryDump,processMemoryAllocatorDumpsByFullName,globalMemoryAllocatorDumpsByFullName,allMemoryAllocatorDumpsByGuid,dumps,pid,dumpId){var rawAllocatorDumps=dumps.allocators;if(rawAllocatorDumps===undefined)return;for(var fullName in rawAllocatorDumps){var rawAllocatorDump=rawAllocatorDumps[fullName];var guid=rawAllocatorDump.guid;if(guid===undefined){this.model_.importWarning({type:'memory_dump_parse_error',message:'Memory allocator dump '+fullName+' for PID='+pid+' and dump ID='+dumpId+' does not have a GUID.'});}var flags=rawAllocatorDump.flags||0;var isWeakDump=!!(flags&WEAK_MEMORY_ALLOCATOR_DUMP_FLAG);var containerMemoryDump;var dstIndex;if(fullName.startsWith(GLOBAL_MEMORY_ALLOCATOR_DUMP_PREFIX)){fullName=fullName.substring(GLOBAL_MEMORY_ALLOCATOR_DUMP_PREFIX.length);containerMemoryDump=globalMemoryDump;dstIndex=globalMemoryAllocatorDumpsByFullName;}else{containerMemoryDump=processMemoryDump;dstIndex=processMemoryAllocatorDumpsByFullName;}var allocatorDump=allMemoryAllocatorDumpsByGuid[guid];if(allocatorDump===undefined){if(fullName in dstIndex){this.model_.importWarning({type:'memory_dump_parse_error',message:'Multiple GUIDs provided for'+' memory allocator dump '+fullName+': '+dstIndex[fullName].guid+', '+guid+' (ignored) for'+' PID='+pid+' and dump ID='+dumpId+'.'});continue;}allocatorDump=new tr.model.MemoryAllocatorDump(containerMemoryDump,fullName,guid);allocatorDump.weak=isWeakDump;dstIndex[fullName]=allocatorDump;if(guid!==undefined)allMemoryAllocatorDumpsByGuid[guid]=allocatorDump;}else{if(allocatorDump.containerMemoryDump!==containerMemoryDump){this.model_.importWarning({type:'memory_dump_parse_error',message:'Memory allocator dump '+fullName+' (GUID='+guid+') for PID='+pid+' and dump ID='+dumpId+' dumped in different contexts.'});continue;}if(allocatorDump.fullName!==fullName){this.model_.importWarning({type:'memory_dump_parse_error',message:'Memory allocator dump with GUID='+guid+' for PID='+pid+' and dump ID='+dumpId+' has multiple names: '+allocatorDump.fullName+', '+fullName+' (ignored).'});continue;}if(!isWeakDump){allocatorDump.weak=false;}}var attributes=rawAllocatorDump.attrs;if(attributes===undefined){this.model_.importWarning({type:'memory_dump_parse_error',message:'Memory allocator dump '+fullName+' (GUID='+guid+') for PID='+pid+' and dump ID='+dumpId+' does not have attributes.'});attributes={};}for(var attrName in attributes){var attrArgs=attributes[attrName];var attrType=attrArgs.type;var attrValue=attrArgs.value;switch(attrType){case'scalar':if(attrName in allocatorDump.numerics){this.model_.importWarning({type:'memory_dump_parse_error',message:'Multiple values provided for scalar attribute '+attrName+' of memory allocator dump '+fullName+' (GUID='+guid+') for PID='+pid+' and dump ID='+dumpId+'.'});break;}var unit=attrArgs.units==='bytes'?tr.b.Unit.byName.sizeInBytes_smallerIsBetter:tr.b.Unit.byName.unitlessNumber_smallerIsBetter;var value=parseInt(attrValue,16);allocatorDump.addNumeric(attrName,new tr.v.ScalarNumeric(unit,value));break;case'string':if(attrName in allocatorDump.diagnostics){this.model_.importWarning({type:'memory_dump_parse_error',message:'Multiple values provided for string attribute '+attrName+' of memory allocator dump '+fullName+' (GUID='+guid+') for PID='+pid+' and dump ID='+dumpId+'.'});break;}allocatorDump.addDiagnostic(attrName,attrValue);break;default:this.model_.importWarning({type:'memory_dump_parse_error',message:'Unknown type provided for attribute '+attrName+' of memory allocator dump '+fullName+' (GUID='+guid+') for PID='+pid+' and dump ID='+dumpId+': '+attrType});break;}}}},inferMemoryAllocatorDumpTree_:function(memoryAllocatorDumpsByFullName){var rootAllocatorDumps=[];var fullNames=Object.keys(memoryAllocatorDumpsByFullName);fullNames.sort();for(var i=0;i<fullNames.length;i++){var fullName=fullNames[i];var allocatorDump=memoryAllocatorDumpsByFullName[fullName];while(true){var lastSlashIndex=fullName.lastIndexOf('/');if(lastSlashIndex===-1){rootAllocatorDumps.push(allocatorDump);break;}var parentFullName=fullName.substring(0,lastSlashIndex);var parentAllocatorDump=memoryAllocatorDumpsByFullName[parentFullName];var parentAlreadyExisted=true;if(parentAllocatorDump===undefined){parentAlreadyExisted=false;parentAllocatorDump=new tr.model.MemoryAllocatorDump(allocatorDump.containerMemoryDump,parentFullName);if(allocatorDump.weak!==false){parentAllocatorDump.weak=undefined;}memoryAllocatorDumpsByFullName[parentFullName]=parentAllocatorDump;}allocatorDump.parent=parentAllocatorDump;parentAllocatorDump.children.push(allocatorDump);if(parentAlreadyExisted){if(!allocatorDump.weak){while(parentAllocatorDump!==undefined&&parentAllocatorDump.weak===undefined){parentAllocatorDump.weak=false;parentAllocatorDump=parentAllocatorDump.parent;}}break;}fullName=parentFullName;allocatorDump=parentAllocatorDump;}}for(var fullName in memoryAllocatorDumpsByFullName){var allocatorDump=memoryAllocatorDumpsByFullName[fullName];if(allocatorDump.weak===undefined)allocatorDump.weak=true;}return rootAllocatorDumps;},parseMemoryDumpAllocatorEdges_:function(allMemoryAllocatorDumpsByGuid,dumpIdEvents,dumpId){for(var pid in dumpIdEvents){var processEvents=dumpIdEvents[pid];for(var i=0;i<processEvents.length;i++){var processEvent=processEvents[i];var dumps=processEvent.args.dumps;if(dumps===undefined)continue;var rawEdges=dumps.allocators_graph;if(rawEdges===undefined)continue;for(var j=0;j<rawEdges.length;j++){var rawEdge=rawEdges[j];var sourceGuid=rawEdge.source;var sourceDump=allMemoryAllocatorDumpsByGuid[sourceGuid];if(sourceDump===undefined){this.model_.importWarning({type:'memory_dump_parse_error',message:'Edge for PID='+pid+' and dump ID='+dumpId+' is missing source memory allocator dump (GUID='+sourceGuid+').'});continue;}var targetGuid=rawEdge.target;var targetDump=allMemoryAllocatorDumpsByGuid[targetGuid];if(targetDump===undefined){this.model_.importWarning({type:'memory_dump_parse_error',message:'Edge for PID='+pid+' and dump ID='+dumpId+' is missing target memory allocator dump (GUID='+targetGuid+').'});continue;}var importance=rawEdge.importance;var edge=new tr.model.MemoryAllocatorDumpLink(sourceDump,targetDump,importance);switch(rawEdge.type){case'ownership':if(sourceDump.owns!==undefined){this.model_.importWarning({type:'memory_dump_parse_error',message:'Memory allocator dump '+sourceDump.fullName+' (GUID='+sourceGuid+') already owns a memory'+' allocator dump ('+sourceDump.owns.target.fullName+').'});}else{sourceDump.owns=edge;targetDump.ownedBy.push(edge);}break;case'retention':sourceDump.retains.push(edge);targetDump.retainedBy.push(edge);break;default:this.model_.importWarning({type:'memory_dump_parse_error',message:'Invalid edge type: '+rawEdge.type+' (PID='+pid+', dump ID='+dumpId+', source='+sourceGuid+', target='+targetGuid+', importance='+importance+').'});}}}}},toModelTimeFromUs_:function(ts){if(!this.toModelTime_){this.toModelTime_=this.model_.clockSyncManager.getModelTimeTransformer(this.clockDomainId_);}return this.toModelTime_(tr.b.Unit.timestampFromUs(ts));},maybeToModelTimeFromUs_:function(ts){if(ts===undefined)return undefined;return this.toModelTimeFromUs_(ts);}};tr.importer.Importer.register(TraceEventImporter);return{TraceEventImporter:TraceEventImporter};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/base64.js":34,"../../base/color_scheme.js":37,"../../base/range.js":52,"../../base/unit.js":62,"../../base/utils.js":64,"../../importer/context_processor.js":75,"../../importer/importer.js":81,"../../model/comment_box_annotation.js":111,"../../model/constants.js":113,"../../model/container_memory_dump.js":114,"../../model/counter_series.js":117,"../../model/flow_event.js":126,"../../model/global_memory_dump.js":128,"../../model/heap_dump.js":129,"../../model/instant_event.js":135,"../../model/memory_allocator_dump.js":139,"../../model/model.js":140,"../../model/process_memory_dump.js":150,"../../model/rect_annotation.js":151,"../../model/scoped_id.js":153,"../../model/slice_group.js":157,"../../model/vm_region.js":173,"../../model/x_marker_annotation.js":174,"../../value/numeric.js":195,"./trace_code_entry.js":69,"./trace_code_map.js":70,"./v8/codemap.js":72}],72:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/base64.js":35,"../../base/color_scheme.js":38,"../../base/range.js":53,"../../base/unit.js":63,"../../base/utils.js":65,"../../importer/context_processor.js":76,"../../importer/importer.js":82,"../../model/comment_box_annotation.js":112,"../../model/constants.js":114,"../../model/container_memory_dump.js":115,"../../model/counter_series.js":118,"../../model/flow_event.js":127,"../../model/global_memory_dump.js":129,"../../model/heap_dump.js":130,"../../model/instant_event.js":136,"../../model/memory_allocator_dump.js":140,"../../model/model.js":141,"../../model/process_memory_dump.js":151,"../../model/rect_annotation.js":152,"../../model/scoped_id.js":154,"../../model/slice_group.js":158,"../../model/vm_region.js":174,"../../model/x_marker_annotation.js":175,"../../value/numeric.js":196,"./trace_code_entry.js":70,"./trace_code_map.js":71,"./v8/codemap.js":73}],73:[function(require,module,exports){
+(function (global){
 "use strict";require("./splaytree.js");'use strict';global.tr.exportTo('tr.e.importer.v8',function(){function CodeMap(){this.dynamics_=new tr.e.importer.v8.SplayTree();this.dynamicsNameGen_=new tr.e.importer.v8.CodeMap.NameGenerator();this.statics_=new tr.e.importer.v8.SplayTree();this.libraries_=new tr.e.importer.v8.SplayTree();this.pages_=[];}CodeMap.PAGE_ALIGNMENT=12;CodeMap.PAGE_SIZE=1<<CodeMap.PAGE_ALIGNMENT;CodeMap.prototype.addCode=function(start,codeEntry){this.deleteAllCoveredNodes_(this.dynamics_,start,start+codeEntry.size);this.dynamics_.insert(start,codeEntry);};CodeMap.prototype.moveCode=function(from,to){var removedNode=this.dynamics_.remove(from);this.deleteAllCoveredNodes_(this.dynamics_,to,to+removedNode.value.size);this.dynamics_.insert(to,removedNode.value);};CodeMap.prototype.deleteCode=function(start){var removedNode=this.dynamics_.remove(start);};CodeMap.prototype.addLibrary=function(start,codeEntry){this.markPages_(start,start+codeEntry.size);this.libraries_.insert(start,codeEntry);};CodeMap.prototype.addStaticCode=function(start,codeEntry){this.statics_.insert(start,codeEntry);};CodeMap.prototype.markPages_=function(start,end){for(var addr=start;addr<=end;addr+=CodeMap.PAGE_SIZE){this.pages_[addr>>>CodeMap.PAGE_ALIGNMENT]=1;}};CodeMap.prototype.deleteAllCoveredNodes_=function(tree,start,end){var toDelete=[];var addr=end-1;while(addr>=start){var node=tree.findGreatestLessThan(addr);if(!node)break;var start2=node.key,end2=start2+node.value.size;if(start2<end&&start<end2)toDelete.push(start2);addr=start2-1;}for(var i=0,l=toDelete.length;i<l;++i)tree.remove(toDelete[i]);};CodeMap.prototype.isAddressBelongsTo_=function(addr,node){return addr>=node.key&&addr<node.key+node.value.size;};CodeMap.prototype.findInTree_=function(tree,addr){var node=tree.findGreatestLessThan(addr);return node&&this.isAddressBelongsTo_(addr,node)?node.value:null;};CodeMap.prototype.findEntryInLibraries=function(addr){var pageAddr=addr>>>CodeMap.PAGE_ALIGNMENT;if(pageAddr in this.pages_)return this.findInTree_(this.libraries_,addr);return undefined;};CodeMap.prototype.findEntry=function(addr){var pageAddr=addr>>>CodeMap.PAGE_ALIGNMENT;if(pageAddr in this.pages_){return this.findInTree_(this.statics_,addr)||this.findInTree_(this.libraries_,addr);}var min=this.dynamics_.findMin();var max=this.dynamics_.findMax();if(max!=null&&addr<max.key+max.value.size&&addr>=min.key){var dynaEntry=this.findInTree_(this.dynamics_,addr);if(dynaEntry==null)return null;if(!dynaEntry.nameUpdated_){dynaEntry.name=this.dynamicsNameGen_.getName(dynaEntry.name);dynaEntry.nameUpdated_=true;}return dynaEntry;}return null;};CodeMap.prototype.findDynamicEntryByStartAddress=function(addr){var node=this.dynamics_.find(addr);return node?node.value:null;};CodeMap.prototype.getAllDynamicEntries=function(){return this.dynamics_.exportValues();};CodeMap.prototype.getAllDynamicEntriesWithAddresses=function(){return this.dynamics_.exportKeysAndValues();};CodeMap.prototype.getAllStaticEntries=function(){return this.statics_.exportValues();};CodeMap.prototype.getAllLibrariesEntries=function(){return this.libraries_.exportValues();};CodeMap.CodeState={COMPILED:0,OPTIMIZABLE:1,OPTIMIZED:2};CodeMap.CodeEntry=function(size,opt_name,opt_type){this.id=tr.b.GUID.allocateSimple();this.size=size;this.name_=opt_name||'';this.type=opt_type||'';this.nameUpdated_=false;};CodeMap.CodeEntry.prototype={__proto__:Object.prototype,get name(){return this.name_;},set name(value){this.name_=value;},toString:function(){this.name_+': '+this.size.toString(16);}};CodeMap.CodeEntry.TYPE={SHARED_LIB:'SHARED_LIB',CPP:'CPP'};CodeMap.DynamicFuncCodeEntry=function(size,type,func,state){CodeMap.CodeEntry.call(this,size,'',type);this.func=func;this.state=state;};CodeMap.DynamicFuncCodeEntry.STATE_PREFIX=['','~','*'];CodeMap.DynamicFuncCodeEntry.prototype={__proto__:CodeMap.CodeEntry.prototype,get name(){return CodeMap.DynamicFuncCodeEntry.STATE_PREFIX[this.state]+this.func.name;},set name(value){this.name_=value;},getRawName:function(){return this.func.getName();},isJSFunction:function(){return true;},toString:function(){return this.type+': '+this.name+': '+this.size.toString(16);}};CodeMap.FunctionEntry=function(name){CodeMap.CodeEntry.call(this,0,name);};CodeMap.FunctionEntry.prototype={__proto__:CodeMap.CodeEntry.prototype,get name(){var name=this.name_;if(name.length==0){name='<anonymous>';}else if(name.charAt(0)==' '){name='<anonymous>'+name;}return name;},set name(value){this.name_=value;}};CodeMap.NameGenerator=function(){this.knownNames_={};};CodeMap.NameGenerator.prototype.getName=function(name){if(!(name in this.knownNames_)){this.knownNames_[name]=0;return name;}var count=++this.knownNames_[name];return name+' {'+count+'}';};return{CodeMap:CodeMap};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./splaytree.js":73}],73:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./splaytree.js":74}],74:[function(require,module,exports){
+(function (global){
 "use strict";require("../../../base/base.js");'use strict';global.tr.exportTo('tr.e.importer.v8',function(){function SplayTree(){};SplayTree.prototype.root_=null;SplayTree.prototype.isEmpty=function(){return!this.root_;};SplayTree.prototype.insert=function(key,value){if(this.isEmpty()){this.root_=new SplayTree.Node(key,value);return;}this.splay_(key);if(this.root_.key==key){return;}var node=new SplayTree.Node(key,value);if(key>this.root_.key){node.left=this.root_;node.right=this.root_.right;this.root_.right=null;}else{node.right=this.root_;node.left=this.root_.left;this.root_.left=null;}this.root_=node;};SplayTree.prototype.remove=function(key){if(this.isEmpty()){throw Error('Key not found: '+key);}this.splay_(key);if(this.root_.key!=key){throw Error('Key not found: '+key);}var removed=this.root_;if(!this.root_.left){this.root_=this.root_.right;}else{var right=this.root_.right;this.root_=this.root_.left;this.splay_(key);this.root_.right=right;}return removed;};SplayTree.prototype.find=function(key){if(this.isEmpty()){return null;}this.splay_(key);return this.root_.key==key?this.root_:null;};SplayTree.prototype.findMin=function(){if(this.isEmpty()){return null;}var current=this.root_;while(current.left){current=current.left;}return current;};SplayTree.prototype.findMax=function(opt_startNode){if(this.isEmpty()){return null;}var current=opt_startNode||this.root_;while(current.right){current=current.right;}return current;};SplayTree.prototype.findGreatestLessThan=function(key){if(this.isEmpty()){return null;}this.splay_(key);if(this.root_.key<=key){return this.root_;}else if(this.root_.left){return this.findMax(this.root_.left);}else{return null;}};SplayTree.prototype.exportKeysAndValues=function(){var result=[];this.traverse_(function(node){result.push([node.key,node.value]);});return result;};SplayTree.prototype.exportValues=function(){var result=[];this.traverse_(function(node){result.push(node.value);});return result;};SplayTree.prototype.splay_=function(key){if(this.isEmpty()){return;}var dummy,left,right;dummy=left=right=new SplayTree.Node(null,null);var current=this.root_;while(true){if(key<current.key){if(!current.left){break;}if(key<current.left.key){var tmp=current.left;current.left=tmp.right;tmp.right=current;current=tmp;if(!current.left){break;}}right.left=current;right=current;current=current.left;}else if(key>current.key){if(!current.right){break;}if(key>current.right.key){var tmp=current.right;current.right=tmp.left;tmp.left=current;current=tmp;if(!current.right){break;}}left.right=current;left=current;current=current.right;}else{break;}}left.right=current.left;right.left=current.right;current.left=dummy.right;current.right=dummy.left;this.root_=current;};SplayTree.prototype.traverse_=function(f){var nodesToVisit=[this.root_];while(nodesToVisit.length>0){var node=nodesToVisit.shift();if(node==null){continue;}f(node);nodesToVisit.push(node.left);nodesToVisit.push(node.right);}};SplayTree.Node=function(key,value){this.key=key;this.value=value;};SplayTree.Node.prototype.left=null;SplayTree.Node.prototype.right=null;return{SplayTree:SplayTree};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../../base/base.js":33}],74:[function(require,module,exports){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../../base/base.js":34}],75:[function(require,module,exports){
 "use strict";require("./importer/trace_event_importer.js");require("../model/model.js");
-},{"../model/model.js":140,"./importer/trace_event_importer.js":71}],75:[function(require,module,exports){
-(function(global){
+},{"../model/model.js":141,"./importer/trace_event_importer.js":72}],76:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/base.js");'use strict';global.tr.exportTo('tr.importer',function(){function ContextProcessor(model){this.model_=model;this.activeContexts_=[];this.stackPerType_={};this.contextCache_={};this.contextSetCache_={};this.cachedEntryForActiveContexts_=undefined;this.seenSnapshots_={};};ContextProcessor.prototype={enterContext:function(contextType,scopedId){var newActiveContexts=[this.getOrCreateContext_(contextType,scopedId)];for(var oldContext of this.activeContexts_){if(oldContext.type===contextType){this.pushContext_(oldContext);}else{newActiveContexts.push(oldContext);}}this.activeContexts_=newActiveContexts;this.cachedEntryForActiveContexts_=undefined;},leaveContext:function(contextType,scopedId){this.leaveContextImpl_(context=>context.type===contextType&&context.snapshot.scope===scopedId.scope&&context.snapshot.idRef===scopedId.id);},destroyContext:function(scopedId){tr.b.iterItems(this.stackPerType_,function(contextType,stack){var newLength=0;for(var i=0;i<stack.length;++i){if(stack[i].snapshot.scope!==scopedId.scope||stack[i].snapshot.idRef!==scopedId.id){stack[newLength++]=stack[i];}}stack.length=newLength;});this.leaveContextImpl_(context=>context.snapshot.scope===scopedId.scope&&context.snapshot.idRef===scopedId.id);},leaveContextImpl_:function(predicate){var newActiveContexts=[];for(var oldContext of this.activeContexts_){if(predicate(oldContext)){var previousContext=this.popContext_(oldContext.type);if(previousContext)newActiveContexts.push(previousContext);}else{newActiveContexts.push(oldContext);}}this.activeContexts_=newActiveContexts;this.cachedEntryForActiveContexts_=undefined;},getOrCreateContext_:function(contextType,scopedId){var context={type:contextType,snapshot:{scope:scopedId.scope,idRef:scopedId.id}};var key=this.getContextKey_(context);if(key in this.contextCache_)return this.contextCache_[key];this.contextCache_[key]=context;var snapshotKey=this.getSnapshotKey_(scopedId);this.seenSnapshots_[snapshotKey]=true;return context;},pushContext_:function(context){if(!(context.type in this.stackPerType_))this.stackPerType_[context.type]=[];this.stackPerType_[context.type].push(context);},popContext_:function(contextType){if(!(contextType in this.stackPerType_))return undefined;return this.stackPerType_[contextType].pop();},getContextKey_:function(context){return[context.type,context.snapshot.scope,context.snapshot.idRef].join('\x00');},getSnapshotKey_:function(scopedId){return[scopedId.scope,scopedId.idRef].join('\x00');},get activeContexts(){if(this.cachedEntryForActiveContexts_===undefined){var key=[];for(var context of this.activeContexts_)key.push(this.getContextKey_(context));key.sort();key=key.join('\x00');if(key in this.contextSetCache_){this.cachedEntryForActiveContexts_=this.contextSetCache_[key];}else{this.activeContexts_.sort(function(a,b){var keyA=this.getContextKey_(a);var keyB=this.getContextKey_(b);if(keyA<keyB)return-1;if(keyA>keyB)return 1;return 0;}.bind(this));this.contextSetCache_[key]=Object.freeze(this.activeContexts_);this.cachedEntryForActiveContexts_=this.contextSetCache_[key];}}return this.cachedEntryForActiveContexts_;},invalidateContextCacheForSnapshot:function(scopedId){var snapshotKey=this.getSnapshotKey_(scopedId);if(!(snapshotKey in this.seenSnapshots_))return;this.contextCache_={};this.contextSetCache_={};this.cachedEntryForActiveContexts_=undefined;this.activeContexts_=this.activeContexts_.map(function(context){if(context.snapshot.scope!==scopedId.scope||context.snapshot.idRef!==scopedId.id)return context;return{type:context.type,snapshot:{scope:context.snapshot.scope,idRef:context.snapshot.idRef}};});this.seenSnapshots_={};}};return{ContextProcessor:ContextProcessor};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/base.js":33}],76:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/base.js":34}],77:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/base.js");require("./importer.js");'use strict';global.tr.exportTo('tr.importer',function(){function EmptyImporter(events){this.importPriority=0;};EmptyImporter.canImport=function(eventData){if(eventData instanceof Array&&eventData.length==0)return true;if(typeof eventData==='string'||eventData instanceof String){return eventData.length==0;}return false;};EmptyImporter.prototype={__proto__:tr.importer.Importer.prototype,get importerName(){return'EmptyImporter';}};tr.importer.Importer.register(EmptyImporter);return{EmptyImporter:EmptyImporter};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/base.js":33,"./importer.js":81}],77:[function(require,module,exports){
-(function(global){
-"use strict";require("../base/range_utils.js");require("../extras/chrome/cc/input_latency_async_slice.js");require("./proto_expectation.js");'use strict';global.tr.exportTo('tr.importer',function(){var ProtoExpectation=tr.importer.ProtoExpectation;var INPUT_TYPE=tr.e.cc.INPUT_EVENT_TYPE_NAMES;var KEYBOARD_TYPE_NAMES=[INPUT_TYPE.CHAR,INPUT_TYPE.KEY_DOWN_RAW,INPUT_TYPE.KEY_DOWN,INPUT_TYPE.KEY_UP];var MOUSE_RESPONSE_TYPE_NAMES=[INPUT_TYPE.CLICK,INPUT_TYPE.CONTEXT_MENU];var MOUSE_WHEEL_TYPE_NAMES=[INPUT_TYPE.MOUSE_WHEEL];var MOUSE_DRAG_TYPE_NAMES=[INPUT_TYPE.MOUSE_DOWN,INPUT_TYPE.MOUSE_MOVE,INPUT_TYPE.MOUSE_UP];var TAP_TYPE_NAMES=[INPUT_TYPE.TAP,INPUT_TYPE.TAP_CANCEL,INPUT_TYPE.TAP_DOWN];var PINCH_TYPE_NAMES=[INPUT_TYPE.PINCH_BEGIN,INPUT_TYPE.PINCH_END,INPUT_TYPE.PINCH_UPDATE];var FLING_TYPE_NAMES=[INPUT_TYPE.FLING_CANCEL,INPUT_TYPE.FLING_START];var TOUCH_TYPE_NAMES=[INPUT_TYPE.TOUCH_END,INPUT_TYPE.TOUCH_MOVE,INPUT_TYPE.TOUCH_START];var SCROLL_TYPE_NAMES=[INPUT_TYPE.SCROLL_BEGIN,INPUT_TYPE.SCROLL_END,INPUT_TYPE.SCROLL_UPDATE];var ALL_HANDLED_TYPE_NAMES=[].concat(KEYBOARD_TYPE_NAMES,MOUSE_RESPONSE_TYPE_NAMES,MOUSE_WHEEL_TYPE_NAMES,MOUSE_DRAG_TYPE_NAMES,PINCH_TYPE_NAMES,TAP_TYPE_NAMES,FLING_TYPE_NAMES,TOUCH_TYPE_NAMES,SCROLL_TYPE_NAMES);var RENDERER_FLING_TITLE='InputHandlerProxy::HandleGestureFling::started';var PLAYBACK_EVENT_TITLE='VideoPlayback';var CSS_ANIMATION_TITLE='Animation';var INPUT_MERGE_THRESHOLD_MS=200;var ANIMATION_MERGE_THRESHOLD_MS=32;var MOUSE_WHEEL_THRESHOLD_MS=40;var MOUSE_MOVE_THRESHOLD_MS=40;var KEYBOARD_IR_NAME='Keyboard';var MOUSE_IR_NAME='Mouse';var MOUSEWHEEL_IR_NAME='MouseWheel';var TAP_IR_NAME='Tap';var PINCH_IR_NAME='Pinch';var FLING_IR_NAME='Fling';var TOUCH_IR_NAME='Touch';var SCROLL_IR_NAME='Scroll';var CSS_IR_NAME='CSS';var WEBGL_IR_NAME='WebGL';var VIDEO_IR_NAME='Video';function compareEvents(x,y){if(x.start!==y.start)return x.start-y.start;if(x.end!==y.end)return x.end-y.end;if(x.guid&&y.guid)return x.guid-y.guid;return 0;}function forEventTypesIn(events,typeNames,cb,opt_this){events.forEach(function(event){if(typeNames.indexOf(event.typeName)>=0){cb.call(opt_this,event);}});}function causedFrame(event){return event.associatedEvents.some(x=>x.title===tr.model.helpers.IMPL_RENDERING_STATS);}function getSortedFrameEventsByProcess(modelHelper){var frameEventsByPid={};tr.b.iterItems(modelHelper.rendererHelpers,function(pid,rendererHelper){frameEventsByPid[pid]=rendererHelper.getFrameEventsInRange(tr.model.helpers.IMPL_FRAMETIME_TYPE,modelHelper.model.bounds);});return frameEventsByPid;}function getSortedInputEvents(modelHelper){var inputEvents=[];var browserProcess=modelHelper.browserHelper.process;var mainThread=browserProcess.findAtMostOneThreadNamed('CrBrowserMain');for(var slice of mainThread.asyncSliceGroup.getDescendantEvents()){if(!slice.isTopLevel)continue;if(!(slice instanceof tr.e.cc.InputLatencyAsyncSlice))continue;if(isNaN(slice.start)||isNaN(slice.duration)||isNaN(slice.end))continue;inputEvents.push(slice);}return inputEvents.sort(compareEvents);}function findProtoExpectations(modelHelper,sortedInputEvents){var protoExpectations=[];var handlers=[handleKeyboardEvents,handleMouseResponseEvents,handleMouseWheelEvents,handleMouseDragEvents,handleTapResponseEvents,handlePinchEvents,handleFlingEvents,handleTouchEvents,handleScrollEvents,handleCSSAnimations,handleWebGLAnimations,handleVideoAnimations];handlers.forEach(function(handler){protoExpectations.push.apply(protoExpectations,handler(modelHelper,sortedInputEvents));});protoExpectations.sort(compareEvents);return protoExpectations;}function handleKeyboardEvents(modelHelper,sortedInputEvents){var protoExpectations=[];forEventTypesIn(sortedInputEvents,KEYBOARD_TYPE_NAMES,function(event){var pe=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,KEYBOARD_IR_NAME);pe.pushEvent(event);protoExpectations.push(pe);});return protoExpectations;}function handleMouseResponseEvents(modelHelper,sortedInputEvents){var protoExpectations=[];forEventTypesIn(sortedInputEvents,MOUSE_RESPONSE_TYPE_NAMES,function(event){var pe=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,MOUSE_IR_NAME);pe.pushEvent(event);protoExpectations.push(pe);});return protoExpectations;}function handleMouseWheelEvents(modelHelper,sortedInputEvents){var protoExpectations=[];var currentPE=undefined;var prevEvent_=undefined;forEventTypesIn(sortedInputEvents,MOUSE_WHEEL_TYPE_NAMES,function(event){var prevEvent=prevEvent_;prevEvent_=event;if(currentPE&&prevEvent.start+MOUSE_WHEEL_THRESHOLD_MS>=event.start){if(currentPE.irType===ProtoExpectation.ANIMATION_TYPE){currentPE.pushEvent(event);}else{currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,MOUSEWHEEL_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);}return;}currentPE=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,MOUSEWHEEL_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);});return protoExpectations;}function handleMouseDragEvents(modelHelper,sortedInputEvents){var protoExpectations=[];var currentPE=undefined;var mouseDownEvent=undefined;forEventTypesIn(sortedInputEvents,MOUSE_DRAG_TYPE_NAMES,function(event){switch(event.typeName){case INPUT_TYPE.MOUSE_DOWN:if(causedFrame(event)){var pe=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,MOUSE_IR_NAME);pe.pushEvent(event);protoExpectations.push(pe);}else{mouseDownEvent=event;}break;case INPUT_TYPE.MOUSE_MOVE:if(!causedFrame(event)){var pe=new ProtoExpectation(ProtoExpectation.IGNORED_TYPE);pe.pushEvent(event);protoExpectations.push(pe);}else if(!currentPE||!currentPE.isNear(event,MOUSE_MOVE_THRESHOLD_MS)){currentPE=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,MOUSE_IR_NAME);currentPE.pushEvent(event);if(mouseDownEvent){currentPE.associatedEvents.push(mouseDownEvent);mouseDownEvent=undefined;}protoExpectations.push(currentPE);}else{if(currentPE.irType===ProtoExpectation.ANIMATION_TYPE){currentPE.pushEvent(event);}else{currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,MOUSE_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);}}break;case INPUT_TYPE.MOUSE_UP:if(!mouseDownEvent){var pe=new ProtoExpectation(causedFrame(event)?ProtoExpectation.RESPONSE_TYPE:ProtoExpectation.IGNORED_TYPE,MOUSE_IR_NAME);pe.pushEvent(event);protoExpectations.push(pe);break;}if(currentPE){currentPE.pushEvent(event);}else{currentPE=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,MOUSE_IR_NAME);if(mouseDownEvent)currentPE.associatedEvents.push(mouseDownEvent);currentPE.pushEvent(event);protoExpectations.push(currentPE);}mouseDownEvent=undefined;currentPE=undefined;break;}});if(mouseDownEvent){currentPE=new ProtoExpectation(ProtoExpectation.IGNORED_TYPE);currentPE.pushEvent(mouseDownEvent);protoExpectations.push(currentPE);}return protoExpectations;}function handleTapResponseEvents(modelHelper,sortedInputEvents){var protoExpectations=[];var currentPE=undefined;forEventTypesIn(sortedInputEvents,TAP_TYPE_NAMES,function(event){switch(event.typeName){case INPUT_TYPE.TAP_DOWN:currentPE=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,TAP_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);break;case INPUT_TYPE.TAP:if(currentPE){currentPE.pushEvent(event);}else{currentPE=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,TAP_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);}currentPE=undefined;break;case INPUT_TYPE.TAP_CANCEL:if(!currentPE){var pe=new ProtoExpectation(ProtoExpectation.IGNORED_TYPE);pe.pushEvent(event);protoExpectations.push(pe);break;}if(currentPE.isNear(event,INPUT_MERGE_THRESHOLD_MS)){currentPE.pushEvent(event);}else{currentPE=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,TAP_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);}currentPE=undefined;break;}});return protoExpectations;}function handlePinchEvents(modelHelper,sortedInputEvents){var protoExpectations=[];var currentPE=undefined;var sawFirstUpdate=false;var modelBounds=modelHelper.model.bounds;forEventTypesIn(sortedInputEvents,PINCH_TYPE_NAMES,function(event){switch(event.typeName){case INPUT_TYPE.PINCH_BEGIN:if(currentPE&&currentPE.isNear(event,INPUT_MERGE_THRESHOLD_MS)){currentPE.pushEvent(event);break;}currentPE=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,PINCH_IR_NAME);currentPE.pushEvent(event);currentPE.isAnimationBegin=true;protoExpectations.push(currentPE);sawFirstUpdate=false;break;case INPUT_TYPE.PINCH_UPDATE:if(!currentPE||currentPE.irType===ProtoExpectation.RESPONSE_TYPE&&sawFirstUpdate||!currentPE.isNear(event,INPUT_MERGE_THRESHOLD_MS)){currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,PINCH_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);}else{currentPE.pushEvent(event);sawFirstUpdate=true;}break;case INPUT_TYPE.PINCH_END:if(currentPE){currentPE.pushEvent(event);}else{var pe=new ProtoExpectation(ProtoExpectation.IGNORED_TYPE);pe.pushEvent(event);protoExpectations.push(pe);}currentPE=undefined;break;}});return protoExpectations;}function handleFlingEvents(modelHelper,sortedInputEvents){var protoExpectations=[];var currentPE=undefined;function isRendererFling(event){return event.title===RENDERER_FLING_TITLE;}var browserHelper=modelHelper.browserHelper;var flingEvents=browserHelper.getAllAsyncSlicesMatching(isRendererFling);forEventTypesIn(sortedInputEvents,FLING_TYPE_NAMES,function(event){flingEvents.push(event);});flingEvents.sort(compareEvents);flingEvents.forEach(function(event){if(event.title===RENDERER_FLING_TITLE){if(currentPE){currentPE.pushEvent(event);}else{currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,FLING_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);}return;}switch(event.typeName){case INPUT_TYPE.FLING_START:if(currentPE){console.error('Another FlingStart? File a bug with this trace!');currentPE.pushEvent(event);}else{currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,FLING_IR_NAME);currentPE.pushEvent(event);currentPE.end=0;protoExpectations.push(currentPE);}break;case INPUT_TYPE.FLING_CANCEL:if(currentPE){currentPE.pushEvent(event);currentPE.end=event.start;currentPE=undefined;}else{var pe=new ProtoExpectation(ProtoExpectation.IGNORED_TYPE);pe.pushEvent(event);protoExpectations.push(pe);}break;}});if(currentPE&&!currentPE.end)currentPE.end=modelHelper.model.bounds.max;return protoExpectations;}function handleTouchEvents(modelHelper,sortedInputEvents){var protoExpectations=[];var currentPE=undefined;var sawFirstMove=false;forEventTypesIn(sortedInputEvents,TOUCH_TYPE_NAMES,function(event){switch(event.typeName){case INPUT_TYPE.TOUCH_START:if(currentPE){currentPE.pushEvent(event);}else{currentPE=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,TOUCH_IR_NAME);currentPE.pushEvent(event);currentPE.isAnimationBegin=true;protoExpectations.push(currentPE);sawFirstMove=false;}break;case INPUT_TYPE.TOUCH_MOVE:if(!currentPE){currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,TOUCH_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);break;}if(sawFirstMove&&currentPE.irType===ProtoExpectation.RESPONSE_TYPE||!currentPE.isNear(event,INPUT_MERGE_THRESHOLD_MS)){var prevEnd=currentPE.end;currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,TOUCH_IR_NAME);currentPE.pushEvent(event);currentPE.start=prevEnd;protoExpectations.push(currentPE);}else{currentPE.pushEvent(event);sawFirstMove=true;}break;case INPUT_TYPE.TOUCH_END:if(!currentPE){var pe=new ProtoExpectation(ProtoExpectation.IGNORED_TYPE);pe.pushEvent(event);protoExpectations.push(pe);break;}if(currentPE.isNear(event,INPUT_MERGE_THRESHOLD_MS)){currentPE.pushEvent(event);}else{var pe=new ProtoExpectation(ProtoExpectation.IGNORED_TYPE);pe.pushEvent(event);protoExpectations.push(pe);}currentPE=undefined;break;}});return protoExpectations;}function handleScrollEvents(modelHelper,sortedInputEvents){var protoExpectations=[];var currentPE=undefined;var sawFirstUpdate=false;forEventTypesIn(sortedInputEvents,SCROLL_TYPE_NAMES,function(event){switch(event.typeName){case INPUT_TYPE.SCROLL_BEGIN:currentPE=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,SCROLL_IR_NAME);currentPE.pushEvent(event);currentPE.isAnimationBegin=true;protoExpectations.push(currentPE);sawFirstUpdate=false;break;case INPUT_TYPE.SCROLL_UPDATE:if(currentPE){if(currentPE.isNear(event,INPUT_MERGE_THRESHOLD_MS)&&(currentPE.irType===ProtoExpectation.ANIMATION_TYPE||!sawFirstUpdate)){currentPE.pushEvent(event);sawFirstUpdate=true;}else{currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,SCROLL_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);}}else{currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,SCROLL_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);}break;case INPUT_TYPE.SCROLL_END:if(!currentPE){console.error('ScrollEnd without ScrollUpdate? '+'File a bug with this trace!');var pe=new ProtoExpectation(ProtoExpectation.IGNORED_TYPE);pe.pushEvent(event);protoExpectations.push(pe);break;}currentPE.pushEvent(event);break;}});return protoExpectations;}function handleVideoAnimations(modelHelper,sortedInputEvents){var events=[];for(var pid in modelHelper.rendererHelpers){for(var asyncSlice of modelHelper.rendererHelpers[pid].mainThread.asyncSliceGroup.slices){if(asyncSlice.title===PLAYBACK_EVENT_TITLE)events.push(asyncSlice);}}events.sort(tr.importer.compareEvents);var protoExpectations=[];for(var event of events){var currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,VIDEO_IR_NAME);currentPE.start=event.start;currentPE.end=event.end;currentPE.pushEvent(event);protoExpectations.push(currentPE);}return protoExpectations;}function handleCSSAnimations(modelHelper,sortedInputEvents){var animationEvents=modelHelper.browserHelper.getAllAsyncSlicesMatching(function(event){return event.title===CSS_ANIMATION_TITLE&&event.isTopLevel&&event.duration>0;});var animationRanges=[];function pushAnimationRange(start,end,animation){var range=tr.b.Range.fromExplicitRange(start,end);range.animation=animation;animationRanges.push(range);}animationEvents.forEach(function(animation){if(animation.subSlices.length===0){pushAnimationRange(animation.start,animation.end,animation);}else{var start=undefined;animation.subSlices.forEach(function(sub){if(sub.args.data.state==='running'&&start===undefined){start=sub.start;}else if(sub.args.data.state==='paused'||sub.args.data.state==='idle'||sub.args.data.state==='finished'){if(start===undefined){start=modelHelper.model.bounds.min;}pushAnimationRange(start,sub.start,animation);start=undefined;}});if(start!==undefined)pushAnimationRange(start,animation.end,animation);}});return animationRanges.map(function(range){var protoExpectation=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,CSS_IR_NAME);protoExpectation.start=range.min;protoExpectation.end=range.max;protoExpectation.associatedEvents.push(range.animation);return protoExpectation;});}function findWebGLEvents(modelHelper,mailboxEvents,animationEvents){for(var event of modelHelper.model.getDescendantEvents()){if(event.title==='DrawingBuffer::prepareMailbox')mailboxEvents.push(event);else if(event.title==='PageAnimator::serviceScriptedAnimations')animationEvents.push(event);}}function findMailboxEventsNearAnimationEvents(mailboxEvents,animationEvents){if(animationEvents.length===0)return[];mailboxEvents.sort(compareEvents);animationEvents.sort(compareEvents);var animationIterator=animationEvents[Symbol.iterator]();var animationEvent=animationIterator.next().value;var filteredEvents=[];for(var event of mailboxEvents){while(animationEvent&&animationEvent.start<event.start-ANIMATION_MERGE_THRESHOLD_MS)animationEvent=animationIterator.next().value;if(!animationEvent)break;if(animationEvent.start<event.start+ANIMATION_MERGE_THRESHOLD_MS)filteredEvents.push(event);}return filteredEvents;}function createProtoExpectationsFromMailboxEvents(mailboxEvents){var protoExpectations=[];var currentPE=undefined;for(var event of mailboxEvents){if(currentPE===undefined||!currentPE.isNear(event,ANIMATION_MERGE_THRESHOLD_MS)){currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,WEBGL_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);}else{currentPE.pushEvent(event);}}return protoExpectations;}function handleWebGLAnimations(modelHelper,sortedInputEvents){var prepareMailboxEvents=[];var scriptedAnimationEvents=[];findWebGLEvents(modelHelper,prepareMailboxEvents,scriptedAnimationEvents);var webGLMailboxEvents=findMailboxEventsNearAnimationEvents(prepareMailboxEvents,scriptedAnimationEvents);return createProtoExpectationsFromMailboxEvents(webGLMailboxEvents);}function postProcessProtoExpectations(modelHelper,protoExpectations){protoExpectations=findFrameEventsForAnimations(modelHelper,protoExpectations);protoExpectations=mergeIntersectingResponses(protoExpectations);protoExpectations=mergeIntersectingAnimations(protoExpectations);protoExpectations=fixResponseAnimationStarts(protoExpectations);protoExpectations=fixTapResponseTouchAnimations(protoExpectations);return protoExpectations;}function mergeIntersectingResponses(protoExpectations){var newPEs=[];while(protoExpectations.length){var pe=protoExpectations.shift();newPEs.push(pe);if(pe.irType!==ProtoExpectation.RESPONSE_TYPE)continue;for(var i=0;i<protoExpectations.length;++i){var otherPE=protoExpectations[i];if(otherPE.irType!==pe.irType)continue;if(!otherPE.intersects(pe))continue;var typeNames=pe.associatedEvents.map(function(event){return event.typeName;});if(otherPE.containsTypeNames(typeNames))continue;pe.merge(otherPE);protoExpectations.splice(i,1);--i;}}return newPEs;}function mergeIntersectingAnimations(protoExpectations){var newPEs=[];while(protoExpectations.length){var pe=protoExpectations.shift();newPEs.push(pe);if(pe.irType!==ProtoExpectation.ANIMATION_TYPE)continue;var isCSS=pe.containsSliceTitle(CSS_ANIMATION_TITLE);var isFling=pe.containsTypeNames([INPUT_TYPE.FLING_START]);var isVideo=pe.containsTypeNames([VIDEO_IR_NAME]);for(var i=0;i<protoExpectations.length;++i){var otherPE=protoExpectations[i];if(otherPE.irType!==pe.irType)continue;if(isCSS!=otherPE.containsSliceTitle(CSS_ANIMATION_TITLE))continue;if(isCSS){if(!pe.isNear(otherPE,ANIMATION_MERGE_THRESHOLD_MS))continue;}else if(!otherPE.intersects(pe)){continue;}if(isFling!==otherPE.containsTypeNames([INPUT_TYPE.FLING_START]))continue;if(isVideo!==otherPE.containsTypeNames([VIDEO_IR_NAME]))continue;pe.merge(otherPE);protoExpectations.splice(i,1);--i;}}return newPEs;}function fixResponseAnimationStarts(protoExpectations){protoExpectations.forEach(function(ape){if(ape.irType!==ProtoExpectation.ANIMATION_TYPE)return;protoExpectations.forEach(function(rpe){if(rpe.irType!==ProtoExpectation.RESPONSE_TYPE)return;if(!ape.containsTimestampInclusive(rpe.end))return;if(ape.containsTimestampInclusive(rpe.start))return;ape.start=rpe.end;});});return protoExpectations;}function fixTapResponseTouchAnimations(protoExpectations){function isTapResponse(pe){return pe.irType===ProtoExpectation.RESPONSE_TYPE&&pe.containsTypeNames([INPUT_TYPE.TAP]);}function isTouchAnimation(pe){return pe.irType===ProtoExpectation.ANIMATION_TYPE&&pe.containsTypeNames([INPUT_TYPE.TOUCH_MOVE])&&!pe.containsTypeNames([INPUT_TYPE.SCROLL_UPDATE,INPUT_TYPE.PINCH_UPDATE]);}var newPEs=[];while(protoExpectations.length){var pe=protoExpectations.shift();newPEs.push(pe);var peIsTapResponse=isTapResponse(pe);var peIsTouchAnimation=isTouchAnimation(pe);if(!peIsTapResponse&&!peIsTouchAnimation)continue;for(var i=0;i<protoExpectations.length;++i){var otherPE=protoExpectations[i];if(!otherPE.intersects(pe))continue;if(peIsTapResponse&&!isTouchAnimation(otherPE))continue;if(peIsTouchAnimation&&!isTapResponse(otherPE))continue;pe.irType=ProtoExpectation.RESPONSE_TYPE;pe.merge(otherPE);protoExpectations.splice(i,1);--i;}}return newPEs;}function findFrameEventsForAnimations(modelHelper,protoExpectations){var newPEs=[];var frameEventsByPid=getSortedFrameEventsByProcess(modelHelper);for(var pe of protoExpectations){if(pe.irType!==ProtoExpectation.ANIMATION_TYPE){newPEs.push(pe);continue;}var frameEvents=[];for(var pid of Object.keys(modelHelper.rendererHelpers)){var range=tr.b.Range.fromExplicitRange(pe.start,pe.end);frameEvents.push.apply(frameEvents,range.filterArray(frameEventsByPid[pid],e=>e.start));}if(frameEvents.length===0&&!pe.names.has(WEBGL_IR_NAME)){pe.irType=ProtoExpectation.IGNORED_TYPE;newPEs.push(pe);continue;}pe.associatedEvents.addEventSet(frameEvents);newPEs.push(pe);}return newPEs;}function checkAllInputEventsHandled(sortedInputEvents,protoExpectations){var handledEvents=[];protoExpectations.forEach(function(protoExpectation){protoExpectation.associatedEvents.forEach(function(event){if(event.title===CSS_ANIMATION_TITLE&&event.subSlices.length>0)return;if(handledEvents.indexOf(event)>=0&&event.title!==tr.model.helpers.IMPL_RENDERING_STATS){console.error('double-handled event',event.typeName,parseInt(event.start),parseInt(event.end),protoExpectation);return;}handledEvents.push(event);});});sortedInputEvents.forEach(function(event){if(handledEvents.indexOf(event)<0){console.error('UNHANDLED INPUT EVENT!',event.typeName,parseInt(event.start),parseInt(event.end));}});}function findInputExpectations(modelHelper){var sortedInputEvents=getSortedInputEvents(modelHelper);var protoExpectations=findProtoExpectations(modelHelper,sortedInputEvents);protoExpectations=postProcessProtoExpectations(modelHelper,protoExpectations);checkAllInputEventsHandled(sortedInputEvents,protoExpectations);var irs=[];protoExpectations.forEach(function(protoExpectation){var ir=protoExpectation.createInteractionRecord(modelHelper.model);if(ir)irs.push(ir);});return irs;}return{findInputExpectations:findInputExpectations,compareEvents:compareEvents,CSS_ANIMATION_TITLE:CSS_ANIMATION_TITLE};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/range_utils.js":53,"../extras/chrome/cc/input_latency_async_slice.js":67,"./proto_expectation.js":82}],78:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/base.js":34,"./importer.js":82}],78:[function(require,module,exports){
+(function (global){
+"use strict";require("../base/range_utils.js");require("../extras/chrome/cc/input_latency_async_slice.js");require("./proto_expectation.js");'use strict';global.tr.exportTo('tr.importer',function(){var ProtoExpectation=tr.importer.ProtoExpectation;var INPUT_TYPE=tr.e.cc.INPUT_EVENT_TYPE_NAMES;var KEYBOARD_TYPE_NAMES=[INPUT_TYPE.CHAR,INPUT_TYPE.KEY_DOWN_RAW,INPUT_TYPE.KEY_DOWN,INPUT_TYPE.KEY_UP];var MOUSE_RESPONSE_TYPE_NAMES=[INPUT_TYPE.CLICK,INPUT_TYPE.CONTEXT_MENU];var MOUSE_WHEEL_TYPE_NAMES=[INPUT_TYPE.MOUSE_WHEEL];var MOUSE_DRAG_TYPE_NAMES=[INPUT_TYPE.MOUSE_DOWN,INPUT_TYPE.MOUSE_MOVE,INPUT_TYPE.MOUSE_UP];var TAP_TYPE_NAMES=[INPUT_TYPE.TAP,INPUT_TYPE.TAP_CANCEL,INPUT_TYPE.TAP_DOWN];var PINCH_TYPE_NAMES=[INPUT_TYPE.PINCH_BEGIN,INPUT_TYPE.PINCH_END,INPUT_TYPE.PINCH_UPDATE];var FLING_TYPE_NAMES=[INPUT_TYPE.FLING_CANCEL,INPUT_TYPE.FLING_START];var TOUCH_TYPE_NAMES=[INPUT_TYPE.TOUCH_END,INPUT_TYPE.TOUCH_MOVE,INPUT_TYPE.TOUCH_START];var SCROLL_TYPE_NAMES=[INPUT_TYPE.SCROLL_BEGIN,INPUT_TYPE.SCROLL_END,INPUT_TYPE.SCROLL_UPDATE];var ALL_HANDLED_TYPE_NAMES=[].concat(KEYBOARD_TYPE_NAMES,MOUSE_RESPONSE_TYPE_NAMES,MOUSE_WHEEL_TYPE_NAMES,MOUSE_DRAG_TYPE_NAMES,PINCH_TYPE_NAMES,TAP_TYPE_NAMES,FLING_TYPE_NAMES,TOUCH_TYPE_NAMES,SCROLL_TYPE_NAMES);var RENDERER_FLING_TITLE='InputHandlerProxy::HandleGestureFling::started';var PLAYBACK_EVENT_TITLE='VideoPlayback';var CSS_ANIMATION_TITLE='Animation';var INPUT_MERGE_THRESHOLD_MS=200;var ANIMATION_MERGE_THRESHOLD_MS=32;var MOUSE_WHEEL_THRESHOLD_MS=40;var MOUSE_MOVE_THRESHOLD_MS=40;var KEYBOARD_IR_NAME='Keyboard';var MOUSE_IR_NAME='Mouse';var MOUSEWHEEL_IR_NAME='MouseWheel';var TAP_IR_NAME='Tap';var PINCH_IR_NAME='Pinch';var FLING_IR_NAME='Fling';var TOUCH_IR_NAME='Touch';var SCROLL_IR_NAME='Scroll';var CSS_IR_NAME='CSS';var WEBGL_IR_NAME='WebGL';var VIDEO_IR_NAME='Video';function compareEvents(x,y){if(x.start!==y.start)return x.start-y.start;if(x.end!==y.end)return x.end-y.end;if(x.guid&&y.guid)return x.guid-y.guid;return 0;}function forEventTypesIn(events,typeNames,cb,opt_this){events.forEach(function(event){if(typeNames.indexOf(event.typeName)>=0){cb.call(opt_this,event);}});}function causedFrame(event){return event.associatedEvents.some(x=>x.title===tr.model.helpers.IMPL_RENDERING_STATS);}function getSortedFrameEventsByProcess(modelHelper){var frameEventsByPid={};tr.b.iterItems(modelHelper.rendererHelpers,function(pid,rendererHelper){frameEventsByPid[pid]=rendererHelper.getFrameEventsInRange(tr.model.helpers.IMPL_FRAMETIME_TYPE,modelHelper.model.bounds);});return frameEventsByPid;}function getSortedInputEvents(modelHelper){var inputEvents=[];var browserProcess=modelHelper.browserHelper.process;var mainThread=browserProcess.findAtMostOneThreadNamed('CrBrowserMain');for(var slice of mainThread.asyncSliceGroup.getDescendantEvents()){if(!slice.isTopLevel)continue;if(!(slice instanceof tr.e.cc.InputLatencyAsyncSlice))continue;if(isNaN(slice.start)||isNaN(slice.duration)||isNaN(slice.end))continue;inputEvents.push(slice);}return inputEvents.sort(compareEvents);}function findProtoExpectations(modelHelper,sortedInputEvents){var protoExpectations=[];var handlers=[handleKeyboardEvents,handleMouseResponseEvents,handleMouseWheelEvents,handleMouseDragEvents,handleTapResponseEvents,handlePinchEvents,handleFlingEvents,handleTouchEvents,handleScrollEvents,handleCSSAnimations,handleWebGLAnimations,handleVideoAnimations];handlers.forEach(function(handler){protoExpectations.push.apply(protoExpectations,handler(modelHelper,sortedInputEvents));});protoExpectations.sort(compareEvents);return protoExpectations;}function handleKeyboardEvents(modelHelper,sortedInputEvents){var protoExpectations=[];forEventTypesIn(sortedInputEvents,KEYBOARD_TYPE_NAMES,function(event){var pe=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,KEYBOARD_IR_NAME);pe.pushEvent(event);protoExpectations.push(pe);});return protoExpectations;}function handleMouseResponseEvents(modelHelper,sortedInputEvents){var protoExpectations=[];forEventTypesIn(sortedInputEvents,MOUSE_RESPONSE_TYPE_NAMES,function(event){var pe=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,MOUSE_IR_NAME);pe.pushEvent(event);protoExpectations.push(pe);});return protoExpectations;}function handleMouseWheelEvents(modelHelper,sortedInputEvents){var protoExpectations=[];var currentPE=undefined;var prevEvent_=undefined;forEventTypesIn(sortedInputEvents,MOUSE_WHEEL_TYPE_NAMES,function(event){var prevEvent=prevEvent_;prevEvent_=event;if(currentPE&&prevEvent.start+MOUSE_WHEEL_THRESHOLD_MS>=event.start){if(currentPE.irType===ProtoExpectation.ANIMATION_TYPE){currentPE.pushEvent(event);}else{currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,MOUSEWHEEL_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);}return;}currentPE=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,MOUSEWHEEL_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);});return protoExpectations;}function handleMouseDragEvents(modelHelper,sortedInputEvents){var protoExpectations=[];var currentPE=undefined;var mouseDownEvent=undefined;forEventTypesIn(sortedInputEvents,MOUSE_DRAG_TYPE_NAMES,function(event){switch(event.typeName){case INPUT_TYPE.MOUSE_DOWN:if(causedFrame(event)){var pe=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,MOUSE_IR_NAME);pe.pushEvent(event);protoExpectations.push(pe);}else{mouseDownEvent=event;}break;case INPUT_TYPE.MOUSE_MOVE:if(!causedFrame(event)){var pe=new ProtoExpectation(ProtoExpectation.IGNORED_TYPE);pe.pushEvent(event);protoExpectations.push(pe);}else if(!currentPE||!currentPE.isNear(event,MOUSE_MOVE_THRESHOLD_MS)){currentPE=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,MOUSE_IR_NAME);currentPE.pushEvent(event);if(mouseDownEvent){currentPE.associatedEvents.push(mouseDownEvent);mouseDownEvent=undefined;}protoExpectations.push(currentPE);}else{if(currentPE.irType===ProtoExpectation.ANIMATION_TYPE){currentPE.pushEvent(event);}else{currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,MOUSE_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);}}break;case INPUT_TYPE.MOUSE_UP:if(!mouseDownEvent){var pe=new ProtoExpectation(causedFrame(event)?ProtoExpectation.RESPONSE_TYPE:ProtoExpectation.IGNORED_TYPE,MOUSE_IR_NAME);pe.pushEvent(event);protoExpectations.push(pe);break;}if(currentPE){currentPE.pushEvent(event);}else{currentPE=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,MOUSE_IR_NAME);if(mouseDownEvent)currentPE.associatedEvents.push(mouseDownEvent);currentPE.pushEvent(event);protoExpectations.push(currentPE);}mouseDownEvent=undefined;currentPE=undefined;break;}});if(mouseDownEvent){currentPE=new ProtoExpectation(ProtoExpectation.IGNORED_TYPE);currentPE.pushEvent(mouseDownEvent);protoExpectations.push(currentPE);}return protoExpectations;}function handleTapResponseEvents(modelHelper,sortedInputEvents){var protoExpectations=[];var currentPE=undefined;forEventTypesIn(sortedInputEvents,TAP_TYPE_NAMES,function(event){switch(event.typeName){case INPUT_TYPE.TAP_DOWN:currentPE=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,TAP_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);break;case INPUT_TYPE.TAP:if(currentPE){currentPE.pushEvent(event);}else{currentPE=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,TAP_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);}currentPE=undefined;break;case INPUT_TYPE.TAP_CANCEL:if(!currentPE){var pe=new ProtoExpectation(ProtoExpectation.IGNORED_TYPE);pe.pushEvent(event);protoExpectations.push(pe);break;}if(currentPE.isNear(event,INPUT_MERGE_THRESHOLD_MS)){currentPE.pushEvent(event);}else{currentPE=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,TAP_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);}currentPE=undefined;break;}});return protoExpectations;}function handlePinchEvents(modelHelper,sortedInputEvents){var protoExpectations=[];var currentPE=undefined;var sawFirstUpdate=false;var modelBounds=modelHelper.model.bounds;forEventTypesIn(sortedInputEvents,PINCH_TYPE_NAMES,function(event){switch(event.typeName){case INPUT_TYPE.PINCH_BEGIN:if(currentPE&&currentPE.isNear(event,INPUT_MERGE_THRESHOLD_MS)){currentPE.pushEvent(event);break;}currentPE=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,PINCH_IR_NAME);currentPE.pushEvent(event);currentPE.isAnimationBegin=true;protoExpectations.push(currentPE);sawFirstUpdate=false;break;case INPUT_TYPE.PINCH_UPDATE:if(!currentPE||currentPE.irType===ProtoExpectation.RESPONSE_TYPE&&sawFirstUpdate||!currentPE.isNear(event,INPUT_MERGE_THRESHOLD_MS)){currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,PINCH_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);}else{currentPE.pushEvent(event);sawFirstUpdate=true;}break;case INPUT_TYPE.PINCH_END:if(currentPE){currentPE.pushEvent(event);}else{var pe=new ProtoExpectation(ProtoExpectation.IGNORED_TYPE);pe.pushEvent(event);protoExpectations.push(pe);}currentPE=undefined;break;}});return protoExpectations;}function handleFlingEvents(modelHelper,sortedInputEvents){var protoExpectations=[];var currentPE=undefined;function isRendererFling(event){return event.title===RENDERER_FLING_TITLE;}var browserHelper=modelHelper.browserHelper;var flingEvents=browserHelper.getAllAsyncSlicesMatching(isRendererFling);forEventTypesIn(sortedInputEvents,FLING_TYPE_NAMES,function(event){flingEvents.push(event);});flingEvents.sort(compareEvents);flingEvents.forEach(function(event){if(event.title===RENDERER_FLING_TITLE){if(currentPE){currentPE.pushEvent(event);}else{currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,FLING_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);}return;}switch(event.typeName){case INPUT_TYPE.FLING_START:if(currentPE){console.assert(true,'Another FlingStart? File a bug with this trace!');currentPE.pushEvent(event);}else{currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,FLING_IR_NAME);currentPE.pushEvent(event);currentPE.end=0;protoExpectations.push(currentPE);}break;case INPUT_TYPE.FLING_CANCEL:if(currentPE){currentPE.pushEvent(event);currentPE.end=event.start;currentPE=undefined;}else{var pe=new ProtoExpectation(ProtoExpectation.IGNORED_TYPE);pe.pushEvent(event);protoExpectations.push(pe);}break;}});if(currentPE&&!currentPE.end)currentPE.end=modelHelper.model.bounds.max;return protoExpectations;}function handleTouchEvents(modelHelper,sortedInputEvents){var protoExpectations=[];var currentPE=undefined;var sawFirstMove=false;forEventTypesIn(sortedInputEvents,TOUCH_TYPE_NAMES,function(event){switch(event.typeName){case INPUT_TYPE.TOUCH_START:if(currentPE){currentPE.pushEvent(event);}else{currentPE=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,TOUCH_IR_NAME);currentPE.pushEvent(event);currentPE.isAnimationBegin=true;protoExpectations.push(currentPE);sawFirstMove=false;}break;case INPUT_TYPE.TOUCH_MOVE:if(!currentPE){currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,TOUCH_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);break;}if(sawFirstMove&&currentPE.irType===ProtoExpectation.RESPONSE_TYPE||!currentPE.isNear(event,INPUT_MERGE_THRESHOLD_MS)){var prevEnd=currentPE.end;currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,TOUCH_IR_NAME);currentPE.pushEvent(event);currentPE.start=prevEnd;protoExpectations.push(currentPE);}else{currentPE.pushEvent(event);sawFirstMove=true;}break;case INPUT_TYPE.TOUCH_END:if(!currentPE){var pe=new ProtoExpectation(ProtoExpectation.IGNORED_TYPE);pe.pushEvent(event);protoExpectations.push(pe);break;}if(currentPE.isNear(event,INPUT_MERGE_THRESHOLD_MS)){currentPE.pushEvent(event);}else{var pe=new ProtoExpectation(ProtoExpectation.IGNORED_TYPE);pe.pushEvent(event);protoExpectations.push(pe);}currentPE=undefined;break;}});return protoExpectations;}function handleScrollEvents(modelHelper,sortedInputEvents){var protoExpectations=[];var currentPE=undefined;var sawFirstUpdate=false;forEventTypesIn(sortedInputEvents,SCROLL_TYPE_NAMES,function(event){switch(event.typeName){case INPUT_TYPE.SCROLL_BEGIN:currentPE=new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE,SCROLL_IR_NAME);currentPE.pushEvent(event);currentPE.isAnimationBegin=true;protoExpectations.push(currentPE);sawFirstUpdate=false;break;case INPUT_TYPE.SCROLL_UPDATE:if(currentPE){if(currentPE.isNear(event,INPUT_MERGE_THRESHOLD_MS)&&(currentPE.irType===ProtoExpectation.ANIMATION_TYPE||!sawFirstUpdate)){currentPE.pushEvent(event);sawFirstUpdate=true;}else{currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,SCROLL_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);}}else{currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,SCROLL_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);}break;case INPUT_TYPE.SCROLL_END:if(!currentPE){console.assert(true,'ScrollEnd without ScrollUpdate? '+'File a bug with this trace!');var pe=new ProtoExpectation(ProtoExpectation.IGNORED_TYPE);pe.pushEvent(event);protoExpectations.push(pe);break;}currentPE.pushEvent(event);break;}});return protoExpectations;}function handleVideoAnimations(modelHelper,sortedInputEvents){var events=[];for(var pid in modelHelper.rendererHelpers){for(var asyncSlice of modelHelper.rendererHelpers[pid].mainThread.asyncSliceGroup.slices){if(asyncSlice.title===PLAYBACK_EVENT_TITLE)events.push(asyncSlice);}}events.sort(tr.importer.compareEvents);var protoExpectations=[];for(var event of events){var currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,VIDEO_IR_NAME);currentPE.start=event.start;currentPE.end=event.end;currentPE.pushEvent(event);protoExpectations.push(currentPE);}return protoExpectations;}function handleCSSAnimations(modelHelper,sortedInputEvents){var animationEvents=modelHelper.browserHelper.getAllAsyncSlicesMatching(function(event){return event.title===CSS_ANIMATION_TITLE&&event.isTopLevel&&event.duration>0;});var animationRanges=[];function pushAnimationRange(start,end,animation){var range=tr.b.Range.fromExplicitRange(start,end);range.animation=animation;animationRanges.push(range);}animationEvents.forEach(function(animation){if(animation.subSlices.length===0){pushAnimationRange(animation.start,animation.end,animation);}else{var start=undefined;animation.subSlices.forEach(function(sub){if(sub.args.data.state==='running'&&start===undefined){start=sub.start;}else if(sub.args.data.state==='paused'||sub.args.data.state==='idle'||sub.args.data.state==='finished'){if(start===undefined){start=modelHelper.model.bounds.min;}pushAnimationRange(start,sub.start,animation);start=undefined;}});if(start!==undefined)pushAnimationRange(start,animation.end,animation);}});return animationRanges.map(function(range){var protoExpectation=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,CSS_IR_NAME);protoExpectation.start=range.min;protoExpectation.end=range.max;protoExpectation.associatedEvents.push(range.animation);return protoExpectation;});}function findWebGLEvents(modelHelper,mailboxEvents,animationEvents){for(var event of modelHelper.model.getDescendantEvents()){if(event.title==='DrawingBuffer::prepareMailbox')mailboxEvents.push(event);else if(event.title==='PageAnimator::serviceScriptedAnimations')animationEvents.push(event);}}function findMailboxEventsNearAnimationEvents(mailboxEvents,animationEvents){if(animationEvents.length===0)return[];mailboxEvents.sort(compareEvents);animationEvents.sort(compareEvents);var animationIterator=animationEvents[Symbol.iterator]();var animationEvent=animationIterator.next().value;var filteredEvents=[];for(var event of mailboxEvents){while(animationEvent&&animationEvent.start<event.start-ANIMATION_MERGE_THRESHOLD_MS)animationEvent=animationIterator.next().value;if(!animationEvent)break;if(animationEvent.start<event.start+ANIMATION_MERGE_THRESHOLD_MS)filteredEvents.push(event);}return filteredEvents;}function createProtoExpectationsFromMailboxEvents(mailboxEvents){var protoExpectations=[];var currentPE=undefined;for(var event of mailboxEvents){if(currentPE===undefined||!currentPE.isNear(event,ANIMATION_MERGE_THRESHOLD_MS)){currentPE=new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE,WEBGL_IR_NAME);currentPE.pushEvent(event);protoExpectations.push(currentPE);}else{currentPE.pushEvent(event);}}return protoExpectations;}function handleWebGLAnimations(modelHelper,sortedInputEvents){var prepareMailboxEvents=[];var scriptedAnimationEvents=[];findWebGLEvents(modelHelper,prepareMailboxEvents,scriptedAnimationEvents);var webGLMailboxEvents=findMailboxEventsNearAnimationEvents(prepareMailboxEvents,scriptedAnimationEvents);return createProtoExpectationsFromMailboxEvents(webGLMailboxEvents);}function postProcessProtoExpectations(modelHelper,protoExpectations){protoExpectations=findFrameEventsForAnimations(modelHelper,protoExpectations);protoExpectations=mergeIntersectingResponses(protoExpectations);protoExpectations=mergeIntersectingAnimations(protoExpectations);protoExpectations=fixResponseAnimationStarts(protoExpectations);protoExpectations=fixTapResponseTouchAnimations(protoExpectations);return protoExpectations;}function mergeIntersectingResponses(protoExpectations){var newPEs=[];while(protoExpectations.length){var pe=protoExpectations.shift();newPEs.push(pe);if(pe.irType!==ProtoExpectation.RESPONSE_TYPE)continue;for(var i=0;i<protoExpectations.length;++i){var otherPE=protoExpectations[i];if(otherPE.irType!==pe.irType)continue;if(!otherPE.intersects(pe))continue;var typeNames=pe.associatedEvents.map(function(event){return event.typeName;});if(otherPE.containsTypeNames(typeNames))continue;pe.merge(otherPE);protoExpectations.splice(i,1);--i;}}return newPEs;}function mergeIntersectingAnimations(protoExpectations){var newPEs=[];while(protoExpectations.length){var pe=protoExpectations.shift();newPEs.push(pe);if(pe.irType!==ProtoExpectation.ANIMATION_TYPE)continue;var isCSS=pe.containsSliceTitle(CSS_ANIMATION_TITLE);var isFling=pe.containsTypeNames([INPUT_TYPE.FLING_START]);var isVideo=pe.containsTypeNames([VIDEO_IR_NAME]);for(var i=0;i<protoExpectations.length;++i){var otherPE=protoExpectations[i];if(otherPE.irType!==pe.irType)continue;if(isCSS!=otherPE.containsSliceTitle(CSS_ANIMATION_TITLE))continue;if(isCSS){if(!pe.isNear(otherPE,ANIMATION_MERGE_THRESHOLD_MS))continue;}else if(!otherPE.intersects(pe)){continue;}if(isFling!==otherPE.containsTypeNames([INPUT_TYPE.FLING_START]))continue;if(isVideo!==otherPE.containsTypeNames([VIDEO_IR_NAME]))continue;pe.merge(otherPE);protoExpectations.splice(i,1);--i;}}return newPEs;}function fixResponseAnimationStarts(protoExpectations){protoExpectations.forEach(function(ape){if(ape.irType!==ProtoExpectation.ANIMATION_TYPE)return;protoExpectations.forEach(function(rpe){if(rpe.irType!==ProtoExpectation.RESPONSE_TYPE)return;if(!ape.containsTimestampInclusive(rpe.end))return;if(ape.containsTimestampInclusive(rpe.start))return;ape.start=rpe.end;});});return protoExpectations;}function fixTapResponseTouchAnimations(protoExpectations){function isTapResponse(pe){return pe.irType===ProtoExpectation.RESPONSE_TYPE&&pe.containsTypeNames([INPUT_TYPE.TAP]);}function isTouchAnimation(pe){return pe.irType===ProtoExpectation.ANIMATION_TYPE&&pe.containsTypeNames([INPUT_TYPE.TOUCH_MOVE])&&!pe.containsTypeNames([INPUT_TYPE.SCROLL_UPDATE,INPUT_TYPE.PINCH_UPDATE]);}var newPEs=[];while(protoExpectations.length){var pe=protoExpectations.shift();newPEs.push(pe);var peIsTapResponse=isTapResponse(pe);var peIsTouchAnimation=isTouchAnimation(pe);if(!peIsTapResponse&&!peIsTouchAnimation)continue;for(var i=0;i<protoExpectations.length;++i){var otherPE=protoExpectations[i];if(!otherPE.intersects(pe))continue;if(peIsTapResponse&&!isTouchAnimation(otherPE))continue;if(peIsTouchAnimation&&!isTapResponse(otherPE))continue;pe.irType=ProtoExpectation.RESPONSE_TYPE;pe.merge(otherPE);protoExpectations.splice(i,1);--i;}}return newPEs;}function findFrameEventsForAnimations(modelHelper,protoExpectations){var newPEs=[];var frameEventsByPid=getSortedFrameEventsByProcess(modelHelper);for(var pe of protoExpectations){if(pe.irType!==ProtoExpectation.ANIMATION_TYPE){newPEs.push(pe);continue;}var frameEvents=[];for(var pid of Object.keys(modelHelper.rendererHelpers)){var range=tr.b.Range.fromExplicitRange(pe.start,pe.end);frameEvents.push.apply(frameEvents,range.filterArray(frameEventsByPid[pid],e=>e.start));}if(frameEvents.length===0&&!pe.names.has(WEBGL_IR_NAME)){pe.irType=ProtoExpectation.IGNORED_TYPE;newPEs.push(pe);continue;}pe.associatedEvents.addEventSet(frameEvents);newPEs.push(pe);}return newPEs;}function checkAllInputEventsHandled(sortedInputEvents,protoExpectations){var handledEvents=[];protoExpectations.forEach(function(protoExpectation){protoExpectation.associatedEvents.forEach(function(event){if(event.title===CSS_ANIMATION_TITLE&&event.subSlices.length>0)return;if(handledEvents.indexOf(event)>=0&&event.title!==tr.model.helpers.IMPL_RENDERING_STATS){console.assert(true,'double-handled event',event.typeName,parseInt(event.start),parseInt(event.end),protoExpectation);return;}handledEvents.push(event);});});sortedInputEvents.forEach(function(event){if(handledEvents.indexOf(event)<0){console.assert(true,'UNHANDLED INPUT EVENT!',event.typeName,parseInt(event.start),parseInt(event.end));}});}function findInputExpectations(modelHelper){var sortedInputEvents=getSortedInputEvents(modelHelper);var protoExpectations=findProtoExpectations(modelHelper,sortedInputEvents);protoExpectations=postProcessProtoExpectations(modelHelper,protoExpectations);checkAllInputEventsHandled(sortedInputEvents,protoExpectations);var irs=[];protoExpectations.forEach(function(protoExpectation){var ir=protoExpectation.createInteractionRecord(modelHelper.model);if(ir)irs.push(ir);});return irs;}return{findInputExpectations:findInputExpectations,compareEvents:compareEvents,CSS_ANIMATION_TITLE:CSS_ANIMATION_TITLE};});
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/range_utils.js":54,"../extras/chrome/cc/input_latency_async_slice.js":68,"./proto_expectation.js":83}],79:[function(require,module,exports){
+(function (global){
 "use strict";require("../model/user_model/load_expectation.js");'use strict';global.tr.exportTo('tr.importer',function(){var NAVIGATION_START='NavigationTiming navigationStart';var FIRST_CONTENTFUL_PAINT_TITLE='firstContentfulPaint';function findLoadExpectations(modelHelper){var events=[];for(var event of modelHelper.model.getDescendantEvents()){if(event.title===NAVIGATION_START||event.title===FIRST_CONTENTFUL_PAINT_TITLE)events.push(event);}events.sort(tr.importer.compareEvents);var loads=[];var startEvent=undefined;for(var event of events){if(event.title===NAVIGATION_START){startEvent=event;}else if(event.title===FIRST_CONTENTFUL_PAINT_TITLE){if(startEvent){loads.push(new tr.model.um.LoadExpectation(modelHelper.model,tr.model.um.LOAD_SUBTYPE_NAMES.SUCCESSFUL,startEvent.start,event.start-startEvent.start));startEvent=undefined;}}}if(startEvent){loads.push(new tr.model.um.LoadExpectation(modelHelper.model,tr.model.um.LOAD_SUBTYPE_NAMES.SUCCESSFUL,startEvent.start,modelHelper.model.bounds.max-startEvent.start));}return loads;}return{findLoadExpectations:findLoadExpectations};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../model/user_model/load_expectation.js":168}],79:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../model/user_model/load_expectation.js":169}],80:[function(require,module,exports){
+(function (global){
 "use strict";require("../model/user_model/startup_expectation.js");'use strict';global.tr.exportTo('tr.importer',function(){function getAllFrameEvents(modelHelper){var frameEvents=[];frameEvents.push.apply(frameEvents,modelHelper.browserHelper.getFrameEventsInRange(tr.model.helpers.IMPL_FRAMETIME_TYPE,modelHelper.model.bounds));tr.b.iterItems(modelHelper.rendererHelpers,function(pid,renderer){frameEvents.push.apply(frameEvents,renderer.getFrameEventsInRange(tr.model.helpers.IMPL_FRAMETIME_TYPE,modelHelper.model.bounds));});return frameEvents.sort(tr.importer.compareEvents);}function getStartupEvents(modelHelper){function isStartupSlice(slice){return slice.title==='BrowserMainLoop::CreateThreads';}var events=modelHelper.browserHelper.getAllAsyncSlicesMatching(isStartupSlice);var deduper=new tr.model.EventSet();events.forEach(function(event){var sliceGroup=event.parentContainer.sliceGroup;var slice=sliceGroup&&sliceGroup.findFirstSlice();if(slice)deduper.push(slice);});return deduper.toArray();}function findStartupExpectations(modelHelper){var openingEvents=getStartupEvents(modelHelper);var closingEvents=getAllFrameEvents(modelHelper);var startups=[];openingEvents.forEach(function(openingEvent){closingEvents.forEach(function(closingEvent){if(openingEvent.closingEvent)return;if(closingEvent.openingEvent)return;if(closingEvent.start<=openingEvent.start)return;if(openingEvent.parentContainer.parent.pid!==closingEvent.parentContainer.parent.pid)return;openingEvent.closingEvent=closingEvent;closingEvent.openingEvent=openingEvent;var se=new tr.model.um.StartupExpectation(modelHelper.model,openingEvent.start,closingEvent.end-openingEvent.start);se.associatedEvents.push(openingEvent);se.associatedEvents.push(closingEvent);startups.push(se);});});return startups;}return{findStartupExpectations:findStartupExpectations};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../model/user_model/startup_expectation.js":170}],80:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../model/user_model/startup_expectation.js":171}],81:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/base.js");require("../base/timing.js");require("./empty_importer.js");require("./importer.js");require("./user_model_builder.js");require("../ui/base/overlay.js");'use strict';global.tr.exportTo('tr.importer',function(){var Timing=tr.b.Timing;function ImportOptions(){this.shiftWorldToZero=true;this.pruneEmptyContainers=true;this.showImportWarnings=true;this.trackDetailedModelStats=false;this.customizeModelCallback=undefined;var auditorTypes=tr.c.Auditor.getAllRegisteredTypeInfos();this.auditorConstructors=auditorTypes.map(function(typeInfo){return typeInfo.constructor;});}function Import(model,opt_options){if(model===undefined)throw new Error('Must provide model to import into.');this.importing_=false;this.importOptions_=opt_options||new ImportOptions();this.model_=model;this.model_.importOptions=this.importOptions_;}Import.prototype={__proto__:Object.prototype,importTraces:function(traces){var progressMeter={update:function(msg){}};tr.b.Task.RunSynchronously(this.createImportTracesTask(progressMeter,traces));},importTracesWithProgressDialog:function(traces){if(tr.isHeadless)throw new Error('Cannot use this method in headless mode.');var overlay=tr.ui.b.Overlay();overlay.title='Importing...';overlay.userCanClose=false;overlay.msgEl=document.createElement('div');Polymer.dom(overlay).appendChild(overlay.msgEl);overlay.msgEl.style.margin='20px';overlay.update=function(msg){Polymer.dom(this.msgEl).textContent=msg;};overlay.visible=true;var promise=tr.b.Task.RunWhenIdle(this.createImportTracesTask(overlay,traces));promise.then(function(){overlay.visible=false;},function(err){overlay.visible=false;});return promise;},createImportTracesTask:function(progressMeter,traces){if(this.importing_)throw new Error('Already importing.');this.importing_=true;var importTask=new tr.b.Task(function prepareImport(){progressMeter.update('I will now import your traces for you...');},this);var lastTask=importTask;var importers=[];lastTask=lastTask.timedAfter('TraceImport',function createImports(){traces=traces.slice(0);progressMeter.update('Creating importers...');for(var i=0;i<traces.length;++i)importers.push(this.createImporter_(traces[i]));for(var i=0;i<importers.length;i++){var subtraces=importers[i].extractSubtraces();for(var j=0;j<subtraces.length;j++){try{traces.push(subtraces[j]);importers.push(this.createImporter_(subtraces[j]));}catch(error){console.warn(error.name+': '+error.message);continue;}}}if(traces.length&&!this.hasEventDataDecoder_(importers)){throw new Error('Could not find an importer for the provided eventData.');}importers.sort(function(x,y){return x.importPriority-y.importPriority;});},this);lastTask=lastTask.timedAfter('TraceImport',function importClockSyncMarkers(task){importers.forEach(function(importer,index){task.subTask(Timing.wrapNamedFunction('TraceImport',importer.importerName,function runImportClockSyncMarkersOnOneImporter(){progressMeter.update('Importing clock sync markers '+(index+1)+' of '+importers.length);importer.importClockSyncMarkers();}),this);},this);},this);lastTask=lastTask.timedAfter('TraceImport',function runImport(task){importers.forEach(function(importer,index){task.subTask(Timing.wrapNamedFunction('TraceImport',importer.importerName,function runImportEventsOnOneImporter(){progressMeter.update('Importing '+(index+1)+' of '+importers.length);importer.importEvents();}),this);},this);},this);if(this.importOptions_.customizeModelCallback){lastTask=lastTask.timedAfter('TraceImport',function runCustomizeCallbacks(task){this.importOptions_.customizeModelCallback(this.model_);},this);}lastTask=lastTask.timedAfter('TraceImport',function importSampleData(task){importers.forEach(function(importer,index){progressMeter.update('Importing sample data '+(index+1)+'/'+importers.length);importer.importSampleData();},this);},this);lastTask=lastTask.timedAfter('TraceImport',function runAutoclosers(){progressMeter.update('Autoclosing open slices...');this.model_.autoCloseOpenSlices();this.model_.createSubSlices();},this);lastTask=lastTask.timedAfter('TraceImport',function finalizeImport(task){importers.forEach(function(importer,index){progressMeter.update('Finalizing import '+(index+1)+'/'+importers.length);importer.finalizeImport();},this);},this);lastTask=lastTask.timedAfter('TraceImport',function runPreinits(){progressMeter.update('Initializing objects (step 1/2)...');this.model_.preInitializeObjects();},this);if(this.importOptions_.pruneEmptyContainers){lastTask=lastTask.timedAfter('TraceImport',function runPruneEmptyContainers(){progressMeter.update('Pruning empty containers...');this.model_.pruneEmptyContainers();},this);}lastTask=lastTask.timedAfter('TraceImport',function runMergeKernelWithuserland(){progressMeter.update('Merging kernel with userland...');this.model_.mergeKernelWithUserland();},this);var auditors=[];lastTask=lastTask.timedAfter('TraceImport',function createAuditorsAndRunAnnotate(){progressMeter.update('Adding arbitrary data to model...');auditors=this.importOptions_.auditorConstructors.map(function(auditorConstructor){return new auditorConstructor(this.model_);},this);auditors.forEach(function(auditor){auditor.runAnnotate();auditor.installUserFriendlyCategoryDriverIfNeeded();});},this);lastTask=lastTask.timedAfter('TraceImport',function computeWorldBounds(){progressMeter.update('Computing final world bounds...');this.model_.computeWorldBounds(this.importOptions_.shiftWorldToZero);},this);lastTask=lastTask.timedAfter('TraceImport',function buildFlowEventIntervalTree(){progressMeter.update('Building flow event map...');this.model_.buildFlowEventIntervalTree();},this);lastTask=lastTask.timedAfter('TraceImport',function joinRefs(){progressMeter.update('Joining object refs...');this.model_.joinRefs();},this);lastTask=lastTask.timedAfter('TraceImport',function cleanupUndeletedObjects(){progressMeter.update('Cleaning up undeleted objects...');this.model_.cleanupUndeletedObjects();},this);lastTask=lastTask.timedAfter('TraceImport',function sortMemoryDumps(){progressMeter.update('Sorting memory dumps...');this.model_.sortMemoryDumps();},this);lastTask=lastTask.timedAfter('TraceImport',function finalizeMemoryGraphs(){progressMeter.update('Finalizing memory dump graphs...');this.model_.finalizeMemoryGraphs();},this);lastTask=lastTask.timedAfter('TraceImport',function initializeObjects(){progressMeter.update('Initializing objects (step 2/2)...');this.model_.initializeObjects();},this);lastTask=lastTask.timedAfter('TraceImport',function buildEventIndices(){progressMeter.update('Building event indices...');this.model_.buildEventIndices();},this);lastTask=lastTask.timedAfter('TraceImport',function buildUserModel(){progressMeter.update('Building UserModel...');var userModelBuilder=new tr.importer.UserModelBuilder(this.model_);userModelBuilder.buildUserModel();},this);lastTask=lastTask.timedAfter('TraceImport',function sortExpectations(){progressMeter.update('Sorting user expectations...');this.model_.userModel.sortExpectations();},this);lastTask=lastTask.timedAfter('TraceImport',function runAudits(){progressMeter.update('Running auditors...');auditors.forEach(function(auditor){auditor.runAudit();});},this);lastTask=lastTask.timedAfter('TraceImport',function sortAlerts(){progressMeter.update('Updating alerts...');this.model_.sortAlerts();},this);lastTask=lastTask.timedAfter('TraceImport',function lastUpdateBounds(){progressMeter.update('Update bounds...');this.model_.updateBounds();},this);lastTask=lastTask.timedAfter('TraceImport',function addModelWarnings(){progressMeter.update('Looking for warnings...');if(!this.model_.isTimeHighResolution){this.model_.importWarning({type:'low_resolution_timer',message:'Trace time is low resolution, trace may be unusable.',showToUser:true});}},this);lastTask.after(function(){this.importing_=false;},this);return importTask;},createImporter_:function(eventData){var importerConstructor=tr.importer.Importer.findImporterFor(eventData);if(!importerConstructor){throw new Error('Couldn\'t create an importer for the provided '+'eventData.');}return new importerConstructor(this.model_,eventData);},hasEventDataDecoder_:function(importers){for(var i=0;i<importers.length;++i){if(!importers[i].isTraceDataContainer())return true;}return false;}};return{ImportOptions:ImportOptions,Import:Import};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/base.js":33,"../base/timing.js":61,"../ui/base/overlay.js":179,"./empty_importer.js":76,"./importer.js":81,"./user_model_builder.js":83}],81:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/base.js":34,"../base/timing.js":62,"../ui/base/overlay.js":180,"./empty_importer.js":77,"./importer.js":82,"./user_model_builder.js":84}],82:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/base.js");require("../base/extension_registry.js");'use strict';global.tr.exportTo('tr.importer',function(){function Importer(){}Importer.prototype={__proto__:Object.prototype,get importerName(){return'Importer';},isTraceDataContainer:function(){return false;},extractSubtraces:function(){return[];},importClockSyncMarkers:function(){},importEvents:function(){},importSampleData:function(){},finalizeImport:function(){}};var options=new tr.b.ExtensionRegistryOptions(tr.b.BASIC_REGISTRY_MODE);options.defaultMetadata={};options.mandatoryBaseClass=Importer;tr.b.decorateExtensionRegistry(Importer,options);Importer.findImporterFor=function(eventData){var typeInfo=Importer.findTypeInfoMatching(function(ti){return ti.constructor.canImport(eventData);});if(typeInfo)return typeInfo.constructor;return undefined;};return{Importer:Importer};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/base.js":33,"../base/extension_registry.js":40}],82:[function(require,module,exports){
-(function(global){
-"use strict";require("../base/base.js");require("../base/range_utils.js");require("../core/auditor.js");require("../model/event_info.js");require("../model/user_model/animation_expectation.js");require("../model/user_model/response_expectation.js");'use strict';global.tr.exportTo('tr.importer',function(){function ProtoExpectation(irType,name){this.irType=irType;this.names=new Set(name?[name]:undefined);this.start=Infinity;this.end=-Infinity;this.associatedEvents=new tr.model.EventSet();this.isAnimationBegin=false;}ProtoExpectation.RESPONSE_TYPE='r';ProtoExpectation.ANIMATION_TYPE='a';ProtoExpectation.IGNORED_TYPE='ignored';ProtoExpectation.prototype={get isValid(){return this.end>this.start;},containsTypeNames:function(typeNames){return this.associatedEvents.some(x=>typeNames.indexOf(x.typeName)>=0);},containsSliceTitle:function(title){return this.associatedEvents.some(x=>title===x.title);},createInteractionRecord:function(model){if(!this.isValid){console.error('Invalid ProtoExpectation: '+this.debug()+' File a bug with this trace!');return undefined;}var initiatorTitles=[];this.names.forEach(function(name){initiatorTitles.push(name);});initiatorTitles=initiatorTitles.sort().join(',');var duration=this.end-this.start;var ir=undefined;switch(this.irType){case ProtoExpectation.RESPONSE_TYPE:ir=new tr.model.um.ResponseExpectation(model,initiatorTitles,this.start,duration,this.isAnimationBegin);break;case ProtoExpectation.ANIMATION_TYPE:ir=new tr.model.um.AnimationExpectation(model,initiatorTitles,this.start,duration);break;}if(!ir)return undefined;ir.sourceEvents.addEventSet(this.associatedEvents);function pushAssociatedEvents(event){ir.associatedEvents.push(event);if(event.associatedEvents)ir.associatedEvents.addEventSet(event.associatedEvents);}this.associatedEvents.forEach(function(event){pushAssociatedEvents(event);if(event.subSlices)event.subSlices.forEach(pushAssociatedEvents);});return ir;},merge:function(other){other.names.forEach(function(name){this.names.add(name);}.bind(this));this.associatedEvents.addEventSet(other.associatedEvents);this.start=Math.min(this.start,other.start);this.end=Math.max(this.end,other.end);if(other.isAnimationBegin)this.isAnimationBegin=true;},pushEvent:function(event){this.start=Math.min(this.start,event.start);this.end=Math.max(this.end,event.end);this.associatedEvents.push(event);},containsTimestampInclusive:function(timestamp){return this.start<=timestamp&&timestamp<=this.end;},intersects:function(other){return other.start<this.end&&other.end>this.start;},isNear:function(event,threshold){return this.end+threshold>event.start;},debug:function(){var debugString=this.irType+'(';debugString+=parseInt(this.start)+' ';debugString+=parseInt(this.end);this.associatedEvents.forEach(function(event){debugString+=' '+event.typeName;});return debugString+')';}};return{ProtoExpectation:ProtoExpectation};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/base.js":33,"../base/range_utils.js":53,"../core/auditor.js":65,"../model/event_info.js":123,"../model/user_model/animation_expectation.js":166,"../model/user_model/response_expectation.js":169}],83:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/base.js":34,"../base/extension_registry.js":41}],83:[function(require,module,exports){
+(function (global){
+"use strict";require("../base/base.js");require("../base/range_utils.js");require("../core/auditor.js");require("../model/event_info.js");require("../model/user_model/animation_expectation.js");require("../model/user_model/response_expectation.js");'use strict';global.tr.exportTo('tr.importer',function(){function ProtoExpectation(irType,name){this.irType=irType;this.names=new Set(name?[name]:undefined);this.start=Infinity;this.end=-Infinity;this.associatedEvents=new tr.model.EventSet();this.isAnimationBegin=false;}ProtoExpectation.RESPONSE_TYPE='r';ProtoExpectation.ANIMATION_TYPE='a';ProtoExpectation.IGNORED_TYPE='ignored';ProtoExpectation.prototype={get isValid(){return this.end>this.start;},containsTypeNames:function(typeNames){return this.associatedEvents.some(x=>typeNames.indexOf(x.typeName)>=0);},containsSliceTitle:function(title){return this.associatedEvents.some(x=>title===x.title);},createInteractionRecord:function(model){if(!this.isValid){console.assert(true,'Invalid ProtoExpectation: '+this.debug()+' File a bug with this trace!');return undefined;}var initiatorTitles=[];this.names.forEach(function(name){initiatorTitles.push(name);});initiatorTitles=initiatorTitles.sort().join(',');var duration=this.end-this.start;var ir=undefined;switch(this.irType){case ProtoExpectation.RESPONSE_TYPE:ir=new tr.model.um.ResponseExpectation(model,initiatorTitles,this.start,duration,this.isAnimationBegin);break;case ProtoExpectation.ANIMATION_TYPE:ir=new tr.model.um.AnimationExpectation(model,initiatorTitles,this.start,duration);break;}if(!ir)return undefined;ir.sourceEvents.addEventSet(this.associatedEvents);function pushAssociatedEvents(event){ir.associatedEvents.push(event);if(event.associatedEvents)ir.associatedEvents.addEventSet(event.associatedEvents);}this.associatedEvents.forEach(function(event){pushAssociatedEvents(event);if(event.subSlices)event.subSlices.forEach(pushAssociatedEvents);});return ir;},merge:function(other){other.names.forEach(function(name){this.names.add(name);}.bind(this));this.associatedEvents.addEventSet(other.associatedEvents);this.start=Math.min(this.start,other.start);this.end=Math.max(this.end,other.end);if(other.isAnimationBegin)this.isAnimationBegin=true;},pushEvent:function(event){this.start=Math.min(this.start,event.start);this.end=Math.max(this.end,event.end);this.associatedEvents.push(event);},containsTimestampInclusive:function(timestamp){return this.start<=timestamp&&timestamp<=this.end;},intersects:function(other){return other.start<this.end&&other.end>this.start;},isNear:function(event,threshold){return this.end+threshold>event.start;},debug:function(){var debugString=this.irType+'(';debugString+=parseInt(this.start)+' ';debugString+=parseInt(this.end);this.associatedEvents.forEach(function(event){debugString+=' '+event.typeName;});return debugString+')';}};return{ProtoExpectation:ProtoExpectation};});
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/base.js":34,"../base/range_utils.js":54,"../core/auditor.js":66,"../model/event_info.js":124,"../model/user_model/animation_expectation.js":167,"../model/user_model/response_expectation.js":170}],84:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/base.js");require("../base/range_utils.js");require("../core/auditor.js");require("../extras/chrome/cc/input_latency_async_slice.js");require("./find_input_expectations.js");require("./find_load_expectations.js");require("./find_startup_expectations.js");require("../model/event_info.js");require("../model/ir_coverage.js");require("../model/user_model/idle_expectation.js");'use strict';global.tr.exportTo('tr.importer',function(){var INSIGNIFICANT_MS=1;function UserModelBuilder(model){this.model=model;this.modelHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);};UserModelBuilder.supportsModelHelper=function(modelHelper){return modelHelper.browserHelper!==undefined;};UserModelBuilder.prototype={buildUserModel:function(){if(!this.modelHelper||!this.modelHelper.browserHelper)return;var expectations=undefined;try{expectations=this.findUserExpectations();}catch(error){this.model.importWarning({type:'UserModelBuilder',message:error,showToUser:true});return;}expectations.forEach(function(expectation){this.model.userModel.expectations.push(expectation);},this);},findUserExpectations:function(){var expectations=[];expectations.push.apply(expectations,tr.importer.findStartupExpectations(this.modelHelper));expectations.push.apply(expectations,tr.importer.findLoadExpectations(this.modelHelper));expectations.push.apply(expectations,tr.importer.findInputExpectations(this.modelHelper));expectations.push.apply(expectations,this.findIdleExpectations(expectations));this.collectUnassociatedEvents_(expectations);return expectations;},collectUnassociatedEvents_:function(rirs){var vacuumIRs=[];rirs.forEach(function(ir){if(ir instanceof tr.model.um.IdleExpectation||ir instanceof tr.model.um.LoadExpectation||ir instanceof tr.model.um.StartupExpectation)vacuumIRs.push(ir);});if(vacuumIRs.length===0)return;var allAssociatedEvents=tr.model.getAssociatedEvents(rirs);var unassociatedEvents=tr.model.getUnassociatedEvents(this.model,allAssociatedEvents);unassociatedEvents.forEach(function(event){if(!(event instanceof tr.model.ThreadSlice))return;if(!event.isTopLevel)return;for(var iri=0;iri<vacuumIRs.length;++iri){var ir=vacuumIRs[iri];if(event.start>=ir.start&&event.start<ir.end){ir.associatedEvents.addEventSet(event.entireHierarchy);return;}}});},findIdleExpectations:function(otherIRs){if(this.model.bounds.isEmpty)return;var emptyRanges=tr.b.findEmptyRangesBetweenRanges(tr.b.convertEventsToRanges(otherIRs),this.model.bounds);var irs=[];var model=this.model;emptyRanges.forEach(function(range){if(range.max<range.min+INSIGNIFICANT_MS)return;irs.push(new tr.model.um.IdleExpectation(model,range.min,range.max-range.min));});return irs;}};function createCustomizeModelLinesFromModel(model){var modelLines=[];modelLines.push('      audits.addEvent(model.browserMain,');modelLines.push('          {title: \'model start\', start: 0, end: 1});');var typeNames={};for(var typeName in tr.e.cc.INPUT_EVENT_TYPE_NAMES){typeNames[tr.e.cc.INPUT_EVENT_TYPE_NAMES[typeName]]=typeName;}var modelEvents=new tr.model.EventSet();model.userModel.expectations.forEach(function(ir,index){modelEvents.addEventSet(ir.sourceEvents);});modelEvents=modelEvents.toArray();modelEvents.sort(tr.importer.compareEvents);modelEvents.forEach(function(event){var startAndEnd='start: '+parseInt(event.start)+', '+'end: '+parseInt(event.end)+'});';if(event instanceof tr.e.cc.InputLatencyAsyncSlice){modelLines.push('      audits.addInputEvent(model, INPUT_TYPE.'+typeNames[event.typeName]+',');}else if(event.title==='RenderFrameImpl::didCommitProvisionalLoad'){modelLines.push('      audits.addCommitLoadEvent(model,');}else if(event.title==='InputHandlerProxy::HandleGestureFling::started'){modelLines.push('      audits.addFlingAnimationEvent(model,');}else if(event.title===tr.model.helpers.IMPL_RENDERING_STATS){modelLines.push('      audits.addFrameEvent(model,');}else if(event.title===tr.importer.CSS_ANIMATION_TITLE){modelLines.push('      audits.addEvent(model.rendererMain, {');modelLines.push('        title: \'Animation\', '+startAndEnd);return;}else{throw'You must extend createCustomizeModelLinesFromModel()'+'to support this event:\n'+event.title+'\n';}modelLines.push('          {'+startAndEnd);});modelLines.push('      audits.addEvent(model.browserMain,');modelLines.push('          {'+'title: \'model end\', '+'start: '+(parseInt(model.bounds.max)-1)+', '+'end: '+parseInt(model.bounds.max)+'});');return modelLines;}function createExpectedIRLinesFromModel(model){var expectedLines=[];var irCount=model.userModel.expectations.length;model.userModel.expectations.forEach(function(ir,index){var irString='      {';irString+='title: \''+ir.title+'\', ';irString+='start: '+parseInt(ir.start)+', ';irString+='end: '+parseInt(ir.end)+', ';irString+='eventCount: '+ir.sourceEvents.length;irString+='}';if(index<irCount-1)irString+=',';expectedLines.push(irString);});return expectedLines;}function createIRFinderTestCaseStringFromModel(model){var filename=window.location.hash.substr(1);var testName=filename.substr(filename.lastIndexOf('/')+1);testName=testName.substr(0,testName.indexOf('.'));try{var testLines=[];testLines.push('  /*');testLines.push('    This test was generated from');testLines.push('    '+filename+'');testLines.push('   */');testLines.push('  test(\''+testName+'\', function() {');testLines.push('    var verifier = new UserExpectationVerifier();');testLines.push('    verifier.customizeModelCallback = function(model) {');testLines.push.apply(testLines,createCustomizeModelLinesFromModel(model));testLines.push('    };');testLines.push('    verifier.expectedIRs = [');testLines.push.apply(testLines,createExpectedIRLinesFromModel(model));testLines.push('    ];');testLines.push('    verifier.verify();');testLines.push('  });');return testLines.join('\n');}catch(error){return error;}}return{UserModelBuilder:UserModelBuilder,createIRFinderTestCaseStringFromModel:createIRFinderTestCaseStringFromModel};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/base.js":33,"../base/range_utils.js":53,"../core/auditor.js":65,"../extras/chrome/cc/input_latency_async_slice.js":67,"../model/event_info.js":123,"../model/ir_coverage.js":136,"../model/user_model/idle_expectation.js":167,"./find_input_expectations.js":77,"./find_load_expectations.js":78,"./find_startup_expectations.js":79}],84:[function(require,module,exports){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/base.js":34,"../base/range_utils.js":54,"../core/auditor.js":66,"../extras/chrome/cc/input_latency_async_slice.js":68,"../model/event_info.js":124,"../model/ir_coverage.js":137,"../model/user_model/idle_expectation.js":168,"./find_input_expectations.js":78,"./find_load_expectations.js":79,"./find_startup_expectations.js":80}],85:[function(require,module,exports){
 "use strict";require("./importer/import.js");require("./model/model.js");require("./extras/lean_config.js");require("./metrics/all_metrics.js");
-},{"./extras/lean_config.js":74,"./importer/import.js":80,"./metrics/all_metrics.js":85,"./model/model.js":140}],85:[function(require,module,exports){
+},{"./extras/lean_config.js":75,"./importer/import.js":81,"./metrics/all_metrics.js":86,"./model/model.js":141}],86:[function(require,module,exports){
 "use strict";require("./blink/gc_metric.js");require("./cpu_process_metric.js");require("./sample_metric.js");require("./system_health/clock_sync_latency_metric.js");require("./system_health/hazard_metric.js");require("./system_health/loading_metric.js");require("./system_health/memory_metric.js");require("./system_health/power_metric.js");require("./system_health/responsiveness_metric.js");require("./system_health/system_health_metrics.js");require("./system_health/webview_startup_metric.js");require("./tracing_metric.js");require("./v8/execution_metric.js");require("./v8/gc_metric.js");require("./v8/v8_metrics.js");
-},{"./blink/gc_metric.js":86,"./cpu_process_metric.js":87,"./sample_metric.js":89,"./system_health/clock_sync_latency_metric.js":90,"./system_health/hazard_metric.js":92,"./system_health/loading_metric.js":93,"./system_health/memory_metric.js":95,"./system_health/power_metric.js":96,"./system_health/responsiveness_metric.js":97,"./system_health/system_health_metrics.js":98,"./system_health/webview_startup_metric.js":100,"./tracing_metric.js":101,"./v8/execution_metric.js":102,"./v8/gc_metric.js":103,"./v8/v8_metrics.js":105}],86:[function(require,module,exports){
-(function(global){
+},{"./blink/gc_metric.js":87,"./cpu_process_metric.js":88,"./sample_metric.js":90,"./system_health/clock_sync_latency_metric.js":91,"./system_health/hazard_metric.js":93,"./system_health/loading_metric.js":94,"./system_health/memory_metric.js":96,"./system_health/power_metric.js":97,"./system_health/responsiveness_metric.js":98,"./system_health/system_health_metrics.js":99,"./system_health/webview_startup_metric.js":101,"./tracing_metric.js":102,"./v8/execution_metric.js":103,"./v8/gc_metric.js":104,"./v8/v8_metrics.js":106}],87:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/range.js");require("../../base/unit.js");require("../metric_registry.js");require("../v8/utils.js");require("../../value/histogram.js");'use strict';global.tr.exportTo('tr.metrics.blink',function(){var BLINK_GC_EVENTS={'BlinkGCMarking':'blink-gc-marking','ThreadState::completeSweep':'blink-gc-complete-sweep','ThreadState::performIdleLazySweep':'blink-gc-idle-lazy-sweep'};function isBlinkGarbageCollectionEvent(event){return event.title in BLINK_GC_EVENTS;}function blinkGarbageCollectionEventName(event){return BLINK_GC_EVENTS[event.title];}function blinkGcMetric(values,model){addDurationOfTopEvents(values,model);addTotalDurationOfTopEvents(values,model);addIdleTimesOfTopEvents(values,model);addTotalIdleTimesOfTopEvents(values,model);}tr.metrics.MetricRegistry.register(blinkGcMetric);var timeDurationInMs_smallerIsBetter=tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;var percentage_biggerIsBetter=tr.b.Unit.byName.normalizedPercentage_biggerIsBetter;var CUSTOM_BOUNDARIES=tr.v.HistogramBinBoundaries.createLinear(0,20,200).addExponentialBins(200,100);function createNumericForTopEventTime(name){var n=new tr.v.Histogram(name,timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);n.customizeSummaryOptions({avg:true,count:true,max:true,min:false,std:true,sum:true,percentile:[0.90]});return n;}function createNumericForIdleTime(name){var n=new tr.v.Histogram(name,timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);n.customizeSummaryOptions({avg:true,count:false,max:true,min:false,std:false,sum:true,percentile:[]});return n;}function createPercentage(name,numerator,denominator){var histogram=new tr.v.Histogram(name,percentage_biggerIsBetter);if(denominator===0)histogram.addSample(0);else histogram.addSample(numerator/denominator);return histogram;}function addDurationOfTopEvents(values,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isBlinkGarbageCollectionEvent,blinkGarbageCollectionEventName,function(name,events){var cpuDuration=createNumericForTopEventTime(name);events.forEach(function(event){cpuDuration.addSample(event.cpuDuration);});values.addHistogram(cpuDuration);});}function addTotalDurationOfTopEvents(values,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isBlinkGarbageCollectionEvent,event=>'blink-gc-total',function(name,events){var cpuDuration=createNumericForTopEventTime(name);events.forEach(function(event){cpuDuration.addSample(event.cpuDuration);});values.addHistogram(cpuDuration);});}function addIdleTimesOfTopEvents(values,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isBlinkGarbageCollectionEvent,blinkGarbageCollectionEventName,function(name,events){addIdleTimes(values,model,name,events);});}function addTotalIdleTimesOfTopEvents(values,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isBlinkGarbageCollectionEvent,event=>'blink-gc-total',function(name,events){addIdleTimes(values,model,name,events);});}function addIdleTimes(values,model,name,events){var cpuDuration=createNumericForIdleTime(name+'_cpu');var insideIdle=createNumericForIdleTime(name+'_inside_idle');var outsideIdle=createNumericForIdleTime(name+'_outside_idle');var idleDeadlineOverrun=createNumericForIdleTime(name+'_idle_deadline_overrun');events.forEach(function(event){var idleTask=tr.metrics.v8.utils.findParent(event,tr.metrics.v8.utils.isIdleTask);var inside=0;var overrun=0;if(idleTask){var allottedTime=idleTask['args']['allotted_time_ms'];if(event.duration>allottedTime){overrun=event.duration-allottedTime;inside=event.cpuDuration*allottedTime/event.duration;}else{inside=event.cpuDuration;}}cpuDuration.addSample(event.cpuDuration);insideIdle.addSample(inside);outsideIdle.addSample(event.cpuDuration-inside);idleDeadlineOverrun.addSample(overrun);});values.addHistogram(idleDeadlineOverrun);values.addHistogram(outsideIdle);var percentage=createPercentage(name+'_percentage_idle',insideIdle.sum,cpuDuration.sum);values.addHistogram(percentage);}return{blinkGcMetric:blinkGcMetric};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/range.js":52,"../../base/unit.js":62,"../../value/histogram.js":194,"../metric_registry.js":88,"../v8/utils.js":104}],87:[function(require,module,exports){
-(function(global){
-"use strict";var _slicedToArray=function(){function sliceIterator(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n=(_s=_i.next()).done);_n=true){_arr.push(_s.value);if(i&&_arr.length===i)break;}}catch(err){_d=true;_e=err;}finally{try{if(!_n&&_i["return"])_i["return"]();}finally{if(_d)throw _e;}}return _arr;}return function(arr,i){if(Array.isArray(arr)){return arr;}else if(Symbol.iterator in Object(arr)){return sliceIterator(arr,i);}else{throw new TypeError("Invalid attempt to destructure non-iterable instance");}};}();require("./metric_registry.js");require("../value/histogram.js");'use strict';global.tr.exportTo('tr.metrics.sh',function(){function getCpuSnapshotsFromModel(model){var snapshots=[];for(var pid in model.processes){var snapshotInstances=model.processes[pid].objects.getAllInstancesNamed('CPUSnapshots');if(!snapshotInstances)continue;for(var object of snapshotInstances[0].snapshots)snapshots.push(object.args.processes);}return snapshots;}function getProcessSumsFromSnapshot(snapshot){var processSums=new Map();for(var processData of snapshot){var processName=processData.name;if(!processSums.has(processName))processSums.set(processName,{sum:0.0,paths:new Set()});processSums.get(processName).sum+=parseFloat(processData.pCpu);if(processData.path)processSums.get(processName).paths.add(processData.path);}return processSums;}function buildNumericsFromSnapshots(snapshots){var processNumerics=new Map();for(var snapshot of snapshots){var processSums=getProcessSumsFromSnapshot(snapshot);for(var _ref of processSums.entries()){var _ref2=_slicedToArray(_ref,2);var processName=_ref2[0];var processData=_ref2[1];if(!processNumerics.has(processName)){processNumerics.set(processName,{numeric:new tr.v.Histogram('cpu:percent:'+processName,tr.b.Unit.byName.normalizedPercentage_smallerIsBetter),paths:new Set()});}processNumerics.get(processName).numeric.addSample(processData.sum/100.0);for(var path of processData.paths)processNumerics.get(processName).paths.add(path);}}return processNumerics;}function cpuProcessMetric(values,model){var snapshots=getCpuSnapshotsFromModel(model);var processNumerics=buildNumericsFromSnapshots(snapshots);for(var _ref3 of processNumerics){var _ref4=_slicedToArray(_ref3,2);var processName=_ref4[0];var processData=_ref4[1];var numeric=processData.numeric;var missingSnapshotCount=snapshots.length-numeric.numValues;for(var i=0;i<missingSnapshotCount;i++)numeric.addSample(0);numeric.diagnostics.set('paths',new tr.v.d.Generic([...processData.paths]));values.addHistogram(numeric);}}tr.metrics.MetricRegistry.register(cpuProcessMetric);return{cpuProcessMetric:cpuProcessMetric};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../value/histogram.js":194,"./metric_registry.js":88}],88:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/range.js":53,"../../base/unit.js":63,"../../value/histogram.js":195,"../metric_registry.js":89,"../v8/utils.js":105}],88:[function(require,module,exports){
+(function (global){
+"use strict";require("./metric_registry.js");require("../value/histogram.js");'use strict';global.tr.exportTo('tr.metrics.sh',function(){function getCpuSnapshotsFromModel(model){var snapshots=[];for(var pid in model.processes){var snapshotInstances=model.processes[pid].objects.getAllInstancesNamed('CPUSnapshots');if(!snapshotInstances)continue;for(var object of snapshotInstances[0].snapshots)snapshots.push(object.args.processes);}return snapshots;}function getProcessSumsFromSnapshot(snapshot){var processSums=new Map();for(var processData of snapshot){var processName=processData.name;if(!processSums.has(processName))processSums.set(processName,{sum:0.0,paths:new Set()});processSums.get(processName).sum+=parseFloat(processData.pCpu);if(processData.path)processSums.get(processName).paths.add(processData.path);}return processSums;}function buildNumericsFromSnapshots(snapshots){var processNumerics=new Map();for(var snapshot of snapshots){var processSums=getProcessSumsFromSnapshot(snapshot);for(var[processName,processData]of processSums.entries()){if(!processNumerics.has(processName)){processNumerics.set(processName,{numeric:new tr.v.Histogram('cpu:percent:'+processName,tr.b.Unit.byName.normalizedPercentage_smallerIsBetter),paths:new Set()});}processNumerics.get(processName).numeric.addSample(processData.sum/100.0);for(var path of processData.paths)processNumerics.get(processName).paths.add(path);}}return processNumerics;}function cpuProcessMetric(values,model){var snapshots=getCpuSnapshotsFromModel(model);var processNumerics=buildNumericsFromSnapshots(snapshots);for(var[processName,processData]of processNumerics){var numeric=processData.numeric;var missingSnapshotCount=snapshots.length-numeric.numValues;for(var i=0;i<missingSnapshotCount;i++)numeric.addSample(0);numeric.diagnostics.set('paths',new tr.v.d.Generic([...processData.paths]));values.addHistogram(numeric);}}tr.metrics.MetricRegistry.register(cpuProcessMetric);return{cpuProcessMetric:cpuProcessMetric};});
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../value/histogram.js":195,"./metric_registry.js":89}],89:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/base.js");require("../base/extension_registry.js");require("../base/iteration_helpers.js");'use strict';global.tr.exportTo('tr.metrics',function(){function MetricRegistry(){}var options=new tr.b.ExtensionRegistryOptions(tr.b.BASIC_REGISTRY_MODE);options.defaultMetadata={};tr.b.decorateExtensionRegistry(MetricRegistry,options);MetricRegistry.addEventListener('will-register',function(e){var metric=e.typeInfo.constructor;if(!(metric instanceof Function))throw new Error('Metrics must be functions');if(metric.length<2){throw new Error('Metrics take a ValueSet and a Model and '+'optionally an options dictionary');}});return{MetricRegistry:MetricRegistry};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/base.js":33,"../base/extension_registry.js":40,"../base/iteration_helpers.js":46}],89:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/base.js":34,"../base/extension_registry.js":41,"../base/iteration_helpers.js":47}],90:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/range.js");require("./metric_registry.js");require("../value/histogram.js");'use strict';global.tr.exportTo('tr.metrics',function(){function sampleMetric(values,model){var hist=new tr.v.Histogram('foo',tr.b.Unit.byName.sizeInBytes_smallerIsBetter);hist.addSample(9);hist.addSample(91,{bar:new tr.v.d.Generic({hello:42})});for(var expectation of model.userModel.expectations){if(expectation instanceof tr.model.um.ResponseExpectation){}else if(expectation instanceof tr.model.um.AnimationExpectation){}else if(expectation instanceof tr.model.um.IdleExpectation){}else if(expectation instanceof tr.model.um.LoadExpectation){}}var chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);tr.b.iterItems(model.processes,function(pid,process){});values.addHistogram(hist);}tr.metrics.MetricRegistry.register(sampleMetric);return{sampleMetric:sampleMetric};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/range.js":52,"../value/histogram.js":194,"./metric_registry.js":88}],90:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/range.js":53,"../value/histogram.js":195,"./metric_registry.js":89}],91:[function(require,module,exports){
+(function (global){
 "use strict";require("../metric_registry.js");require("./utils.js");require("../../model/model.js");require("../../value/histogram.js");'use strict';global.tr.exportTo('tr.metrics.sh',function(){function syncIsComplete(markers){return markers.length===2;}function syncInvolvesTelemetry(markers){for(var marker of markers)if(marker.domainId===tr.model.ClockDomainId.TELEMETRY)return true;return false;}function clockSyncLatencyMetric(values,model){for(var markers of model.clockSyncManager.markersBySyncId.values()){var latency=undefined;var targetDomain=undefined;if(!syncIsComplete(markers)||!syncInvolvesTelemetry(markers))continue;for(var marker of markers){var domain=marker.domainId;if(domain===tr.model.ClockDomainId.TELEMETRY)latency=marker.endTs-marker.startTs;else targetDomain=domain.toLowerCase();}var hist=new tr.v.Histogram('clock_sync_latency_'+targetDomain,tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,tr.v.HistogramBinBoundaries.createExponential(1e-3,1e3,30));hist.description='Clock sync latency for domain '+targetDomain;hist.addSample(latency);values.addHistogram(hist);}}tr.metrics.MetricRegistry.register(clockSyncLatencyMetric);return{clockSyncLatencyMetric:clockSyncLatencyMetric};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../model/model.js":140,"../../value/histogram.js":194,"../metric_registry.js":88,"./utils.js":99}],91:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../model/model.js":141,"../../value/histogram.js":195,"../metric_registry.js":89,"./utils.js":100}],92:[function(require,module,exports){
+(function (global){
 "use strict";require("../metric_registry.js");require("../../value/histogram.js");'use strict';global.tr.exportTo('tr.metrics.sh',function(){var CPU_TIME_PERCENTAGE_BOUNDARIES=tr.v.HistogramBinBoundaries.createExponential(0.01,50,200);function cpuTimeMetric(values,model,opt_options){var rangeOfInterest=model.bounds;if(opt_options&&opt_options.rangeOfInterest)rangeOfInterest=opt_options.rangeOfInterest;var allProcessCpuTime=0;for(var pid in model.processes){var process=model.processes[pid];var processCpuTime=0;for(var tid in process.threads){var thread=process.threads[tid];var threadCpuTime=0;thread.sliceGroup.topLevelSlices.forEach(function(slice){if(slice.duration===0)return;if(!slice.cpuDuration)return;var sliceRange=tr.b.Range.fromExplicitRange(slice.start,slice.end);var intersection=rangeOfInterest.findIntersection(sliceRange);var fractionOfSliceInsideRangeOfInterest=intersection.duration/slice.duration;threadCpuTime+=slice.cpuDuration*fractionOfSliceInsideRangeOfInterest;});processCpuTime+=threadCpuTime;}allProcessCpuTime+=processCpuTime;}var normalizedAllProcessCpuTime=0;if(rangeOfInterest.duration>0){normalizedAllProcessCpuTime=allProcessCpuTime/rangeOfInterest.duration;}var unit=tr.b.Unit.byName.normalizedPercentage_smallerIsBetter;var cpuTimeHist=new tr.v.Histogram('cpu_time_percentage',unit,CPU_TIME_PERCENTAGE_BOUNDARIES);cpuTimeHist.description='Percent CPU utilization, normalized against a single core. Can be '+'greater than 100% if machine has multiple cores.';cpuTimeHist.addSample(normalizedAllProcessCpuTime);values.addHistogram(cpuTimeHist);}tr.metrics.MetricRegistry.register(cpuTimeMetric,{supportsRangeOfInterest:true});return{cpuTimeMetric:cpuTimeMetric};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../value/histogram.js":194,"../metric_registry.js":88}],92:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../value/histogram.js":195,"../metric_registry.js":89}],93:[function(require,module,exports){
+(function (global){
 "use strict";require("../metric_registry.js");require("./long_tasks_metric.js");require("../../value/numeric.js");'use strict';global.tr.exportTo('tr.metrics.sh',function(){var MS_PER_S=1000;var RESPONSE_RISK=tr.b.Statistics.LogNormalDistribution.fromMedianAndDiminishingReturns(100/MS_PER_S,50/MS_PER_S);function computeResponsivenessRisk(durationMs){durationMs+=16;return RESPONSE_RISK.computePercentile(durationMs/MS_PER_S);}function perceptualBlendSmallerIsBetter(hazardScore){return Math.exp(hazardScore);}function computeHazardForLongTasksInRangeOnThread(thread,opt_range){var taskHazardScores=[];tr.metrics.sh.iterateLongTopLevelTasksOnThreadInRange(thread,opt_range,function(task){taskHazardScores.push(computeResponsivenessRisk(task.duration));});return tr.b.Statistics.weightedMean(taskHazardScores,perceptualBlendSmallerIsBetter);}function computeHazardForLongTasks(model){var threadHazardScores=[];tr.metrics.sh.iterateRendererMainThreads(model,function(thread){threadHazardScores.push(computeHazardForLongTasksInRangeOnThread(thread));});return tr.b.Statistics.weightedMean(threadHazardScores,perceptualBlendSmallerIsBetter);}function hazardMetric(values,model){var overallHazard=computeHazardForLongTasks(model);if(overallHazard===undefined)overallHazard=0;var hist=new tr.v.Histogram('hazard',tr.b.Unit.byName.normalizedPercentage_smallerIsBetter);hist.addSample(overallHazard);values.addHistogram(hist);}tr.metrics.MetricRegistry.register(hazardMetric);return{hazardMetric:hazardMetric,computeHazardForLongTasksInRangeOnThread:computeHazardForLongTasksInRangeOnThread,computeHazardForLongTasks:computeHazardForLongTasks,computeResponsivenessRisk:computeResponsivenessRisk};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../value/numeric.js":195,"../metric_registry.js":88,"./long_tasks_metric.js":94}],93:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../value/numeric.js":196,"../metric_registry.js":89,"./long_tasks_metric.js":95}],94:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/category_util.js");require("../../base/statistics.js");require("../metric_registry.js");require("./utils.js");require("../../model/helpers/chrome_model_helper.js");require("../../model/timed_event.js");require("../../value/histogram.js");require("../../value/numeric.js");'use strict';global.tr.exportTo('tr.metrics.sh',function(){var RESPONSIVENESS_THRESHOLD=50;var INTERACTIVE_WINDOW_SIZE=5*1000;var timeDurationInMs_smallerIsBetter=tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;var RelatedEventSet=tr.v.d.RelatedEventSet;function hasCategoryAndName(event,category,title){return event.title===title&&event.category&&tr.b.getCategoryParts(event.category).indexOf(category)!==-1;}function findTargetRendererHelper(chromeHelper){var largestPid=-1;for(var pid in chromeHelper.rendererHelpers){var rendererHelper=chromeHelper.rendererHelpers[pid];if(rendererHelper.isChromeTracingUI)continue;if(pid>largestPid)largestPid=pid;}if(largestPid===-1)return undefined;return chromeHelper.rendererHelpers[largestPid];}function createBreakdownDiagnostic(rendererHelper,start,end){var breakdownDict=rendererHelper.generateTimeBreakdownTree(start,end);var breakdownDiagnostic=new tr.v.d.Breakdown();breakdownDiagnostic.colorScheme=tr.v.d.COLOR_SCHEME_CHROME_USER_FRIENDLY_CATEGORY_DRIVER;for(var label in breakdownDict){breakdownDiagnostic.set(label,breakdownDict[label].total);}return breakdownDiagnostic;}function NavigationStartFinder(rendererHelper){this.navigationStartsForFrameId_={};for(var ev of rendererHelper.mainThread.sliceGroup.childEvents()){if(!hasCategoryAndName(ev,'blink.user_timing','navigationStart'))continue;var frameIdRef=ev.args['frame'];var list=this.navigationStartsForFrameId_[frameIdRef];if(list===undefined)this.navigationStartsForFrameId_[frameIdRef]=list=[];list.unshift(ev);}}NavigationStartFinder.prototype={findNavigationStartEventForFrameBeforeTimestamp:function(frameIdRef,ts){var list=this.navigationStartsForFrameId_[frameIdRef];if(list===undefined){console.warn('No navigationStartEvent found for frame id "'+frameIdRef+'"');return undefined;}var eventBeforeTimestamp;for(var ev of list){if(ev.start>ts)continue;if(eventBeforeTimestamp===undefined)eventBeforeTimestamp=ev;}if(eventBeforeTimestamp===undefined){console.warn('Failed to find navigationStartEvent.');return undefined;}return eventBeforeTimestamp;}};var FIRST_PAINT_BOUNDARIES=tr.v.HistogramBinBoundaries.createLinear(0,1e3,20).addLinearBins(3e3,20).addExponentialBins(20e3,20);function createHistogram(name){var histogram=new tr.v.Histogram(name,timeDurationInMs_smallerIsBetter,FIRST_PAINT_BOUNDARIES);histogram.customizeSummaryOptions({avg:true,count:false,max:true,min:true,std:true,sum:false,percentile:[0.90,0.95,0.99]});return histogram;}function findFrameLoaderSnapshotAt(rendererHelper,frameIdRef,ts){var snapshot;var objects=rendererHelper.process.objects;var frameLoaderInstances=objects.instancesByTypeName_['FrameLoader'];if(frameLoaderInstances===undefined){console.warn('Failed to find FrameLoader for frameId "'+frameIdRef+'" at ts '+ts+', the trace maybe incomplete or from an old'+'Chrome.');return undefined;}var snapshot;for(var instance of frameLoaderInstances){if(!instance.isAliveAt(ts))continue;var maybeSnapshot=instance.getSnapshotAt(ts);if(frameIdRef!==maybeSnapshot.args['frame']['id_ref'])continue;snapshot=maybeSnapshot;}return snapshot;}function findAllUserTimingEvents(rendererHelper,title){var targetEvents=[];for(var ev of rendererHelper.process.getDescendantEvents()){if(!hasCategoryAndName(ev,'blink.user_timing',title))continue;targetEvents.push(ev);}return targetEvents;}function findFirstMeaningfulPaintCandidates(rendererHelper){var isTelemetryInternalEvent=prepareTelemetryInternalEventPredicate(rendererHelper);var candidatesForFrameId={};for(var ev of rendererHelper.process.getDescendantEvents()){if(!hasCategoryAndName(ev,'loading','firstMeaningfulPaintCandidate'))continue;if(isTelemetryInternalEvent(ev))continue;var frameIdRef=ev.args['frame'];if(frameIdRef===undefined)continue;var list=candidatesForFrameId[frameIdRef];if(list===undefined)candidatesForFrameId[frameIdRef]=list=[];list.push(ev);}return candidatesForFrameId;}function prepareTelemetryInternalEventPredicate(rendererHelper){var ignoreRegions=[];var internalRegionStart;for(var slice of rendererHelper.mainThread.asyncSliceGroup.getDescendantEvents()){if(!!slice.title.match(/^telemetry\.internal\.[^.]*\.start$/))internalRegionStart=slice.start;if(!!slice.title.match(/^telemetry\.internal\.[^.]*\.end$/)){var timedEvent=new tr.model.TimedEvent(internalRegionStart);timedEvent.duration=slice.end-internalRegionStart;ignoreRegions.push(timedEvent);}}return function isTelemetryInternalEvent(slice){for(var region of ignoreRegions)if(region.bounds(slice))return true;return false;};}var URL_BLACKLIST=['about:blank','data:text/html,pluginplaceholderdata'];function shouldIgnoreURL(url){return URL_BLACKLIST.indexOf(url)>=0;}var METRICS=[{valueName:'timeToFirstContentfulPaint',title:'firstContentfulPaint',description:'time to first contentful paint'},{valueName:'timeToOnload',title:'loadEventStart',description:'time to onload. '+'This is temporary metric used for PCv1/v2 sanity checking'}];function timeToFirstContentfulPaintMetric(values,model){var chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);var rendererHelper=findTargetRendererHelper(chromeHelper);var isTelemetryInternalEvent=prepareTelemetryInternalEventPredicate(rendererHelper);var navigationStartFinder=new NavigationStartFinder(rendererHelper);for(var metric of METRICS){var histogram=createHistogram(metric.valueName);histogram.description=metric.description;var targetEvents=findAllUserTimingEvents(rendererHelper,metric.title);for(var ev of targetEvents){if(isTelemetryInternalEvent(ev))continue;var frameIdRef=ev.args['frame'];var snapshot=findFrameLoaderSnapshotAt(rendererHelper,frameIdRef,ev.start);if(snapshot===undefined||!snapshot.args.isLoadingMainFrame)continue;var url=snapshot.args.documentLoaderURL;if(shouldIgnoreURL(url))continue;var navigationStartEvent=navigationStartFinder.findNavigationStartEventForFrameBeforeTimestamp(frameIdRef,ev.start);if(navigationStartEvent===undefined)continue;var timeToEvent=ev.start-navigationStartEvent.start;histogram.addSample(timeToEvent,{url:new tr.v.d.Generic(url)});}values.addHistogram(histogram);}}function addTimeToInteractiveSampleToHistogram(histogram,rendererHelper,navigationStart,firstMeaningfulPaint,url){if(shouldIgnoreURL(url))return;var navigationStartTime=navigationStart.start;var firstInteractive=Infinity;var firstInteractiveCandidate=firstMeaningfulPaint;var lastLongTaskEvent=undefined;for(var ev of[...rendererHelper.mainThread.sliceGroup.childEvents()]){if(ev.start<firstInteractiveCandidate)continue;var interactiveDurationSoFar=ev.start-firstInteractiveCandidate;if(interactiveDurationSoFar>=INTERACTIVE_WINDOW_SIZE){firstInteractive=firstInteractiveCandidate;break;}if(ev.title==='TaskQueueManager::ProcessTaskFromWorkQueue'&&ev.duration>RESPONSIVENESS_THRESHOLD){firstInteractiveCandidate=ev.end-50;lastLongTaskEvent=ev;}}var breakdownDiagnostic=createBreakdownDiagnostic(rendererHelper,navigationStartTime,firstInteractive);var timeToFirstInteractive=firstInteractive-navigationStartTime;histogram.addSample(timeToFirstInteractive,{"Start":new RelatedEventSet(navigationStart),"Last long task":new RelatedEventSet(lastLongTaskEvent),"Navigation infos":new tr.v.d.Generic({url:url,pid:rendererHelper.pid,start:navigationStartTime,interactive:firstInteractive}),"Breakdown of [navStart, Interactive]":breakdownDiagnostic});}function timeToFirstMeaningfulPaintAndTimeToInteractiveMetrics(values,model){var chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);var rendererHelper=findTargetRendererHelper(chromeHelper);var navigationStartFinder=new NavigationStartFinder(rendererHelper);var firstMeaningfulPaintHistogram=createHistogram('timeToFirstMeaningfulPaint');firstMeaningfulPaintHistogram.description='time to first meaningful paint';var firstInteractiveHistogram=createHistogram('timeToFirstInteractive');firstInteractiveHistogram.description='time to first interactive';function addFirstMeaningfulPaintSampleToHistogram(frameIdRef,navigationStart,fmpMarkerEvent){var snapshot=findFrameLoaderSnapshotAt(rendererHelper,frameIdRef,fmpMarkerEvent.start);if(snapshot===undefined||!snapshot.args.isLoadingMainFrame)return;var url=snapshot.args.documentLoaderURL;if(shouldIgnoreURL(url))return;var timeToFirstMeaningfulPaint=fmpMarkerEvent.start-navigationStart.start;var extraDiagnostic={url:url,pid:rendererHelper.pid};var breakdownDiagnostic=createBreakdownDiagnostic(rendererHelper,navigationStart.start,fmpMarkerEvent.start);firstMeaningfulPaintHistogram.addSample(timeToFirstMeaningfulPaint,{"Breakdown of [navStart, FMP]":breakdownDiagnostic,"Start":new RelatedEventSet(navigationStart),"End":new RelatedEventSet(fmpMarkerEvent),"Navigation infos":new tr.v.d.Generic({url:url,pid:rendererHelper.pid,start:navigationStart.start,fmp:fmpMarkerEvent.start})});return{firstMeaningfulPaint:fmpMarkerEvent.start,url:url};}var candidatesForFrameId=findFirstMeaningfulPaintCandidates(rendererHelper);for(var frameIdRef in candidatesForFrameId){var navigationStart;var lastCandidate;for(var ev of candidatesForFrameId[frameIdRef]){var navigationStartForThisCandidate=navigationStartFinder.findNavigationStartEventForFrameBeforeTimestamp(frameIdRef,ev.start);if(navigationStartForThisCandidate===undefined)continue;if(navigationStart!==navigationStartForThisCandidate){if(navigationStart!==undefined&&lastCandidate!==undefined){data=addFirstMeaningfulPaintSampleToHistogram(frameIdRef,navigationStart,lastCandidate);if(data!==undefined)addTimeToInteractiveSampleToHistogram(firstInteractiveHistogram,rendererHelper,navigationStart,data.firstMeaningfulPaint,data.url);}navigationStart=navigationStartForThisCandidate;}lastCandidate=ev;}if(lastCandidate!==undefined){var data=addFirstMeaningfulPaintSampleToHistogram(frameIdRef,navigationStart,lastCandidate);if(data!==undefined)addTimeToInteractiveSampleToHistogram(firstInteractiveHistogram,rendererHelper,navigationStart,data.firstMeaningfulPaint,data.url);}}values.addHistogram(firstMeaningfulPaintHistogram);values.addHistogram(firstInteractiveHistogram);}function loadingMetric(values,model){timeToFirstContentfulPaintMetric(values,model);timeToFirstMeaningfulPaintAndTimeToInteractiveMetrics(values,model);}tr.metrics.MetricRegistry.register(loadingMetric);return{loadingMetric:loadingMetric};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/category_util.js":35,"../../base/statistics.js":58,"../../model/helpers/chrome_model_helper.js":132,"../../model/timed_event.js":165,"../../value/histogram.js":194,"../../value/numeric.js":195,"../metric_registry.js":88,"./utils.js":99}],94:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/category_util.js":36,"../../base/statistics.js":59,"../../model/helpers/chrome_model_helper.js":133,"../../model/timed_event.js":166,"../../value/histogram.js":195,"../../value/numeric.js":196,"../metric_registry.js":89,"./utils.js":100}],95:[function(require,module,exports){
+(function (global){
 "use strict";require("../../extras/chrome/chrome_user_friendly_category_driver.js");require("../metric_registry.js");require("../../model/helpers/chrome_model_helper.js");require("../../value/histogram.js");'use strict';global.tr.exportTo('tr.metrics.sh',function(){var LONG_TASK_MS=50;var LONGEST_TASK_MS=1000;function iterateLongTopLevelTasksOnThreadInRange(thread,opt_range,cb,opt_this){thread.sliceGroup.topLevelSlices.forEach(function(slice){if(opt_range&&!opt_range.intersectsExplicitRangeInclusive(slice.start,slice.end))return;if(slice.duration<LONG_TASK_MS)return;cb.call(opt_this,slice);});}function iterateRendererMainThreads(model,cb,opt_this){var modelHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);tr.b.dictionaryValues(modelHelper.rendererHelpers).forEach(function(rendererHelper){if(!rendererHelper.mainThread)return;cb.call(opt_this,rendererHelper.mainThread);});}function longTasksMetric(values,model,opt_options){var rangeOfInterest=opt_options?opt_options.rangeOfInterest:undefined;var longTaskHist=new tr.v.Histogram('long tasks',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,tr.v.HistogramBinBoundaries.createLinear(LONG_TASK_MS,LONGEST_TASK_MS,40));longTaskHist.description='durations of long tasks';var slices=new tr.model.EventSet();iterateRendererMainThreads(model,function(thread){iterateLongTopLevelTasksOnThreadInRange(thread,rangeOfInterest,function(task){longTaskHist.addSample(task.duration,{relatedEvents:new tr.v.d.RelatedEventSet([task])});slices.push(task);slices.addEventSet(task.descendentSlices);});});values.addHistogram(longTaskHist);var sampleForEvent=undefined;var breakdown=tr.v.d.RelatedHistogramBreakdown.buildFromEvents(values,'long tasks ',slices,e=>model.getUserFriendlyCategoryFromEvent(e)||'unknown',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,sampleForEvent,tr.v.HistogramBinBoundaries.createExponential(1,LONGEST_TASK_MS,40));breakdown.colorScheme=tr.v.d.COLOR_SCHEME_CHROME_USER_FRIENDLY_CATEGORY_DRIVER;longTaskHist.diagnostics.set('category',breakdown);}tr.metrics.MetricRegistry.register(longTasksMetric,{supportsRangeOfInterest:true});return{longTasksMetric:longTasksMetric,iterateLongTopLevelTasksOnThreadInRange:iterateLongTopLevelTasksOnThreadInRange,iterateRendererMainThreads:iterateRendererMainThreads,LONG_TASK_MS:LONG_TASK_MS,LONGEST_TASK_MS:LONGEST_TASK_MS};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../extras/chrome/chrome_user_friendly_category_driver.js":68,"../../model/helpers/chrome_model_helper.js":132,"../../value/histogram.js":194,"../metric_registry.js":88}],95:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../extras/chrome/chrome_user_friendly_category_driver.js":69,"../../model/helpers/chrome_model_helper.js":133,"../../value/histogram.js":195,"../metric_registry.js":89}],96:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/iteration_helpers.js");require("../../base/multi_dimensional_view.js");require("../../base/range.js");require("../../base/unit.js");require("../metric_registry.js");require("../../model/container_memory_dump.js");require("../../model/helpers/chrome_model_helper.js");require("../../model/memory_allocator_dump.js");require("../../value/histogram.js");'use strict';global.tr.exportTo('tr.metrics.sh',function(){var BACKGROUND=tr.model.ContainerMemoryDump.LevelOfDetail.BACKGROUND;var LIGHT=tr.model.ContainerMemoryDump.LevelOfDetail.LIGHT;var DETAILED=tr.model.ContainerMemoryDump.LevelOfDetail.DETAILED;var sizeInBytes_smallerIsBetter=tr.b.Unit.byName.sizeInBytes_smallerIsBetter;var count_smallerIsBetter=tr.b.Unit.byName.count_smallerIsBetter;var DISPLAYED_SIZE_NUMERIC_NAME=tr.model.MemoryAllocatorDump.DISPLAYED_SIZE_NUMERIC_NAME;var LEVEL_OF_DETAIL_NAMES=new Map();LEVEL_OF_DETAIL_NAMES.set(BACKGROUND,'background');LEVEL_OF_DETAIL_NAMES.set(LIGHT,'light');LEVEL_OF_DETAIL_NAMES.set(DETAILED,'detailed');var BOUNDARIES_FOR_UNIT_MAP=new WeakMap();BOUNDARIES_FOR_UNIT_MAP.set(count_smallerIsBetter,tr.v.HistogramBinBoundaries.createLinear(0,20,20));BOUNDARIES_FOR_UNIT_MAP.set(sizeInBytes_smallerIsBetter,new tr.v.HistogramBinBoundaries(0).addBinBoundary(1024).addExponentialBins(16*1024*1024*1024,4*24));function memoryMetric(values,model,opt_options){var rangeOfInterest=opt_options?opt_options.rangeOfInterest:undefined;var browserNameToGlobalDumps=splitGlobalDumpsByBrowserName(model,rangeOfInterest);addGeneralMemoryDumpValues(browserNameToGlobalDumps,values);addDetailedMemoryDumpValues(browserNameToGlobalDumps,values);addMemoryDumpCountValues(browserNameToGlobalDumps,values);}function splitGlobalDumpsByBrowserName(model,opt_rangeOfInterest){var chromeModelHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);var browserNameToGlobalDumps=new Map();var globalDumpToBrowserHelper=new WeakMap();if(chromeModelHelper){chromeModelHelper.browserHelpers.forEach(function(helper){var globalDumps=skipDumpsThatDoNotIntersectRange(helper.process.memoryDumps.map(d=>d.globalMemoryDump),opt_rangeOfInterest);globalDumps.forEach(function(globalDump){var existingHelper=globalDumpToBrowserHelper.get(globalDump);if(existingHelper!==undefined){throw new Error('Memory dump ID clash across multiple browsers '+'with PIDs: '+existingHelper.pid+' and '+helper.pid);}globalDumpToBrowserHelper.set(globalDump,helper);});makeKeyUniqueAndSet(browserNameToGlobalDumps,canonicalizeName(helper.browserName),globalDumps);});}var unclassifiedGlobalDumps=skipDumpsThatDoNotIntersectRange(model.globalMemoryDumps.filter(g=>!globalDumpToBrowserHelper.has(g)),opt_rangeOfInterest);if(unclassifiedGlobalDumps.length>0){makeKeyUniqueAndSet(browserNameToGlobalDumps,'unknown_browser',unclassifiedGlobalDumps);}return browserNameToGlobalDumps;}function skipDumpsThatDoNotIntersectRange(dumps,opt_range){if(!opt_range)return dumps;return dumps.filter(d=>opt_range.intersectsExplicitRangeInclusive(d.start,d.end));}function canonicalizeName(name){return name.toLowerCase().replace(' ','_');}var USER_FRIENDLY_BROWSER_NAMES={'chrome':'Chrome','webview':'WebView','unknown_browser':'an unknown browser'};function convertBrowserNameToUserFriendlyName(browserName){for(var baseName in USER_FRIENDLY_BROWSER_NAMES){if(!browserName.startsWith(baseName))continue;var userFriendlyBaseName=USER_FRIENDLY_BROWSER_NAMES[baseName];var suffix=browserName.substring(baseName.length);if(suffix.length===0)return userFriendlyBaseName;else if(/^\d+$/.test(suffix))return userFriendlyBaseName+'('+suffix+')';}return'\''+browserName+'\' browser';}function canonicalizeProcessName(rawProcessName){if(!rawProcessName)return'unknown_processes';var baseCanonicalName=canonicalizeName(rawProcessName);switch(baseCanonicalName){case'renderer':return'renderer_processes';case'browser':return'browser_process';default:return baseCanonicalName;}}function convertProcessNameToUserFriendlyName(processName,opt_requirePlural){switch(processName){case'browser_process':return opt_requirePlural?'browser processes':'the browser process';case'renderer_processes':return'renderer processes';case'gpu_process':return opt_requirePlural?'GPU processes':'the GPU process';case'ppapi_process':return opt_requirePlural?'PPAPI processes':'the PPAPI process';case'all_processes':return'all processes';case'unknown_processes':return'unknown processes';default:return'\''+processName+'\' processes';}}function makeKeyUniqueAndSet(map,key,value){var uniqueKey=key;var nextIndex=2;while(map.has(uniqueKey)){uniqueKey=key+nextIndex;nextIndex++;}map.set(uniqueKey,value);}function addGeneralMemoryDumpValues(browserNameToGlobalDumps,values){addMemoryDumpValues(browserNameToGlobalDumps,gmd=>true,function(processDump,addProcessScalar){addProcessScalar({source:'process_count',value:1,unit:count_smallerIsBetter,descriptionPrefixBuilder:buildProcessCountDescriptionPrefix});if(processDump.totals!==undefined){tr.b.iterItems(SYSTEM_TOTAL_VALUE_PROPERTIES,function(propertyName,propertySpec){addProcessScalar({source:'reported_by_os',property:propertyName,component:['system_memory'],value:propertySpec.getPropertyFunction(processDump),unit:sizeInBytes_smallerIsBetter,descriptionPrefixBuilder:propertySpec.descriptionPrefixBuilder});});}if(processDump.memoryAllocatorDumps===undefined)return;processDump.memoryAllocatorDumps.forEach(function(rootAllocatorDump){tr.b.iterItems(CHROME_VALUE_PROPERTIES,function(propertyName,descriptionPrefixBuilder){addProcessScalar({source:'reported_by_chrome',component:[rootAllocatorDump.name],property:propertyName,value:rootAllocatorDump.numerics[propertyName],descriptionPrefixBuilder:descriptionPrefixBuilder});});if(rootAllocatorDump.numerics['allocated_objects_size']===undefined){var allocatedObjectsDump=rootAllocatorDump.getDescendantDumpByFullName('allocated_objects');if(allocatedObjectsDump!==undefined){addProcessScalar({source:'reported_by_chrome',component:[rootAllocatorDump.name],property:'allocated_objects_size',value:allocatedObjectsDump.numerics['size'],descriptionPrefixBuilder:CHROME_VALUE_PROPERTIES['allocated_objects_size']});}}});addV8MemoryDumpValues(processDump,addProcessScalar);},function(componentTree){var tracingNode=componentTree.children[1].get('tracing');if(tracingNode===undefined)return;for(var i=0;i<componentTree.values.length;i++)componentTree.values[i].total-=tracingNode.values[i].total;},values);}function addV8MemoryDumpValues(processDump,addProcessScalar){var v8Dump=processDump.getMemoryAllocatorDumpByFullName('v8');if(v8Dump===undefined)return;v8Dump.children.forEach(function(isolateDump){var mallocDump=isolateDump.getDescendantDumpByFullName('malloc');if(mallocDump!==undefined){addV8ComponentValues(mallocDump,['v8','allocated_by_malloc'],addProcessScalar);}var heapDump=isolateDump.getDescendantDumpByFullName('heap_spaces');if(heapDump!==undefined){addV8ComponentValues(heapDump,['v8','heap'],addProcessScalar);heapDump.children.forEach(function(spaceDump){if(spaceDump.name==='other_spaces')return;addV8ComponentValues(spaceDump,['v8','heap',spaceDump.name],addProcessScalar);});}});addProcessScalar({source:'reported_by_chrome',component:['v8'],property:'code_and_metadata_size',value:v8Dump.numerics['code_and_metadata_size'],descriptionPrefixBuilder:buildCodeAndMetadataSizeValueDescriptionPrefix});addProcessScalar({source:'reported_by_chrome',component:['v8'],property:'code_and_metadata_size',value:v8Dump.numerics['bytecode_and_metadata_size'],descriptionPrefixBuilder:buildCodeAndMetadataSizeValueDescriptionPrefix});}function addV8ComponentValues(componentDump,componentPath,addProcessScalar){tr.b.iterItems(CHROME_VALUE_PROPERTIES,function(propertyName,descriptionPrefixBuilder){addProcessScalar({source:'reported_by_chrome',component:componentPath,property:propertyName,value:componentDump.numerics[propertyName],descriptionPrefixBuilder:descriptionPrefixBuilder});});}function buildProcessCountDescriptionPrefix(componentPath,processName){if(componentPath.length>0){throw new Error('Unexpected process count non-empty component path: '+componentPath.join(':'));}return'total number of '+convertProcessNameToUserFriendlyName(processName,true);}function buildChromeValueDescriptionPrefix(formatSpec,componentPath,processName){var nameParts=[];if(componentPath.length===0){nameParts.push('total');if(formatSpec.totalUserFriendlyPropertyName){nameParts.push(formatSpec.totalUserFriendlyPropertyName);}else{if(formatSpec.userFriendlyPropertyNamePrefix)nameParts.push(formatSpec.userFriendlyPropertyNamePrefix);nameParts.push(formatSpec.userFriendlyPropertyName);}nameParts.push('reported by Chrome for');}else{if(formatSpec.componentPreposition===undefined){if(formatSpec.userFriendlyPropertyNamePrefix)nameParts.push(formatSpec.userFriendlyPropertyNamePrefix);nameParts.push(componentPath.join(':'));nameParts.push(formatSpec.userFriendlyPropertyName);}else{if(formatSpec.userFriendlyPropertyNamePrefix)nameParts.push(formatSpec.userFriendlyPropertyNamePrefix);nameParts.push(formatSpec.userFriendlyPropertyName);nameParts.push(formatSpec.componentPreposition);if(componentPath[componentPath.length-1]==='allocated_by_malloc'){nameParts.push('objects allocated by malloc for');nameParts.push(componentPath.slice(0,componentPath.length-1).join(':'));}else{nameParts.push(componentPath.join(':'));}}nameParts.push('in');}nameParts.push(convertProcessNameToUserFriendlyName(processName));return nameParts.join(' ');}var CHROME_VALUE_PROPERTIES={'effective_size':buildChromeValueDescriptionPrefix.bind(undefined,{userFriendlyPropertyName:'effective size',componentPreposition:'of'}),'allocated_objects_size':buildChromeValueDescriptionPrefix.bind(undefined,{userFriendlyPropertyName:'size of all objects allocated',totalUserFriendlyPropertyName:'size of all allocated objects',componentPreposition:'by'}),'locked_size':buildChromeValueDescriptionPrefix.bind(undefined,{userFriendlyPropertyName:'locked (pinned) size',componentPreposition:'of'}),'peak_size':buildChromeValueDescriptionPrefix.bind(undefined,{userFriendlyPropertyName:'peak size',componentPreposition:'of'})};var SYSTEM_TOTAL_VALUE_PROPERTIES={'resident_size':{getPropertyFunction:function(processDump){return processDump.totals.residentBytes;},descriptionPrefixBuilder:buildOsValueDescriptionPrefix.bind(undefined,'resident set size (RSS)')},'peak_resident_size':{getPropertyFunction:function(processDump){return processDump.totals.peakResidentBytes;},descriptionPrefixBuilder:buildOsValueDescriptionPrefix.bind(undefined,'peak resident set size')}};function addDetailedMemoryDumpValues(browserNameToGlobalDumps,values){addMemoryDumpValues(browserNameToGlobalDumps,g=>g.levelOfDetail===DETAILED,function(processDump,addProcessScalar){tr.b.iterItems(SYSTEM_VALUE_COMPONENTS,function(componentName,componentSpec){tr.b.iterItems(SYSTEM_VALUE_PROPERTIES,function(propertyName,propertySpec){var node=getDescendantVmRegionClassificationNode(processDump.vmRegions,componentSpec.classificationPath);var componentPath=['system_memory'];if(componentName)componentPath.push(componentName);addProcessScalar({source:'reported_by_os',component:componentPath,property:propertyName,value:node===undefined?0:node.byteStats[propertySpec.byteStat]||0,unit:sizeInBytes_smallerIsBetter,descriptionPrefixBuilder:propertySpec.descriptionPrefixBuilder});});});var memtrackDump=processDump.getMemoryAllocatorDumpByFullName('gpu/android_memtrack');if(memtrackDump!==undefined){var descriptionPrefixBuilder=SYSTEM_VALUE_PROPERTIES['proportional_resident_size'].descriptionPrefixBuilder;memtrackDump.children.forEach(function(memtrackChildDump){var childName=memtrackChildDump.name;addProcessScalar({source:'reported_by_os',component:['gpu_memory',childName],property:'proportional_resident_size',value:memtrackChildDump.numerics['memtrack_pss'],descriptionPrefixBuilder:descriptionPrefixBuilder});});}},function(componentTree){},values);}var SYSTEM_VALUE_COMPONENTS={'':{classificationPath:[]},'java_heap':{classificationPath:['Android','Java runtime','Spaces'],userFriendlyName:'the Java heap'},'ashmem':{classificationPath:['Android','Ashmem'],userFriendlyName:'ashmem'},'native_heap':{classificationPath:['Native heap'],userFriendlyName:'the native heap'}};var SYSTEM_VALUE_PROPERTIES={'proportional_resident_size':{byteStat:'proportionalResident',descriptionPrefixBuilder:buildOsValueDescriptionPrefix.bind(undefined,'proportional resident size (PSS)')},'private_dirty_size':{byteStat:'privateDirtyResident',descriptionPrefixBuilder:buildOsValueDescriptionPrefix.bind(undefined,'private dirty size')}};function buildOsValueDescriptionPrefix(userFriendlyPropertyName,componentPath,processName){if(componentPath.length>2){throw new Error('OS value component path for \''+userFriendlyPropertyName+'\' too long: '+componentPath.join(':'));}var nameParts=[];if(componentPath.length<2)nameParts.push('total');nameParts.push(userFriendlyPropertyName);if(componentPath.length>0){switch(componentPath[0]){case'system_memory':if(componentPath.length>1){var userFriendlyComponentName=SYSTEM_VALUE_COMPONENTS[componentPath[1]].userFriendlyName;if(userFriendlyComponentName===undefined){throw new Error('System value sub-component for \''+userFriendlyPropertyName+'\' unknown: '+componentPath.join(':'));}nameParts.push('of',userFriendlyComponentName,'in');}else{nameParts.push('of system memory (RAM) used by');}break;case'gpu_memory':if(componentPath.length>1){nameParts.push('of the',componentPath[1]);nameParts.push('Android memtrack component in');}else{nameParts.push('of GPU memory (Android memtrack) used by');}break;default:throw new Error('OS value component for \''+userFriendlyPropertyName+'\' unknown: '+componentPath.join(':'));}}else{nameParts.push('reported by the OS for');}nameParts.push(convertProcessNameToUserFriendlyName(processName));return nameParts.join(' ');}function buildCodeAndMetadataSizeValueDescriptionPrefix(componentPath,processName){return buildChromeValueDescriptionPrefix({userFriendlyPropertyNamePrefix:'size of',userFriendlyPropertyName:'code and metadata'},componentPath,processName);}function getDescendantVmRegionClassificationNode(node,path){for(var i=0;i<path.length;i++){if(node===undefined)break;node=tr.b.findFirstInArray(node.children,c=>c.title===path[i]);}return node;}function addMemoryDumpCountValues(browserNameToGlobalDumps,values){browserNameToGlobalDumps.forEach(function(globalDumps,browserName){var totalDumpCount=0;var levelOfDetailNameToDumpCount={};LEVEL_OF_DETAIL_NAMES.forEach(function(levelOfDetailName){levelOfDetailNameToDumpCount[levelOfDetailName]=0;});globalDumps.forEach(function(globalDump){totalDumpCount++;var levelOfDetailName=LEVEL_OF_DETAIL_NAMES.get(globalDump.levelOfDetail);if(!(levelOfDetailName in levelOfDetailNameToDumpCount))return;levelOfDetailNameToDumpCount[levelOfDetailName]++;});reportMemoryDumpCountAsValue(browserName,undefined,totalDumpCount,values);tr.b.iterItems(levelOfDetailNameToDumpCount,function(levelOfDetailName,levelOfDetailDumpCount){reportMemoryDumpCountAsValue(browserName,levelOfDetailName,levelOfDetailDumpCount,values);});});}function reportMemoryDumpCountAsValue(browserName,levelOfDetailName,levelOfDetailDumpCount,values){var nameParts=['memory',browserName,'all_processes','dump_count'];if(levelOfDetailName!==undefined)nameParts.push(levelOfDetailName);var name=nameParts.join(':');var histogram=new tr.v.Histogram(name,count_smallerIsBetter,BOUNDARIES_FOR_UNIT_MAP.get(count_smallerIsBetter));histogram.addSample(levelOfDetailDumpCount);histogram.description=['total number of',levelOfDetailName||'all','memory dumps added by',convertBrowserNameToUserFriendlyName(browserName),'to the trace'].join(' ');values.addHistogram(histogram);}function addMemoryDumpValues(browserNameToGlobalDumps,customGlobalDumpFilter,customProcessDumpValueExtractor,customComponentTreeModifier,values){browserNameToGlobalDumps.forEach(function(globalDumps,browserName){var filteredGlobalDumps=globalDumps.filter(customGlobalDumpFilter);var sourceToPropertyToData=extractDataFromGlobalDumps(filteredGlobalDumps,customProcessDumpValueExtractor);reportDataAsValues(sourceToPropertyToData,browserName,customComponentTreeModifier,values);});}function extractDataFromGlobalDumps(globalDumps,customProcessDumpValueExtractor){var sourceToPropertyToData=new Map();var dumpCount=globalDumps.length;globalDumps.forEach(function(globalDump,dumpIndex){tr.b.iterItems(globalDump.processMemoryDumps,function(_,processDump){extractDataFromProcessDump(processDump,sourceToPropertyToData,dumpIndex,dumpCount,customProcessDumpValueExtractor);});});return sourceToPropertyToData;}function extractDataFromProcessDump(processDump,sourceToPropertyToData,dumpIndex,dumpCount,customProcessDumpValueExtractor){var rawProcessName=processDump.process.name;var processNamePath=[canonicalizeProcessName(rawProcessName)];customProcessDumpValueExtractor(processDump,function addProcessScalar(spec){if(spec.value===undefined)return;var component=spec.component||[];function createDetailsForErrorMessage(){var propertyUserFriendlyName=spec.property===undefined?'(undefined)':spec.property;var componentUserFriendlyName=component.length===0?'(empty)':component.join(':');return['source=',spec.source,', property=',propertyUserFriendlyName,', component=',componentUserFriendlyName,' in ',processDump.process.userFriendlyName].join('');}var value,unit;if(spec.value instanceof tr.v.ScalarNumeric){value=spec.value.value;unit=spec.value.unit;if(spec.unit!==undefined){throw new Error('Histogram value for '+createDetailsForErrorMessage()+' already specifies a unit');}}else{value=spec.value;unit=spec.unit;}var propertyToData=sourceToPropertyToData.get(spec.source);if(propertyToData===undefined){propertyToData=new Map();sourceToPropertyToData.set(spec.source,propertyToData);}var data=propertyToData.get(spec.property);if(data===undefined){data={processAndComponentTreeBuilder:new tr.b.MultiDimensionalViewBuilder(2,dumpCount),unit:unit,descriptionPrefixBuilder:spec.descriptionPrefixBuilder};propertyToData.set(spec.property,data);}else if(data.unit!==unit){throw new Error('Multiple units provided for '+createDetailsForErrorMessage()+':'+data.unit.unitName+' and '+unit.unitName);}else if(data.descriptionPrefixBuilder!==spec.descriptionPrefixBuilder){throw new Error('Multiple description prefix builders provided for'+createDetailsForErrorMessage());}var values=new Array(dumpCount);values[dumpIndex]=value;data.processAndComponentTreeBuilder.addPath([processNamePath,component],values,tr.b.MultiDimensionalViewBuilder.ValueKind.TOTAL);});}function reportDataAsValues(sourceToPropertyToData,browserName,customComponentTreeModifier,values){sourceToPropertyToData.forEach(function(propertyToData,sourceName){propertyToData.forEach(function(data,propertyName){var tree=data.processAndComponentTreeBuilder.buildTopDownTreeView();var unit=data.unit;var descriptionPrefixBuilder=data.descriptionPrefixBuilder;customComponentTreeModifier(tree);reportComponentDataAsValues(browserName,sourceName,propertyName,'all_processes',[],tree,unit,descriptionPrefixBuilder,values);tree.children[0].forEach(function(processTree,processName){if(processTree.children[0].size>0){throw new Error('Multi-dimensional view node for source='+sourceName+', property='+(propertyName===undefined?'(undefined)':propertyName)+', process='+processName+' has children wrt the process name dimension');}customComponentTreeModifier(processTree);reportComponentDataAsValues(browserName,sourceName,propertyName,processName,[],processTree,unit,descriptionPrefixBuilder,values);});});});}function reportComponentDataAsValues(browserName,sourceName,propertyName,processName,componentPath,componentNode,unit,descriptionPrefixBuilder,values){var nameParts=['memory',browserName,processName,sourceName].concat(componentPath);if(propertyName!==undefined)nameParts.push(propertyName);var name=nameParts.join(':');var numeric=buildMemoryNumericFromNode(name,componentNode,unit);numeric.description=[descriptionPrefixBuilder(componentPath,processName),'in',convertBrowserNameToUserFriendlyName(browserName)].join(' ');values.addHistogram(numeric);var depth=componentPath.length;componentPath.push(undefined);componentNode.children[1].forEach(function(childNode,childName){componentPath[depth]=childName;reportComponentDataAsValues(browserName,sourceName,propertyName,processName,componentPath,childNode,unit,descriptionPrefixBuilder,values);});componentPath.pop();}function buildMemoryNumericFromNode(name,node,unit){var histogram=new tr.v.Histogram(name,unit,BOUNDARIES_FOR_UNIT_MAP.get(unit));node.values.forEach(v=>histogram.addSample(v.total));return histogram;}tr.metrics.MetricRegistry.register(memoryMetric,{supportsRangeOfInterest:true});return{memoryMetric:memoryMetric};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/iteration_helpers.js":46,"../../base/multi_dimensional_view.js":48,"../../base/range.js":52,"../../base/unit.js":62,"../../model/container_memory_dump.js":114,"../../model/helpers/chrome_model_helper.js":132,"../../model/memory_allocator_dump.js":139,"../../value/histogram.js":194,"../metric_registry.js":88}],96:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/iteration_helpers.js":47,"../../base/multi_dimensional_view.js":49,"../../base/range.js":53,"../../base/unit.js":63,"../../model/container_memory_dump.js":115,"../../model/helpers/chrome_model_helper.js":133,"../../model/memory_allocator_dump.js":140,"../../value/histogram.js":195,"../metric_registry.js":89}],97:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/statistics.js");require("../metric_registry.js");require("./loading_metric.js");require("../../value/histogram.js");'use strict';global.tr.exportTo('tr.metrics.sh',function(){var FRAMES_PER_SEC=60;var FRAME_MS=tr.b.convertUnit(1.0/FRAMES_PER_SEC,tr.b.UnitScale.Metric.NONE,tr.b.UnitScale.Metric.MILLI);function getPowerData_(model,start,end){var durationInMs=end-start;var durationInS=tr.b.convertUnit(durationInMs,tr.b.UnitScale.Metric.MILLI,tr.b.UnitScale.Metric.NONE);var energyInJ=model.device.powerSeries.getEnergyConsumedInJ(start,end);var powerInW=energyInJ/durationInS;return{duration:durationInMs,energy:energyInJ,power:powerInW};}function getNavigationTTIIntervals_(model){var values=new tr.v.ValueSet();tr.metrics.sh.loadingMetric(values,model);var ttiValues=values.getValuesNamed('timeToFirstInteractive');var intervals=[];for(var bin of tr.b.getOnlyElement(ttiValues).allBins){for(var diagnostics of bin.diagnosticMaps){var breakdown=diagnostics.get('Navigation infos');intervals.push(tr.b.Range.fromExplicitRange(breakdown.value.start,breakdown.value.interactive));}}return intervals.sort((x,y)=>x.min-y.min);}function makeTimeHistogram_(values,title,description){var hist=new tr.v.Histogram(title+':time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);hist.customizeSummaryOptions({avg:false,count:false,max:true,min:true,std:false,sum:true});hist.description='Time spent in '+description;values.addHistogram(hist);return hist;}function makeEnergyHistogram_(values,title,description){var hist=new tr.v.Histogram(title+':energy',tr.b.Unit.byName.energyInJoules_smallerIsBetter);hist.customizeSummaryOptions({avg:false,count:false,max:true,min:true,std:false,sum:true});hist.description='Energy consumed in '+description;values.addHistogram(hist);return hist;}function makePowerHistogram_(values,title,description){var hist=new tr.v.Histogram(title+':power',tr.b.Unit.byName.powerInWatts_smallerIsBetter);hist.customizeSummaryOptions({avg:true,count:false,max:true,min:true,std:false,sum:false});hist.description='Energy consumption rate in '+description;values.addHistogram(hist);return hist;}function storePowerData_(data,timeHist,energyHist,powerHist){if(timeHist!==undefined)timeHist.addSample(data.duration);if(energyHist!==undefined)energyHist.addSample(data.energy);if(powerHist!==undefined)powerHist.addSample(data.power);}function createHistograms_(model,values){var hists={};hists.railStageToTimeHist=new Map();hists.railStageToEnergyHist=new Map();hists.railStageToPowerHist=new Map();hists.scrollTimeHist=makeTimeHistogram_(values,'scroll','scrolling');hists.scrollEnergyHist=makeEnergyHistogram_(values,'scroll','scrolling');hists.scrollPowerHist=makePowerHistogram_(values,'scroll','scrolling');hists.loadTimeHist=makeTimeHistogram_(values,'load','page loads');hists.loadEnergyHist=makeEnergyHistogram_(values,'load','page loads');hists.afterLoadTimeHist=makeTimeHistogram_(values,'after_load','period after load');hists.afterLoadPowerHist=makePowerHistogram_(values,'after_load','period after load');hists.videoPowerHist=makePowerHistogram_(values,'video','video playback');hists.frameEnergyHist=makeEnergyHistogram_(values,'per_frame','each frame');for(var exp of model.userModel.expectations){var currTitle=exp.title.toLowerCase().replace(' ','_');if(!hists.railStageToTimeHist.has(currTitle)){var timeHist=makeTimeHistogram_(values,currTitle,'RAIL stage '+currTitle);var energyHist=makeEnergyHistogram_(values,currTitle,'RAIL stage '+currTitle);var powerHist=makePowerHistogram_(values,currTitle,'RAIL stage '+currTitle);hists.railStageToTimeHist.set(currTitle,timeHist);hists.railStageToEnergyHist.set(currTitle,energyHist);hists.railStageToPowerHist.set(currTitle,powerHist);}}return hists;}function processInteractionRecord_(exp,model,hists){var currTitle=exp.title.toLowerCase().replace(' ','_');var data=getPowerData_(model,exp.start,exp.end);storePowerData_(data,hists.railStageToTimeHist.get(currTitle),hists.railStageToEnergyHist.get(currTitle),hists.railStageToPowerHist.get(currTitle));if(exp.title.indexOf("Scroll")!==-1){storePowerData_(data,hists.scrollTimeHist,hists.scrollEnergyHist,hists.scrollPowerHist);}if(exp.title.indexOf("Video")!==-1)storePowerData_(data,undefined,undefined,hists.videoPowerHist);}function computeLoadingMetric_(model,hists){var intervals=getNavigationTTIIntervals_(model);var lastLoadTime=undefined;for(var interval of intervals){var loadData=getPowerData_(model,interval.min,interval.max);storePowerData_(loadData,hists.loadTimeHist,hists.loadEnergyHist,undefined);lastLoadTime=lastLoadTime==undefined?interval.max:Math.max(lastLoadTime,interval.max);}if(lastLoadTime!==undefined){var afterLoadData=getPowerData_(model,lastLoadTime,model.bounds.max);storePowerData_(afterLoadData,hists.afterLoadTimeHist,undefined,hists.afterLoadPowerHist);}}function computeFrameBasedPowerMetric_(model,hists){model.device.powerSeries.updateBounds();var currentTime=model.device.powerSeries.bounds.min;while(currentTime<model.device.powerSeries.bounds.max){var frameData=getPowerData_(model,currentTime,currentTime+FRAME_MS);hists.frameEnergyHist.addSample(frameData.energy);currentTime+=FRAME_MS;}}function powerMetric(values,model){if(!model.device.powerSeries)return;var hists=createHistograms_(model,values);for(var exp of model.userModel.expectations)processInteractionRecord_(exp,model,hists);computeLoadingMetric_(model,hists);computeFrameBasedPowerMetric_(model,hists);}tr.metrics.MetricRegistry.register(powerMetric);return{powerMetric:powerMetric};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/statistics.js":58,"../../value/histogram.js":194,"../metric_registry.js":88,"./loading_metric.js":93}],97:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/statistics.js":59,"../../value/histogram.js":195,"../metric_registry.js":89,"./loading_metric.js":94}],98:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/statistics.js");require("../metric_registry.js");require("./utils.js");require("../../model/user_model/animation_expectation.js");require("../../model/user_model/load_expectation.js");require("../../model/user_model/response_expectation.js");require("../../value/histogram.js");'use strict';global.tr.exportTo('tr.metrics.sh',function(){function computeAnimationThroughput(animationExpectation){if(animationExpectation.frameEvents===undefined||animationExpectation.frameEvents.length===0)throw new Error('Animation missing frameEvents '+animationExpectation.stableId);var durationInS=tr.b.convertUnit(animationExpectation.duration,tr.b.UnitScale.Metric.MILLI,tr.b.UnitScale.Metric.NONE);return animationExpectation.frameEvents.length/durationInS;}function computeAnimationframeTimeDiscrepancy(animationExpectation){if(animationExpectation.frameEvents===undefined||animationExpectation.frameEvents.length===0)throw new Error('Animation missing frameEvents '+animationExpectation.stableId);var frameTimestamps=animationExpectation.frameEvents;frameTimestamps=frameTimestamps.toArray().map(function(event){return event.start;});var absolute=true;return tr.b.Statistics.timestampsDiscrepancy(frameTimestamps,absolute);}function responsivenessMetric(values,model,opt_options){var responseNumeric=new tr.v.Histogram('response latency',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,tr.v.HistogramBinBoundaries.createLinear(100,1e3,50));var throughputNumeric=new tr.v.Histogram('animation throughput',tr.b.Unit.byName.unitlessNumber_biggerIsBetter,tr.v.HistogramBinBoundaries.createLinear(10,60,10));var frameTimeDiscrepancyNumeric=new tr.v.Histogram('animation frameTimeDiscrepancy',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,tr.v.HistogramBinBoundaries.createLinear(0,1e3,50).addExponentialBins(1e4,10));var latencyNumeric=new tr.v.Histogram('animation latency',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,tr.v.HistogramBinBoundaries.createLinear(0,300,60));model.userModel.expectations.forEach(function(ue){if(opt_options&&opt_options.rangeOfInterest&&!opt_options.rangeOfInterest.intersectsExplicitRangeInclusive(ue.start,ue.end))return;var sampleDiagnosticMap=tr.v.d.DiagnosticMap.fromObject({relatedEvents:new tr.v.d.RelatedEventSet([ue])});if(ue instanceof tr.model.um.IdleExpectation){return;}else if(ue instanceof tr.model.um.StartupExpectation){return;}else if(ue instanceof tr.model.um.LoadExpectation){}else if(ue instanceof tr.model.um.ResponseExpectation){responseNumeric.addSample(ue.duration,sampleDiagnosticMap);}else if(ue instanceof tr.model.um.AnimationExpectation){if(ue.frameEvents===undefined||ue.frameEvents.length===0){return;}var throughput=computeAnimationThroughput(ue);if(throughput===undefined)throw new Error('Missing throughput for '+ue.stableId);throughputNumeric.addSample(throughput,sampleDiagnosticMap);var frameTimeDiscrepancy=computeAnimationframeTimeDiscrepancy(ue);if(frameTimeDiscrepancy===undefined)throw new Error('Missing frameTimeDiscrepancy for '+ue.stableId);frameTimeDiscrepancyNumeric.addSample(frameTimeDiscrepancy,sampleDiagnosticMap);ue.associatedEvents.forEach(function(event){if(!(event instanceof tr.e.cc.InputLatencyAsyncSlice))return;latencyNumeric.addSample(event.duration,sampleDiagnosticMap);});}else{throw new Error('Unrecognized stage for '+ue.stableId);}});[responseNumeric,throughputNumeric,frameTimeDiscrepancyNumeric,latencyNumeric].forEach(function(numeric){numeric.customizeSummaryOptions({avg:true,max:true,min:true,std:true});});values.addHistogram(responseNumeric);values.addHistogram(throughputNumeric);values.addHistogram(frameTimeDiscrepancyNumeric);values.addHistogram(latencyNumeric);}tr.metrics.MetricRegistry.register(responsivenessMetric,{supportsRangeOfInterest:true});return{responsivenessMetric:responsivenessMetric};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/statistics.js":58,"../../model/user_model/animation_expectation.js":166,"../../model/user_model/load_expectation.js":168,"../../model/user_model/response_expectation.js":169,"../../value/histogram.js":194,"../metric_registry.js":88,"./utils.js":99}],98:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/statistics.js":59,"../../model/user_model/animation_expectation.js":167,"../../model/user_model/load_expectation.js":169,"../../model/user_model/response_expectation.js":170,"../../value/histogram.js":195,"../metric_registry.js":89,"./utils.js":100}],99:[function(require,module,exports){
+(function (global){
 "use strict";require("./cpu_time_metric.js");require("./hazard_metric.js");require("./long_tasks_metric.js");require("./power_metric.js");'use strict';global.tr.exportTo('tr.metrics.sh',function(){function systemHealthMetrics(values,model){tr.metrics.sh.responsivenessMetric(values,model);tr.metrics.sh.longTasksMetric(values,model);tr.metrics.sh.hazardMetric(values,model);tr.metrics.sh.powerMetric(values,model);tr.metrics.sh.cpuTimeMetric(values,model);}tr.metrics.MetricRegistry.register(systemHealthMetrics);return{systemHealthMetrics:systemHealthMetrics};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./cpu_time_metric.js":91,"./hazard_metric.js":92,"./long_tasks_metric.js":94,"./power_metric.js":96}],99:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./cpu_time_metric.js":92,"./hazard_metric.js":93,"./long_tasks_metric.js":95,"./power_metric.js":97}],100:[function(require,module,exports){
+(function (global){
 "use strict";require("../../model/user_model/user_expectation.js");'use strict';global.tr.exportTo('tr.metrics.sh',function(){function perceptualBlend(ir,index,score){return Math.exp(1-score);}function filterExpectationsByRange(irs,opt_range){var filteredExpectations=[];irs.forEach(function(ir){if(!(ir instanceof tr.model.um.UserExpectation))return;if(!opt_range||opt_range.intersectsExplicitRangeInclusive(ir.start,ir.end))filteredExpectations.push(ir);});return filteredExpectations;}return{perceptualBlend:perceptualBlend,filterExpectationsByRange:filterExpectationsByRange};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../model/user_model/user_expectation.js":171}],100:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../model/user_model/user_expectation.js":172}],101:[function(require,module,exports){
+(function (global){
 "use strict";require("../metric_registry.js");require("./utils.js");require("../../value/histogram.js");'use strict';global.tr.exportTo('tr.metrics.sh',function(){function webviewStartupMetric(values,model){var startupWallHist=new tr.v.Histogram('webview_startup_wall_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);startupWallHist.description='WebView startup wall time';var startupCPUHist=new tr.v.Histogram('webview_startup_cpu_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);startupCPUHist.description='WebView startup CPU time';var loadWallHist=new tr.v.Histogram('webview_url_load_wall_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);loadWallHist.description='WebView blank URL load wall time';var loadCPUHist=new tr.v.Histogram('webview_url_load_cpu_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);loadCPUHist.description='WebView blank URL load CPU time';for(var slice of model.getDescendantEvents()){if(!(slice instanceof tr.model.ThreadSlice))continue;if(slice.title==='WebViewStartupInterval'){startupWallHist.addSample(slice.duration);startupCPUHist.addSample(slice.cpuDuration);}if(slice.title==='WebViewBlankUrlLoadInterval'){loadWallHist.addSample(slice.duration);loadCPUHist.addSample(slice.cpuDuration);}}values.addHistogram(startupWallHist);values.addHistogram(startupCPUHist);values.addHistogram(loadWallHist);values.addHistogram(loadCPUHist);}tr.metrics.MetricRegistry.register(webviewStartupMetric);return{webviewStartupMetric:webviewStartupMetric};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../value/histogram.js":194,"../metric_registry.js":88,"./utils.js":99}],101:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../value/histogram.js":195,"../metric_registry.js":89,"./utils.js":100}],102:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/iteration_helpers.js");require("./metric_registry.js");require("../value/diagnostics/diagnostic_map.js");require("../value/histogram.js");'use strict';global.tr.exportTo('tr.metrics',function(){var MEMORY_INFRA_TRACING_CATEGORY='disabled-by-default-memory-infra';var TIME_BOUNDARIES=tr.v.HistogramBinBoundaries.createExponential(1e-3,1e5,30);var BYTE_BOUNDARIES=tr.v.HistogramBinBoundaries.createExponential(1,1e9,30);var COUNT_BOUNDARIES=tr.v.HistogramBinBoundaries.createExponential(1,1e5,30);function addTimeDurationValue(valueName,duration,allValues){var hist=new tr.v.Histogram(valueName,tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,TIME_BOUNDARIES);hist.addSample(duration);allValues.addHistogram(hist);}function addMemoryInfraValues(values,model,categoryNamesToTotalEventSizes){var memoryDumpCount=model.globalMemoryDumps.length;if(memoryDumpCount===0)return;var totalOverhead=0;var nonMemoryInfraThreadOverhead=0;var overheadByProvider={};tr.b.iterItems(model.processes,function(pid,process){tr.b.iterItems(process.threads,function(tid,thread){tr.b.iterItems(thread.sliceGroup.slices,(unusedSliceId,slice)=>{if(slice.category!==MEMORY_INFRA_TRACING_CATEGORY)return;totalOverhead+=slice.duration;if(thread.name!=='MemoryInfra')nonMemoryInfraThreadOverhead+=slice.duration;if(slice.args&&slice.args['dump_provider.name']){var providerName=slice.args['dump_provider.name'];var durationAndCount=overheadByProvider[providerName];if(durationAndCount===undefined){overheadByProvider[providerName]=durationAndCount={duration:0,count:0};}durationAndCount.duration+=slice.duration;durationAndCount.count++;}});});});addTimeDurationValue('Average CPU overhead on all threads per memory-infra dump',totalOverhead/memoryDumpCount,values);addTimeDurationValue('Average CPU overhead on non-memory-infra threads per memory-infra '+'dump',nonMemoryInfraThreadOverhead/memoryDumpCount,values);tr.b.iterItems(overheadByProvider,function(providerName,overhead){addTimeDurationValue('Average CPU overhead of '+providerName+' per OnMemoryDump call',overhead.duration/overhead.count,values);});var memoryInfraEventsSize=categoryNamesToTotalEventSizes.get(MEMORY_INFRA_TRACING_CATEGORY);var memoryInfraTraceBytesValue=new tr.v.Histogram('Total trace size of memory-infra dumps in bytes',tr.b.Unit.byName.sizeInBytes_smallerIsBetter,BYTE_BOUNDARIES);memoryInfraTraceBytesValue.addSample(memoryInfraEventsSize);values.addHistogram(memoryInfraTraceBytesValue);var traceBytesPerDumpValue=new tr.v.Histogram('Average trace size of memory-infra dumps in bytes',tr.b.Unit.byName.sizeInBytes_smallerIsBetter,BYTE_BOUNDARIES);traceBytesPerDumpValue.addSample(memoryInfraEventsSize/memoryDumpCount);values.addHistogram(traceBytesPerDumpValue);}function tracingMetric(values,model){if(!model.stats.hasEventSizesinBytes){throw new Error('Model stats does not have event size information. '+'Please enable ImportOptions.trackDetailedModelStats.');}var eventStats=model.stats.allTraceEventStatsInTimeIntervals;eventStats.sort(function(a,b){return a.timeInterval-b.timeInterval;});var totalTraceBytes=eventStats.reduce((a,b)=>a+b.totalEventSizeinBytes,0);var maxEventCountPerSec=0;var maxEventBytesPerSec=0;var INTERVALS_PER_SEC=Math.floor(1000/model.stats.TIME_INTERVAL_SIZE_IN_MS);var runningEventNumPerSec=0;var runningEventBytesPerSec=0;var start=0;var end=0;while(end<eventStats.length){runningEventNumPerSec+=eventStats[end].numEvents;runningEventBytesPerSec+=eventStats[end].totalEventSizeinBytes;end++;while(eventStats[end-1].timeInterval-eventStats[start].timeInterval>=INTERVALS_PER_SEC){runningEventNumPerSec-=eventStats[start].numEvents;runningEventBytesPerSec-=eventStats[start].totalEventSizeinBytes;start++;}maxEventCountPerSec=Math.max(maxEventCountPerSec,runningEventNumPerSec);maxEventBytesPerSec=Math.max(maxEventBytesPerSec,runningEventBytesPerSec);}var stats=model.stats.allTraceEventStats;var categoryNamesToTotalEventSizes=stats.reduce((map,stat)=>map.set(stat.category,(map.get(stat.category)||0)+stat.totalEventSizeinBytes),new Map());var maxCatNameAndBytes=Array.from(categoryNamesToTotalEventSizes.entries()).reduce((a,b)=>b[1]>=a[1]?b:a);var maxEventBytesPerCategory=maxCatNameAndBytes[1];var categoryWithMaxEventBytes=maxCatNameAndBytes[0];var maxEventCountPerSecValue=new tr.v.Histogram('Max number of events per second',tr.b.Unit.byName.count_smallerIsBetter,COUNT_BOUNDARIES);maxEventCountPerSecValue.addSample(maxEventCountPerSec);var maxEventBytesPerSecValue=new tr.v.Histogram('Max event size in bytes per second',tr.b.Unit.byName.sizeInBytes_smallerIsBetter,BYTE_BOUNDARIES);maxEventBytesPerSecValue.addSample(maxEventBytesPerSec);var totalTraceBytesValue=new tr.v.Histogram('Total trace size in bytes',tr.b.Unit.byName.sizeInBytes_smallerIsBetter,BYTE_BOUNDARIES);totalTraceBytesValue.addSample(totalTraceBytes);var biggestCategory={name:categoryWithMaxEventBytes,size_in_bytes:maxEventBytesPerCategory};totalTraceBytesValue.diagnostics.set('category_with_max_event_size',new tr.v.d.Generic(biggestCategory));values.addHistogram(totalTraceBytesValue);maxEventCountPerSecValue.diagnostics.set('category_with_max_event_size',new tr.v.d.Generic(biggestCategory));values.addHistogram(maxEventCountPerSecValue);maxEventBytesPerSecValue.diagnostics.set('category_with_max_event_size',new tr.v.d.Generic(biggestCategory));values.addHistogram(maxEventBytesPerSecValue);addMemoryInfraValues(values,model,categoryNamesToTotalEventSizes);}tr.metrics.MetricRegistry.register(tracingMetric);return{tracingMetric:tracingMetric,MEMORY_INFRA_TRACING_CATEGORY:MEMORY_INFRA_TRACING_CATEGORY};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/iteration_helpers.js":46,"../value/diagnostics/diagnostic_map.js":184,"../value/histogram.js":194,"./metric_registry.js":88}],102:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/iteration_helpers.js":47,"../value/diagnostics/diagnostic_map.js":185,"../value/histogram.js":195,"./metric_registry.js":89}],103:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/range.js");require("../../base/unit.js");require("../metric_registry.js");require("../../value/histogram.js");'use strict';global.tr.exportTo('tr.metrics.v8',function(){var CUSTOM_BOUNDARIES=tr.v.HistogramBinBoundaries.createLinear(4,200,100);function computeExecuteMetrics(values,model){var cpuTotalExecution=new tr.v.Histogram('v8_execution_cpu_total',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);cpuTotalExecution.description='cpu total time spent in script execution';var wallTotalExecution=new tr.v.Histogram('v8_execution_wall_total',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);wallTotalExecution.description='wall total time spent in script execution';var cpuSelfExecution=new tr.v.Histogram('v8_execution_cpu_self',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);cpuSelfExecution.description='cpu self time spent in script execution';var wallSelfExecution=new tr.v.Histogram('v8_execution_wall_self',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);wallSelfExecution.description='wall self time spent in script execution';for(var e of model.findTopmostSlicesNamed('V8.Execute')){cpuTotalExecution.addSample(e.cpuDuration);wallTotalExecution.addSample(e.duration);cpuSelfExecution.addSample(e.cpuSelfTime);wallSelfExecution.addSample(e.selfTime);}values.addHistogram(cpuTotalExecution);values.addHistogram(wallTotalExecution);values.addHistogram(cpuSelfExecution);values.addHistogram(wallSelfExecution);}function computeParseLazyMetrics(values,model){var cpuSelfParseLazy=new tr.v.Histogram('v8_parse_lazy_cpu_self',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);cpuSelfParseLazy.description='cpu self time spent performing lazy parsing';var wallSelfParseLazy=new tr.v.Histogram('v8_parse_lazy_wall_self',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);wallSelfParseLazy.description='wall self time spent performing lazy parsing';for(var e of model.findTopmostSlicesNamed('V8.ParseLazyMicroSeconds')){cpuSelfParseLazy.addSample(e.cpuSelfTime);wallSelfParseLazy.addSample(e.selfTime);}for(var e of model.findTopmostSlicesNamed('V8.ParseLazy')){cpuSelfParseLazy.addSample(e.cpuSelfTime);wallSelfParseLazy.addSample(e.selfTime);}values.addHistogram(cpuSelfParseLazy);values.addHistogram(wallSelfParseLazy);}function computeCompileFullCodeMetrics(values,model){var cpuSelfCompileFullCode=new tr.v.Histogram('v8_compile_full_code_cpu_self',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);cpuSelfCompileFullCode.description='cpu self time spent performing compiling full code';var wallSelfCompileFullCode=new tr.v.Histogram('v8_compile_full_code_wall_self',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);wallSelfCompileFullCode.description='wall self time spent performing compiling full code';for(var e of model.findTopmostSlicesNamed('V8.CompileFullCode')){cpuSelfCompileFullCode.addSample(e.cpuSelfTime);wallSelfCompileFullCode.addSample(e.selfTime);}values.addHistogram(cpuSelfCompileFullCode);values.addHistogram(wallSelfCompileFullCode);}function computeCompileIgnitionMetrics(values,model){var cpuSelfCompileIgnition=new tr.v.Histogram('v8_compile_ignition_cpu_self',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);cpuSelfCompileIgnition.description='cpu self time spent in compile ignition';var wallSelfCompileIgnition=new tr.v.Histogram('v8_compile_ignition_wall_self',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);wallSelfCompileIgnition.description='wall self time spent in compile ignition';for(var e of model.findTopmostSlicesNamed('V8.CompileIgnition')){cpuSelfCompileIgnition.addSample(e.cpuSelfTime);wallSelfCompileIgnition.addSample(e.selfTime);}values.addHistogram(cpuSelfCompileIgnition);values.addHistogram(wallSelfCompileIgnition);}function computeRecompileMetrics(values,model){var cpuTotalRecompileSynchronous=new tr.v.Histogram('v8_recompile_synchronous_cpu_total',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);cpuTotalRecompileSynchronous.description='cpu total time spent in synchronous recompilation';var wallTotalRecompileSynchronous=new tr.v.Histogram('v8_recompile_synchronous_wall_total',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);wallTotalRecompileSynchronous.description='wall total time spent in synchronous recompilation';var cpuTotalRecompileConcurrent=new tr.v.Histogram('v8_recompile_concurrent_cpu_total',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);cpuTotalRecompileConcurrent.description='cpu total time spent in concurrent recompilation';var wallTotalRecompileConcurrent=new tr.v.Histogram('v8_recompile_concurrent_wall_total',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);wallTotalRecompileConcurrent.description='wall total time spent in concurrent recompilation';var cpuTotalRecompileOverall=new tr.v.Histogram('v8_recompile_overall_cpu_total',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);cpuTotalRecompileOverall.description='cpu total time spent in synchronous or concurrent recompilation';var wallTotalRecompileOverall=new tr.v.Histogram('v8_recompile_overall_wall_total',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);wallTotalRecompileOverall.description='wall total time spent in synchronous or concurrent recompilation';for(var e of model.findTopmostSlicesNamed('V8.RecompileSynchronous')){cpuTotalRecompileSynchronous.addSample(e.cpuDuration);wallTotalRecompileSynchronous.addSample(e.duration);cpuTotalRecompileOverall.addSample(e.cpuDuration);wallTotalRecompileOverall.addSample(e.duration);}values.addHistogram(cpuTotalRecompileSynchronous);values.addHistogram(wallTotalRecompileSynchronous);for(var e of model.findTopmostSlicesNamed('V8.RecompileConcurrent')){cpuTotalRecompileConcurrent.addSample(e.cpuDuration);wallTotalRecompileConcurrent.addSample(e.duration);cpuTotalRecompileOverall.addSample(e.cpuDuration);wallTotalRecompileOverall.addSample(e.duration);}values.addHistogram(cpuTotalRecompileConcurrent);values.addHistogram(wallTotalRecompileConcurrent);values.addHistogram(cpuTotalRecompileOverall);values.addHistogram(wallTotalRecompileOverall);}function computeOptimizeCodeMetrics(values,model){var cpuTotalOptimizeCode=new tr.v.Histogram('v8_optimize_code_cpu_total',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);cpuTotalOptimizeCode.description='cpu total time spent in code optimization';var wallTotalOptimizeCode=new tr.v.Histogram('v8_optimize_code_wall_total',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);wallTotalOptimizeCode.description='wall total time spent in code optimization';for(var e of model.findTopmostSlicesNamed('V8.OptimizeCode')){cpuTotalOptimizeCode.addSample(e.cpuDuration);wallTotalOptimizeCode.addSample(e.duration);}values.addHistogram(cpuTotalOptimizeCode);values.addHistogram(wallTotalOptimizeCode);}function computeDeoptimizeCodeMetrics(values,model){var cpuTotalDeoptimizeCode=new tr.v.Histogram('v8_deoptimize_code_cpu_total',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);cpuTotalDeoptimizeCode.description='cpu total time spent in code deoptimization';var wallTotalDeoptimizeCode=new tr.v.Histogram('v8_deoptimize_code_wall_total',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);wallTotalDeoptimizeCode.description='wall total time spent in code deoptimization';for(var e of model.findTopmostSlicesNamed('V8.DeoptimizeCode')){cpuTotalDeoptimizeCode.addSample(e.cpuDuration);wallTotalDeoptimizeCode.addSample(e.duration);}values.addHistogram(cpuTotalDeoptimizeCode);values.addHistogram(wallTotalDeoptimizeCode);}function executionMetric(values,model){computeExecuteMetrics(values,model);computeParseLazyMetrics(values,model);computeCompileIgnitionMetrics(values,model);computeCompileFullCodeMetrics(values,model);computeRecompileMetrics(values,model);computeOptimizeCodeMetrics(values,model);computeDeoptimizeCodeMetrics(values,model);}tr.metrics.MetricRegistry.register(executionMetric);return{executionMetric:executionMetric};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/range.js":52,"../../base/unit.js":62,"../../value/histogram.js":194,"../metric_registry.js":88}],103:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/range.js":53,"../../base/unit.js":63,"../../value/histogram.js":195,"../metric_registry.js":89}],104:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/range.js");require("../../base/unit.js");require("../metric_registry.js");require("./utils.js");require("../../value/histogram.js");'use strict';global.tr.exportTo('tr.metrics.v8',function(){var TARGET_FPS=60;var MS_PER_SECOND=1000;var WINDOW_SIZE_MS=MS_PER_SECOND/TARGET_FPS;function gcMetric(values,model){addDurationOfTopEvents(values,model);addTotalDurationOfTopEvents(values,model);addDurationOfSubEvents(values,model);addIdleTimesOfTopEvents(values,model);addTotalIdleTimesOfTopEvents(values,model);addPercentageInV8ExecuteOfTopEvents(values,model);addTotalPercentageInV8Execute(values,model);addV8ExecuteMutatorUtilization(values,model);}tr.metrics.MetricRegistry.register(gcMetric);var timeDurationInMs_smallerIsBetter=tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;var percentage_biggerIsBetter=tr.b.Unit.byName.normalizedPercentage_biggerIsBetter;var percentage_smallerIsBetter=tr.b.Unit.byName.normalizedPercentage_smallerIsBetter;var CUSTOM_BOUNDARIES=tr.v.HistogramBinBoundaries.createLinear(0,20,200).addExponentialBins(200,100);function createNumericForTopEventTime(name){var n=new tr.v.Histogram(name,timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);n.customizeSummaryOptions({avg:true,count:true,max:true,min:false,std:true,sum:true,percentile:[0.90]});return n;}function createNumericForSubEventTime(name){var n=new tr.v.Histogram(name,timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);n.customizeSummaryOptions({avg:true,count:false,max:true,min:false,std:false,sum:false,percentile:[0.90]});return n;}function createNumericForIdleTime(name){var n=new tr.v.Histogram(name,timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);n.customizeSummaryOptions({avg:true,count:false,max:true,min:false,std:false,sum:true,percentile:[]});return n;}function createPercentage(name,numerator,denominator,unit){var hist=new tr.v.Histogram(name,unit);if(denominator===0)hist.addSample(0);else hist.addSample(numerator/denominator);hist.customizeSummaryOptions({avg:true,count:false,max:false,min:false,std:false,sum:false,percentile:[]});return hist;}function isNotForcedTopGarbageCollectionEvent(event){return tr.metrics.v8.utils.isTopGarbageCollectionEvent(event)&&!tr.metrics.v8.utils.isForcedGarbageCollectionEvent(event);}function isNotForcedSubGarbageCollectionEvent(event){return tr.metrics.v8.utils.isSubGarbageCollectionEvent(event)&&!tr.metrics.v8.utils.isForcedGarbageCollectionEvent(event);}function addDurationOfTopEvents(values,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNotForcedTopGarbageCollectionEvent,tr.metrics.v8.utils.topGarbageCollectionEventName,function(name,events){var cpuDuration=createNumericForTopEventTime(name);events.forEach(function(event){cpuDuration.addSample(event.cpuDuration);});values.addHistogram(cpuDuration);});}function addTotalDurationOfTopEvents(values,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNotForcedTopGarbageCollectionEvent,event=>'v8-gc-total',function(name,events){var cpuDuration=createNumericForTopEventTime(name);events.forEach(function(event){cpuDuration.addSample(event.cpuDuration);});values.addHistogram(cpuDuration);});}function addDurationOfSubEvents(values,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNotForcedSubGarbageCollectionEvent,tr.metrics.v8.utils.subGarbageCollectionEventName,function(name,events){var cpuDuration=createNumericForSubEventTime(name);events.forEach(function(event){cpuDuration.addSample(event.cpuDuration);});values.addHistogram(cpuDuration);});}function addIdleTimesOfTopEvents(values,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNotForcedTopGarbageCollectionEvent,tr.metrics.v8.utils.topGarbageCollectionEventName,function(name,events){addIdleTimes(values,model,name,events);});}function addTotalIdleTimesOfTopEvents(values,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNotForcedTopGarbageCollectionEvent,event=>'v8-gc-total',function(name,events){addIdleTimes(values,model,name,events);});}function addIdleTimes(values,model,name,events){var cpuDuration=createNumericForIdleTime();var insideIdle=createNumericForIdleTime();var outsideIdle=createNumericForIdleTime(name+'_outside_idle');var idleDeadlineOverrun=createNumericForIdleTime(name+'_idle_deadline_overrun');events.forEach(function(event){var idleTask=tr.metrics.v8.utils.findParent(event,tr.metrics.v8.utils.isIdleTask);var inside=0;var overrun=0;if(idleTask){var allottedTime=idleTask['args']['allotted_time_ms'];if(event.duration>allottedTime){overrun=event.duration-allottedTime;inside=event.cpuDuration*allottedTime/event.duration;}else{inside=event.cpuDuration;}}cpuDuration.addSample(event.cpuDuration);insideIdle.addSample(inside);outsideIdle.addSample(event.cpuDuration-inside);idleDeadlineOverrun.addSample(overrun);});values.addHistogram(idleDeadlineOverrun);values.addHistogram(outsideIdle);var percentage=createPercentage(name+'_percentage_idle',insideIdle.sum,cpuDuration.sum,percentage_biggerIsBetter);values.addHistogram(percentage);}function addPercentageInV8ExecuteOfTopEvents(values,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNotForcedTopGarbageCollectionEvent,tr.metrics.v8.utils.topGarbageCollectionEventName,function(name,events){addPercentageInV8Execute(values,model,name,events);});}function addTotalPercentageInV8Execute(values,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNotForcedTopGarbageCollectionEvent,event=>'v8-gc-total',function(name,events){addPercentageInV8Execute(values,model,name,events);});}function addPercentageInV8Execute(values,model,name,events){var cpuDurationInV8Execute=0;var cpuDurationTotal=0;events.forEach(function(event){var v8Execute=tr.metrics.v8.utils.findParent(event,tr.metrics.v8.utils.isV8ExecuteEvent);if(v8Execute){cpuDurationInV8Execute+=event.cpuDuration;}cpuDurationTotal+=event.cpuDuration;});var percentage=createPercentage(name+'_percentage_in_v8_execute',cpuDurationInV8Execute,cpuDurationTotal,percentage_smallerIsBetter);values.addHistogram(percentage);}function addV8ExecuteMutatorUtilization(values,model){tr.metrics.v8.utils.groupAndProcessEvents(model,tr.metrics.v8.utils.isTopV8ExecuteEvent,event=>'v8-execute',function(name,events){events.sort((a,b)=>a.start-b.start);var time=0;var pauses=[];for(var topEvent of events){for(var e of topEvent.enumerateAllDescendents()){if(isNotForcedTopGarbageCollectionEvent(e)){pauses.push({start:e.start-topEvent.start+time,end:e.end-topEvent.start+time});}}time+=topEvent.duration;}var mutatorUtilization=tr.metrics.v8.utils.mutatorUtilization(0,time,WINDOW_SIZE_MS,pauses);[0.90,0.95,0.99].forEach(function(percent){var hist=new tr.v.Histogram('v8-execute-mutator-utilization_pct_0'+percent*100,percentage_biggerIsBetter);hist.addSample(mutatorUtilization.percentile(1.0-percent));values.addHistogram(hist);});var hist=new tr.v.Histogram('v8-execute-mutator-utilization_min',percentage_biggerIsBetter);hist.addSample(mutatorUtilization.min);values.addHistogram(hist);});}return{gcMetric:gcMetric,WINDOW_SIZE_MS:WINDOW_SIZE_MS};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/range.js":52,"../../base/unit.js":62,"../../value/histogram.js":194,"../metric_registry.js":88,"./utils.js":104}],104:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/range.js":53,"../../base/unit.js":63,"../../value/histogram.js":195,"../metric_registry.js":89,"./utils.js":105}],105:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/piecewise_linear_function.js");require("../../base/range.js");require("../../base/range_utils.js");require("../../base/unit.js");require("../metric_registry.js");require("../../value/histogram.js");'use strict';global.tr.exportTo('tr.metrics.v8.utils',function(){var IDLE_TASK_EVENT='SingleThreadIdleTaskRunner::RunTask';var V8_EXECUTE='V8.Execute';var GC_EVENT_PREFIX='V8.GC';var FULL_GC_EVENT='V8.GCCompactor';var LOW_MEMORY_EVENT='V8.GCLowMemoryNotification';var MAJOR_GC_EVENT='MajorGC';var MINOR_GC_EVENT='MinorGC';var TOP_GC_EVENTS={'V8.GCCompactor':'v8-gc-full-mark-compactor','V8.GCFinalizeMC':'v8-gc-latency-mark-compactor','V8.GCFinalizeMCReduceMemory':'v8-gc-memory-mark-compactor','V8.GCIncrementalMarking':'v8-gc-incremental-step','V8.GCIncrementalMarkingFinalize':'v8-gc-incremental-finalize','V8.GCIncrementalMarkingStart':'v8-gc-incremental-start','V8.GCPhantomHandleProcessingCallback':'v8-gc-phantom-handle-callback','V8.GCScavenger':'v8-gc-scavenger'};var LOW_MEMORY_MARK_COMPACTOR='v8-gc-low-memory-mark-compactor';function findParent(event,predicate){var parent=event.parentSlice;while(parent){if(predicate(parent)){return parent;}parent=parent.parentSlice;}return null;}function isIdleTask(event){return event.title===IDLE_TASK_EVENT;}function isLowMemoryEvent(event){return event.title===LOW_MEMORY_EVENT;}function isV8ExecuteEvent(event){return event.title===V8_EXECUTE;}function isTopV8ExecuteEvent(event){return isV8ExecuteEvent(event)&&findParent(isV8ExecuteEvent)===null;}function isGarbageCollectionEvent(event){return event.title&&event.title.startsWith(GC_EVENT_PREFIX)&&event.title!=LOW_MEMORY_EVENT;}function isTopGarbageCollectionEvent(event){return event.title in TOP_GC_EVENTS;}function isForcedGarbageCollectionEvent(event){return findParent(event,isLowMemoryEvent)!==null;}function isSubGarbageCollectionEvent(event){return isGarbageCollectionEvent(event)&&event.parentSlice&&(isTopGarbageCollectionEvent(event.parentSlice)||event.parentSlice.title===MAJOR_GC_EVENT||event.parentSlice.title===MINOR_GC_EVENT);}function topGarbageCollectionEventName(event){if(event.title===FULL_GC_EVENT){if(findParent(event,isLowMemoryEvent)){return LOW_MEMORY_MARK_COMPACTOR;}}return TOP_GC_EVENTS[event.title];}function subGarbageCollectionEventName(event){var topEvent=findParent(event,isTopGarbageCollectionEvent);var prefix=topEvent?topGarbageCollectionEventName(topEvent):'unknown';var name=event.title.replace('V8.GC_MC_','').replace('V8.GC_SCAVENGER_','').replace('V8.GC_','').replace(/_/g,'-').toLowerCase();return prefix+'-'+name;}function groupAndProcessEvents(model,filterCallback,nameCallback,processCallback){var nameToEvents={};for(var event of model.getDescendantEvents()){if(!filterCallback(event))continue;var name=nameCallback(event);nameToEvents[name]=nameToEvents[name]||[];nameToEvents[name].push(event);}tr.b.iterItems(nameToEvents,function(name,events){processCallback(name,events);});}function unionOfIntervals(intervals){if(intervals.length===0)return[];return tr.b.mergeRanges(intervals.map(x=>({min:x.start,max:x.end})),1e-6,function(ranges){return{start:ranges.reduce((acc,x)=>Math.min(acc,x.min),ranges[0].min),end:ranges.reduce((acc,x)=>Math.max(acc,x.max),ranges[0].max)};});}function WindowEndpoint(start,points){this.points=points;this.lastIndex=-1;this.position=start;this.distanceUntilNextPoint=points[0].position-start;this.cummulativePause=0;this.stackDepth=0;}WindowEndpoint.prototype={advance:function(delta){var points=this.points;if(delta<this.distanceUntilNextPoint){this.position+=delta;this.cummulativePause+=this.stackDepth>0?delta:0;this.distanceUntilNextPoint=points[this.lastIndex+1].position-this.position;}else{this.position+=this.distanceUntilNextPoint;this.cummulativePause+=this.stackDepth>0?this.distanceUntilNextPoint:0;this.distanceUntilNextPoint=0;this.lastIndex++;if(this.lastIndex<points.length){this.stackDepth+=points[this.lastIndex].delta;if(this.lastIndex+1<points.length)this.distanceUntilNextPoint=points[this.lastIndex+1].position-this.position;}}}};function mutatorUtilization(start,end,timeWindow,intervals){var mu=new tr.b.PiecewiseLinearFunction();if(end-start<=timeWindow)return mu;if(intervals.length===0){mu.push(start,1.0,end-timeWindow,1.0);return mu;}intervals=unionOfIntervals(intervals);var points=[];intervals.forEach(function(interval){points.push({position:interval.start,delta:1});points.push({position:interval.end,delta:-1});});points.sort((a,b)=>a.position-b.position);points.push({position:end,delta:0});var left=new WindowEndpoint(start,points);var right=new WindowEndpoint(start,points);while(right.position-left.position<timeWindow)right.advance(timeWindow-(right.position-left.position));while(right.lastIndex<points.length){var distanceUntilNextPoint=Math.min(left.distanceUntilNextPoint,right.distanceUntilNextPoint);var position1=left.position;var value1=right.cummulativePause-left.cummulativePause;left.advance(distanceUntilNextPoint);right.advance(distanceUntilNextPoint);if(distanceUntilNextPoint>0){var position2=left.position;var value2=right.cummulativePause-left.cummulativePause;mu.push(position1,1.0-value1/timeWindow,position2,1.0-value2/timeWindow);}}return mu;}function hasV8Stats(globalMemoryDump){var v8stats=undefined;globalMemoryDump.iterateContainerDumps(function(dump){v8stats=v8stats||dump.getMemoryAllocatorDumpByFullName('v8');});return!!v8stats;}function rangeForMemoryDumps(model){var startOfFirstDumpWithV8=model.globalMemoryDumps.filter(hasV8Stats).reduce((start,dump)=>Math.min(start,dump.start),Infinity);if(startOfFirstDumpWithV8===Infinity)return new tr.b.Range();return tr.b.Range.fromExplicitRange(startOfFirstDumpWithV8,Infinity);}return{findParent:findParent,groupAndProcessEvents:groupAndProcessEvents,isForcedGarbageCollectionEvent:isForcedGarbageCollectionEvent,isGarbageCollectionEvent:isGarbageCollectionEvent,isIdleTask:isIdleTask,isLowMemoryEvent:isLowMemoryEvent,isSubGarbageCollectionEvent:isSubGarbageCollectionEvent,isTopGarbageCollectionEvent:isTopGarbageCollectionEvent,isTopV8ExecuteEvent:isTopV8ExecuteEvent,isV8ExecuteEvent:isV8ExecuteEvent,mutatorUtilization:mutatorUtilization,subGarbageCollectionEventName:subGarbageCollectionEventName,topGarbageCollectionEventName:topGarbageCollectionEventName,rangeForMemoryDumps:rangeForMemoryDumps,unionOfIntervals:unionOfIntervals};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/piecewise_linear_function.js":49,"../../base/range.js":52,"../../base/range_utils.js":53,"../../base/unit.js":62,"../../value/histogram.js":194,"../metric_registry.js":88}],105:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/piecewise_linear_function.js":50,"../../base/range.js":53,"../../base/range_utils.js":54,"../../base/unit.js":63,"../../value/histogram.js":195,"../metric_registry.js":89}],106:[function(require,module,exports){
+(function (global){
 "use strict";require("../metric_registry.js");require("../system_health/memory_metric.js");require("./execution_metric.js");require("./gc_metric.js");'use strict';global.tr.exportTo('tr.metrics.v8',function(){function v8AndMemoryMetrics(values,model){tr.metrics.v8.executionMetric(values,model);tr.metrics.v8.gcMetric(values,model);tr.metrics.sh.memoryMetric(values,model,{rangeOfInterest:tr.metrics.v8.utils.rangeForMemoryDumps(model)});}tr.metrics.MetricRegistry.register(v8AndMemoryMetrics);return{v8AndMemoryMetrics:v8AndMemoryMetrics};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../metric_registry.js":88,"../system_health/memory_metric.js":95,"./execution_metric.js":102,"./gc_metric.js":103}],106:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../metric_registry.js":89,"../system_health/memory_metric.js":96,"./execution_metric.js":103,"./gc_metric.js":104}],107:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/unit.js");require("./event_info.js");require("./event_set.js");require("./timed_event.js");'use strict';global.tr.exportTo('tr.model',function(){function Alert(info,start,opt_associatedEvents,opt_args){tr.model.TimedEvent.call(this,start);this.info=info;this.args=opt_args||{};this.associatedEvents=new tr.model.EventSet(opt_associatedEvents);this.associatedEvents.forEach(function(event){event.addAssociatedAlert(this);},this);}Alert.prototype={__proto__:tr.model.TimedEvent.prototype,get title(){return this.info.title;},get colorId(){return this.info.colorId;},get userFriendlyName(){return'Alert '+this.title+' at '+tr.b.Unit.byName.timeStampInMs.format(this.start);}};tr.model.EventRegistry.register(Alert,{name:'alert',pluralName:'alerts'});return{Alert:Alert};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/unit.js":62,"./event_info.js":123,"./event_set.js":125,"./timed_event.js":165}],107:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/unit.js":63,"./event_info.js":124,"./event_set.js":126,"./timed_event.js":166}],108:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/extension_registry.js");require("../base/guid.js");'use strict';global.tr.exportTo('tr.model',function(){function Annotation(){this.guid_=tr.b.GUID.allocateSimple();this.view_=undefined;};Annotation.fromDictIfPossible=function(args){if(args.typeName===undefined)throw new Error('Missing typeName argument');var typeInfo=Annotation.findTypeInfoMatching(function(typeInfo){return typeInfo.metadata.typeName===args.typeName;});if(typeInfo===undefined)return undefined;return typeInfo.constructor.fromDict(args);};Annotation.fromDict=function(){throw new Error('Not implemented');};Annotation.prototype={get guid(){return this.guid_;},onRemove:function(){},toDict:function(){throw new Error('Not implemented');},getOrCreateView:function(viewport){if(!this.view_)this.view_=this.createView_(viewport);return this.view_;},createView_:function(){throw new Error('Not implemented');}};var options=new tr.b.ExtensionRegistryOptions(tr.b.BASIC_REGISTRY_MODE);tr.b.decorateExtensionRegistry(Annotation,options);Annotation.addEventListener('will-register',function(e){if(!e.typeInfo.constructor.hasOwnProperty('fromDict'))throw new Error('Must have fromDict method');if(!e.typeInfo.metadata.typeName)throw new Error('Registered Annotations must provide typeName');});return{Annotation:Annotation};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/extension_registry.js":40,"../base/guid.js":44}],108:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/extension_registry.js":41,"../base/guid.js":45}],109:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/unit.js");require("./timed_event.js");'use strict';global.tr.exportTo('tr.model',function(){function AsyncSlice(category,title,colorId,start,args,duration,opt_isTopLevel,opt_cpuStart,opt_cpuDuration,opt_argsStripped){tr.model.TimedEvent.call(this,start);this.category=category||'';this.originalTitle=title;this.title=title;this.colorId=colorId;this.args=args;this.startStackFrame=undefined;this.endStackFrame=undefined;this.didNotFinish=false;this.important=false;this.subSlices=[];this.parentContainer_=undefined;this.id=undefined;this.startThread=undefined;this.endThread=undefined;this.cpuStart=undefined;this.cpuDuration=undefined;this.argsStripped=false;this.startStackFrame=undefined;this.endStackFrame=undefined;this.duration=duration;this.isTopLevel=opt_isTopLevel===true;if(opt_cpuStart!==undefined)this.cpuStart=opt_cpuStart;if(opt_cpuDuration!==undefined)this.cpuDuration=opt_cpuDuration;if(opt_argsStripped!==undefined)this.argsStripped=opt_argsStripped;}AsyncSlice.prototype={__proto__:tr.model.TimedEvent.prototype,get analysisTypeName(){return this.title;},get parentContainer(){return this.parentContainer_;},set parentContainer(parentContainer){this.parentContainer_=parentContainer;for(var i=0;i<this.subSlices.length;i++){var subSlice=this.subSlices[i];if(subSlice.parentContainer===undefined)subSlice.parentContainer=parentContainer;}},get viewSubGroupTitle(){return this.title;},get userFriendlyName(){return'Async slice '+this.title+' at '+tr.b.Unit.byName.timeStampInMs.format(this.start);},get stableId(){var parentAsyncSliceGroup=this.parentContainer.asyncSliceGroup;return parentAsyncSliceGroup.stableId+'.'+parentAsyncSliceGroup.slices.indexOf(this);},findTopmostSlicesRelativeToThisSlice:function*(eventPredicate,opt_this){if(eventPredicate(this)){yield this;return;}for(var s of this.subSlices)yield*s.findTopmostSlicesRelativeToThisSlice(eventPredicate);},findDescendentSlice:function(targetTitle){if(!this.subSlices)return undefined;for(var i=0;i<this.subSlices.length;i++){if(this.subSlices[i].title==targetTitle)return this.subSlices[i];var slice=this.subSlices[i].findDescendentSlice(targetTitle);if(slice)return slice;}return undefined;},enumerateAllDescendents:function*(){for(var slice of this.subSlices)yield slice;for(var slice of this.subSlices)yield*slice.enumerateAllDescendents();},compareTo:function(that){return this.title.localeCompare(that.title);}};tr.model.EventRegistry.register(AsyncSlice,{name:'asyncSlice',pluralName:'asyncSlices'});return{AsyncSlice:AsyncSlice};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/unit.js":62,"./timed_event.js":165}],109:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/unit.js":63,"./timed_event.js":166}],110:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/guid.js");require("../base/range.js");require("./async_slice.js");require("./event_container.js");'use strict';global.tr.exportTo('tr.model',function(){function AsyncSliceGroup(parentContainer,opt_name){tr.model.EventContainer.call(this);this.parentContainer_=parentContainer;this.slices=[];this.name_=opt_name;this.viewSubGroups_=undefined;}AsyncSliceGroup.prototype={__proto__:tr.model.EventContainer.prototype,get parentContainer(){return this.parentContainer_;},get model(){return this.parentContainer_.parent.model;},get stableId(){return this.parentContainer_.stableId+'.AsyncSliceGroup';},getSettingsKey:function(){if(!this.name_)return undefined;var parentKey=this.parentContainer_.getSettingsKey();if(!parentKey)return undefined;return parentKey+'.'+this.name_;},push:function(slice){slice.parentContainer=this.parentContainer;this.slices.push(slice);return slice;},get length(){return this.slices.length;},shiftTimestampsForward:function(amount){for(var sI=0;sI<this.slices.length;sI++){var slice=this.slices[sI];slice.start=slice.start+amount;var shiftSubSlices=function(subSlices){if(subSlices===undefined||subSlices.length===0)return;for(var sJ=0;sJ<subSlices.length;sJ++){subSlices[sJ].start+=amount;shiftSubSlices(subSlices[sJ].subSlices);}};shiftSubSlices(slice.subSlices);}},updateBounds:function(){this.bounds.reset();for(var i=0;i<this.slices.length;i++){this.bounds.addValue(this.slices[i].start);this.bounds.addValue(this.slices[i].end);}},get viewSubGroups(){if(this.viewSubGroups_===undefined){var prefix='';if(this.name!==undefined)prefix=this.name+'.';else prefix='';var subGroupsByTitle={};for(var i=0;i<this.slices.length;++i){var slice=this.slices[i];var subGroupTitle=slice.viewSubGroupTitle;if(!subGroupsByTitle[subGroupTitle]){subGroupsByTitle[subGroupTitle]=new AsyncSliceGroup(this.parentContainer_,prefix+subGroupTitle);}subGroupsByTitle[subGroupTitle].push(slice);}this.viewSubGroups_=tr.b.dictionaryValues(subGroupsByTitle);this.viewSubGroups_.sort(function(a,b){return a.slices[0].compareTo(b.slices[0]);});}return this.viewSubGroups_;},findTopmostSlicesInThisContainer:function*(eventPredicate,opt_this){for(var slice of this.slices){if(slice.isTopLevel){yield*slice.findTopmostSlicesRelativeToThisSlice(eventPredicate,opt_this);}}},childEvents:function*(){for(var slice of this.slices){yield slice;if(slice.subSlices)yield*slice.subSlices;}},childEventContainers:function*(){}};return{AsyncSliceGroup:AsyncSliceGroup};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/guid.js":44,"../base/range.js":52,"./async_slice.js":108,"./event_container.js":122}],110:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/guid.js":45,"../base/range.js":53,"./async_slice.js":109,"./event_container.js":123}],111:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/iteration_helpers.js");'use strict';global.tr.exportTo('tr.model',function(){var ClockDomainId={BATTOR:'BATTOR',UNKNOWN_CHROME_LEGACY:'UNKNOWN_CHROME_LEGACY',LINUX_CLOCK_MONOTONIC:'LINUX_CLOCK_MONOTONIC',LINUX_FTRACE_GLOBAL:'LINUX_FTRACE_GLOBAL',MAC_MACH_ABSOLUTE_TIME:'MAC_MACH_ABSOLUTE_TIME',WIN_ROLLOVER_PROTECTED_TIME_GET_TIME:'WIN_ROLLOVER_PROTECTED_TIME_GET_TIME',WIN_QPC:'WIN_QPC',TELEMETRY:'TELEMETRY'};var POSSIBLE_CHROME_CLOCK_DOMAINS=new Set([ClockDomainId.UNKNOWN_CHROME_LEGACY,ClockDomainId.LINUX_CLOCK_MONOTONIC,ClockDomainId.MAC_MACH_ABSOLUTE_TIME,ClockDomainId.WIN_ROLLOVER_PROTECTED_TIME_GET_TIME,ClockDomainId.WIN_QPC]);var BATTOR_FAST_SYNC_THRESHOLD_MS=3;function ClockSyncManager(){this.domainsSeen_=new Set();this.markersBySyncId_=new Map();this.transformerMapByDomainId_={};}ClockSyncManager.prototype={addClockSyncMarker:function(domainId,syncId,startTs,opt_endTs){this.onDomainSeen_(domainId);if(tr.b.dictionaryValues(ClockDomainId).indexOf(domainId)<0){throw new Error('"'+domainId+'" is not in the list of known '+'clock domain IDs.');}if(this.modelDomainId_){throw new Error('Cannot add new clock sync markers after getting '+'a model time transformer.');}var marker=new ClockSyncMarker(domainId,startTs,opt_endTs);if(!this.markersBySyncId_.has(syncId)){this.markersBySyncId_.set(syncId,[marker]);return;}var markers=this.markersBySyncId_.get(syncId);if(markers.length===2){throw new Error('Clock sync with ID "'+syncId+'" is already '+'complete - cannot add a third clock sync marker to it.');}if(markers[0].domainId===domainId)throw new Error('A clock domain cannot sync with itself.');markers.push(marker);this.onSyncCompleted_(markers[0],marker);},get markersBySyncId(){return this.markersBySyncId_;},get domainsSeen(){return this.domainsSeen_;},getModelTimeTransformer:function(domainId){return this.getModelTimeTransformerRaw_(domainId).fn;},getModelTimeTransformerError:function(domainId){return this.getModelTimeTransformerRaw_(domainId).error;},getModelTimeTransformerRaw_:function(domainId){this.onDomainSeen_(domainId);if(!this.modelDomainId_)this.selectModelDomainId_();var transformer=this.getTransformerBetween_(domainId,this.modelDomainId_);if(!transformer){throw new Error('No clock sync markers exist pairing clock domain "'+domainId+'" '+'with model clock domain "'+this.modelDomainId_+'".');}return transformer;},getTransformerBetween_:function(fromDomainId,toDomainId){var visitedDomainIds=new Set();var queue=[{domainId:fromDomainId,transformer:Transformer.IDENTITY}];while(queue.length>0){queue.sort((domain1,domain2)=>domain1.transformer.error-domain2.transformer.error);var current=queue.shift();if(current.domainId===toDomainId)return current.transformer;if(visitedDomainIds.has(current.domainId))continue;visitedDomainIds.add(current.domainId);var outgoingTransformers=this.transformerMapByDomainId_[current.domainId];if(!outgoingTransformers)continue;for(var outgoingDomainId in outgoingTransformers){var toNextDomainTransformer=outgoingTransformers[outgoingDomainId];var toCurrentDomainTransformer=current.transformer;queue.push({domainId:outgoingDomainId,transformer:Transformer.compose(toNextDomainTransformer,toCurrentDomainTransformer)});}}return undefined;},selectModelDomainId_:function(){this.ensureAllDomainsAreConnected_();for(var chromeDomainId of POSSIBLE_CHROME_CLOCK_DOMAINS){if(this.domainsSeen_.has(chromeDomainId)){this.modelDomainId_=chromeDomainId;return;}}var domainsSeenArray=Array.from(this.domainsSeen_);domainsSeenArray.sort();this.modelDomainId_=domainsSeenArray[0];},ensureAllDomainsAreConnected_:function(){var firstDomainId=undefined;for(var domainId of this.domainsSeen_){if(!firstDomainId){firstDomainId=domainId;continue;}if(!this.getTransformerBetween_(firstDomainId,domainId)){throw new Error('Unable to select a master clock domain because no '+'path can be found from "'+firstDomainId+'" to "'+domainId+'".');}}return true;},onDomainSeen_:function(domainId){if(domainId===ClockDomainId.UNKNOWN_CHROME_LEGACY&&!this.domainsSeen_.has(ClockDomainId.UNKNOWN_CHROME_LEGACY)){for(var chromeDomainId of POSSIBLE_CHROME_CLOCK_DOMAINS){if(chromeDomainId===ClockDomainId.UNKNOWN_CHROME_LEGACY)continue;this.collapseDomains_(ClockDomainId.UNKNOWN_CHROME_LEGACY,chromeDomainId);}}this.domainsSeen_.add(domainId);},onSyncCompleted_:function(marker1,marker2){var forwardTransformer=Transformer.fromMarkers(marker1,marker2);var backwardTransformer=Transformer.fromMarkers(marker2,marker1);var existingTransformer=this.getOrCreateTransformerMap_(marker1.domainId)[marker2.domainId];if(!existingTransformer||forwardTransformer.error<existingTransformer.error){this.getOrCreateTransformerMap_(marker1.domainId)[marker2.domainId]=forwardTransformer;this.getOrCreateTransformerMap_(marker2.domainId)[marker1.domainId]=backwardTransformer;}},collapseDomains_:function(domain1Id,domain2Id){this.getOrCreateTransformerMap_(domain1Id)[domain2Id]=this.getOrCreateTransformerMap_(domain2Id)[domain1Id]=Transformer.IDENTITY;},getOrCreateTransformerMap_:function(domainId){if(!this.transformerMapByDomainId_[domainId])this.transformerMapByDomainId_[domainId]={};return this.transformerMapByDomainId_[domainId];}};function ClockSyncMarker(domainId,startTs,opt_endTs){this.domainId=domainId;this.startTs=startTs;this.endTs=opt_endTs===undefined?startTs:opt_endTs;}ClockSyncMarker.prototype={get duration(){return this.endTs-this.startTs;},get ts(){return this.startTs+this.duration/2;}};function Transformer(fn,error){this.fn=fn;this.error=error;}Transformer.IDENTITY=new Transformer(tr.b.identity,0);Transformer.compose=function(aToB,bToC){return new Transformer(ts=>bToC.fn(aToB.fn(ts)),aToB.error+bToC.error);};Transformer.fromMarkers=function(fromMarker,toMarker){var fromTs=fromMarker.ts,toTs=toMarker.ts;if(fromMarker.domainId===ClockDomainId.BATTOR&&toMarker.duration>BATTOR_FAST_SYNC_THRESHOLD_MS){toTs=toMarker.startTs;}else if(toMarker.domainId===ClockDomainId.BATTOR&&fromMarker.duration>BATTOR_FAST_SYNC_THRESHOLD_MS){fromTs=fromMarker.startTs;}var tsShift=toTs-fromTs;return new Transformer(ts=>ts+tsShift,fromMarker.duration+toMarker.duration);};return{ClockDomainId:ClockDomainId,ClockSyncManager:ClockSyncManager};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/iteration_helpers.js":46}],111:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/iteration_helpers.js":47}],112:[function(require,module,exports){
+(function (global){
 "use strict";require("./location.js");require("./annotation.js");require("./rect_annotation.js");require("../ui/annotations/comment_box_annotation_view.js");'use strict';global.tr.exportTo('tr.model',function(){function CommentBoxAnnotation(location,text){tr.model.Annotation.apply(this,arguments);this.location=location;this.text=text;}CommentBoxAnnotation.fromDict=function(dict){var args=dict.args;var location=new tr.model.Location(args.location.xWorld,args.location.yComponents);return new tr.model.CommentBoxAnnotation(location,args.text);};CommentBoxAnnotation.prototype={__proto__:tr.model.Annotation.prototype,onRemove:function(){this.view_.removeTextArea();},toDict:function(){return{typeName:'comment_box',args:{text:this.text,location:this.location.toDict()}};},createView_:function(viewport){return new tr.ui.annotations.CommentBoxAnnotationView(viewport,this);}};tr.model.Annotation.register(CommentBoxAnnotation,{typeName:'comment_box'});return{CommentBoxAnnotation:CommentBoxAnnotation};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../ui/annotations/comment_box_annotation_view.js":176,"./annotation.js":107,"./location.js":138,"./rect_annotation.js":151}],112:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../ui/annotations/comment_box_annotation_view.js":177,"./annotation.js":108,"./location.js":139,"./rect_annotation.js":152}],113:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/base.js");'use strict';global.tr.exportTo('tr.model',function(){var CompoundEventSelectionState={NOT_SELECTED:0,EVENT_SELECTED:0x1,SOME_ASSOCIATED_EVENTS_SELECTED:0x2,ALL_ASSOCIATED_EVENTS_SELECTED:0x4,EVENT_AND_SOME_ASSOCIATED_SELECTED:0x1|0x2,EVENT_AND_ALL_ASSOCIATED_SELECTED:0x1|0x4};return{CompoundEventSelectionState:CompoundEventSelectionState};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/base.js":33}],113:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/base.js":34}],114:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/base.js");'use strict';global.tr.exportTo('tr.model',function(){return{BROWSER_PROCESS_PID_REF:-1,OBJECT_DEFAULT_SCOPE:'ptr'};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/base.js":33}],114:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/base.js":34}],115:[function(require,module,exports){
+(function (global){
 "use strict";require("./timed_event.js");'use strict';global.tr.exportTo('tr.model',function(){function ContainerMemoryDump(start){tr.model.TimedEvent.call(this,start);this.levelOfDetail=undefined;this.memoryAllocatorDumps_=undefined;this.memoryAllocatorDumpsByFullName_=undefined;};ContainerMemoryDump.LevelOfDetail={BACKGROUND:0,LIGHT:1,DETAILED:2};ContainerMemoryDump.prototype={__proto__:tr.model.TimedEvent.prototype,shiftTimestampsForward:function(amount){this.start+=amount;},get memoryAllocatorDumps(){return this.memoryAllocatorDumps_;},set memoryAllocatorDumps(memoryAllocatorDumps){this.memoryAllocatorDumps_=memoryAllocatorDumps;this.forceRebuildingMemoryAllocatorDumpByFullNameIndex();},getMemoryAllocatorDumpByFullName:function(fullName){if(this.memoryAllocatorDumps_===undefined)return undefined;if(this.memoryAllocatorDumpsByFullName_===undefined){var index={};function addDumpsToIndex(dumps){dumps.forEach(function(dump){index[dump.fullName]=dump;addDumpsToIndex(dump.children);});};addDumpsToIndex(this.memoryAllocatorDumps_);this.memoryAllocatorDumpsByFullName_=index;}return this.memoryAllocatorDumpsByFullName_[fullName];},forceRebuildingMemoryAllocatorDumpByFullNameIndex:function(){this.memoryAllocatorDumpsByFullName_=undefined;},iterateRootAllocatorDumps:function(fn,opt_this){if(this.memoryAllocatorDumps===undefined)return;this.memoryAllocatorDumps.forEach(fn,opt_this||this);}};return{ContainerMemoryDump:ContainerMemoryDump};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./timed_event.js":165}],115:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./timed_event.js":166}],116:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/guid.js");require("../base/range.js");require("./counter_series.js");require("./event_container.js");'use strict';global.tr.exportTo('tr.model',function(){function Counter(parent,id,category,name){tr.model.EventContainer.call(this);this.parent_=parent;this.id_=id;this.category_=category||'';this.name_=name;this.series_=[];this.totals=[];}Counter.prototype={__proto__:tr.model.EventContainer.prototype,get parent(){return this.parent_;},get id(){return this.id_;},get category(){return this.category_;},get name(){return this.name_;},childEvents:function*(){},childEventContainers:function*(){yield*this.series;},set timestamps(arg){throw new Error('Bad counter API. No cookie.');},set seriesNames(arg){throw new Error('Bad counter API. No cookie.');},set seriesColors(arg){throw new Error('Bad counter API. No cookie.');},set samples(arg){throw new Error('Bad counter API. No cookie.');},addSeries:function(series){series.counter=this;series.seriesIndex=this.series_.length;this.series_.push(series);return series;},getSeries:function(idx){return this.series_[idx];},get series(){return this.series_;},get numSeries(){return this.series_.length;},get numSamples(){if(this.series_.length===0)return 0;return this.series_[0].length;},get timestamps(){if(this.series_.length===0)return[];return this.series_[0].timestamps;},getSampleStatistics:function(sampleIndices){sampleIndices.sort();var ret=[];this.series_.forEach(function(series){ret.push(series.getStatistics(sampleIndices));});return ret;},shiftTimestampsForward:function(amount){for(var i=0;i<this.series_.length;++i)this.series_[i].shiftTimestampsForward(amount);},updateBounds:function(){this.totals=[];this.maxTotal=0;this.bounds.reset();if(this.series_.length===0)return;var firstSeries=this.series_[0];var lastSeries=this.series_[this.series_.length-1];this.bounds.addValue(firstSeries.getTimestamp(0));this.bounds.addValue(lastSeries.getTimestamp(lastSeries.length-1));var numSeries=this.numSeries;this.maxTotal=-Infinity;for(var i=0;i<firstSeries.length;++i){var total=0;this.series_.forEach(function(series){total+=series.getSample(i).value;this.totals.push(total);}.bind(this));this.maxTotal=Math.max(total,this.maxTotal);}}};Counter.compare=function(x,y){var tmp=x.parent.compareTo(y);if(tmp!=0)return tmp;var tmp=x.name.localeCompare(y.name);if(tmp==0)return x.tid-y.tid;return tmp;};return{Counter:Counter};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/guid.js":44,"../base/range.js":52,"./counter_series.js":117,"./event_container.js":122}],116:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/guid.js":45,"../base/range.js":53,"./counter_series.js":118,"./event_container.js":123}],117:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/iteration_helpers.js");require("../base/sorted_array_utils.js");require("../base/unit.js");require("./event.js");require("./event_registry.js");'use strict';global.tr.exportTo('tr.model',function(){function CounterSample(series,timestamp,value){tr.model.Event.call(this);this.series_=series;this.timestamp_=timestamp;this.value_=value;}CounterSample.groupByTimestamp=function(samples){var samplesByTimestamp=tr.b.group(samples,function(sample){return sample.timestamp;});var timestamps=tr.b.dictionaryKeys(samplesByTimestamp);timestamps.sort();var groups=[];for(var i=0;i<timestamps.length;i++){var ts=timestamps[i];var group=samplesByTimestamp[ts];group.sort(function(x,y){return x.series.seriesIndex-y.series.seriesIndex;});groups.push(group);}return groups;};CounterSample.prototype={__proto__:tr.model.Event.prototype,get series(){return this.series_;},get timestamp(){return this.timestamp_;},get value(){return this.value_;},set timestamp(timestamp){this.timestamp_=timestamp;},addBoundsToRange:function(range){range.addValue(this.timestamp);},getSampleIndex:function(){return tr.b.findLowIndexInSortedArray(this.series.timestamps,function(x){return x;},this.timestamp_);},get userFriendlyName(){return'Counter sample from '+this.series_.title+' at '+tr.b.Unit.byName.timeStampInMs.format(this.timestamp);}};tr.model.EventRegistry.register(CounterSample,{name:'counterSample',pluralName:'counterSamples'});return{CounterSample:CounterSample};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/iteration_helpers.js":46,"../base/sorted_array_utils.js":57,"../base/unit.js":62,"./event.js":121,"./event_registry.js":124}],117:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/iteration_helpers.js":47,"../base/sorted_array_utils.js":58,"../base/unit.js":63,"./event.js":122,"./event_registry.js":125}],118:[function(require,module,exports){
+(function (global){
 "use strict";require("./counter_sample.js");require("./event_container.js");'use strict';global.tr.exportTo('tr.model',function(){var CounterSample=tr.model.CounterSample;function CounterSeries(name,color){tr.model.EventContainer.call(this);this.name_=name;this.color_=color;this.timestamps_=[];this.samples_=[];this.counter=undefined;this.seriesIndex=undefined;}CounterSeries.prototype={__proto__:tr.model.EventContainer.prototype,get length(){return this.timestamps_.length;},get name(){return this.name_;},get color(){return this.color_;},get samples(){return this.samples_;},get timestamps(){return this.timestamps_;},getSample:function(idx){return this.samples_[idx];},getTimestamp:function(idx){return this.timestamps_[idx];},addCounterSample:function(ts,val){var sample=new CounterSample(this,ts,val);this.addSample(sample);return sample;},addSample:function(sample){this.timestamps_.push(sample.timestamp);this.samples_.push(sample);},getStatistics:function(sampleIndices){var sum=0;var min=Number.MAX_VALUE;var max=-Number.MAX_VALUE;for(var i=0;i<sampleIndices.length;++i){var sample=this.getSample(sampleIndices[i]).value;sum+=sample;min=Math.min(sample,min);max=Math.max(sample,max);}return{min:min,max:max,avg:sum/sampleIndices.length,start:this.getSample(sampleIndices[0]).value,end:this.getSample(sampleIndices.length-1).value};},shiftTimestampsForward:function(amount){for(var i=0;i<this.timestamps_.length;++i){this.timestamps_[i]+=amount;this.samples_[i].timestamp=this.timestamps_[i];}},childEvents:function*(){yield*this.samples_;},childEventContainers:function*(){}};return{CounterSeries:CounterSeries};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./counter_sample.js":116,"./event_container.js":122}],118:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./counter_sample.js":117,"./event_container.js":123}],119:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/range.js");require("./counter.js");require("./cpu_slice.js");require("./process_base.js");require("./thread_time_slice.js");'use strict';global.tr.exportTo('tr.model',function(){var ColorScheme=tr.b.ColorScheme;var Counter=tr.model.Counter;var CpuSlice=tr.model.CpuSlice;function Cpu(kernel,number){if(kernel===undefined||number===undefined)throw new Error('Missing arguments');this.kernel=kernel;this.cpuNumber=number;this.slices=[];this.counters={};this.bounds_=new tr.b.Range();this.samples_=undefined;this.lastActiveTimestamp_=undefined;this.lastActiveThread_=undefined;this.lastActiveName_=undefined;this.lastActiveArgs_=undefined;}Cpu.prototype={__proto__:tr.model.EventContainer.prototype,get samples(){return this.samples_;},get userFriendlyName(){return'CPU '+this.cpuNumber;},findTopmostSlicesInThisContainer:function*(eventPredicate,opt_this){for(var s of this.slices){yield*s.findTopmostSlicesRelativeToThisSlice(eventPredicate,opt_this);}},childEvents:function*(){yield*this.slices;if(this.samples_)yield*this.samples_;},childEventContainers:function*(){yield*tr.b.dictionaryValues(this.counters);},getOrCreateCounter:function(cat,name){var id=cat+'.'+name;if(!this.counters[id])this.counters[id]=new Counter(this,id,cat,name);return this.counters[id];},getCounter:function(cat,name){var id=cat+'.'+name;if(!this.counters[id])return undefined;return this.counters[id];},shiftTimestampsForward:function(amount){for(var sI=0;sI<this.slices.length;sI++)this.slices[sI].start=this.slices[sI].start+amount;for(var id in this.counters)this.counters[id].shiftTimestampsForward(amount);},updateBounds:function(){this.bounds_.reset();if(this.slices.length){this.bounds_.addValue(this.slices[0].start);this.bounds_.addValue(this.slices[this.slices.length-1].end);}for(var id in this.counters){this.counters[id].updateBounds();this.bounds_.addRange(this.counters[id].bounds);}if(this.samples_&&this.samples_.length){this.bounds_.addValue(this.samples_[0].start);this.bounds_.addValue(this.samples_[this.samples_.length-1].end);}},createSubSlices:function(){this.samples_=this.kernel.model.samples.filter(function(sample){return sample.cpu==this;},this);},addCategoriesToDict:function(categoriesDict){for(var i=0;i<this.slices.length;i++)categoriesDict[this.slices[i].category]=true;for(var id in this.counters)categoriesDict[this.counters[id].category]=true;for(var i=0;i<this.samples_.length;i++)categoriesDict[this.samples_[i].category]=true;},indexOf:function(cpuSlice){var i=tr.b.findLowIndexInSortedArray(this.slices,function(slice){return slice.start;},cpuSlice.start);if(this.slices[i]!==cpuSlice)return undefined;return i;},closeActiveThread:function(endTimestamp,args){if(this.lastActiveThread_==undefined||this.lastActiveThread_==0)return;if(endTimestamp<this.lastActiveTimestamp_){throw new Error('The end timestamp of a thread running on CPU '+this.cpuNumber+' is before its start timestamp.');}for(var key in args){this.lastActiveArgs_[key]=args[key];}var duration=endTimestamp-this.lastActiveTimestamp_;var slice=new tr.model.CpuSlice('',this.lastActiveName_,ColorScheme.getColorIdForGeneralPurposeString(this.lastActiveName_),this.lastActiveTimestamp_,this.lastActiveArgs_,duration);slice.cpu=this;this.slices.push(slice);this.lastActiveTimestamp_=undefined;this.lastActiveThread_=undefined;this.lastActiveName_=undefined;this.lastActiveArgs_=undefined;},switchActiveThread:function(timestamp,oldThreadArgs,newThreadId,newThreadName,newThreadArgs){this.closeActiveThread(timestamp,oldThreadArgs);this.lastActiveTimestamp_=timestamp;this.lastActiveThread_=newThreadId;this.lastActiveName_=newThreadName;this.lastActiveArgs_=newThreadArgs;},getFreqStatsForRange:function(range){var stats={};function addStatsForFreq(freqSample,index){var freqEnd=index<freqSample.series_.length-1?freqSample.series_.samples_[index+1].timestamp:range.max;var freqRange=tr.b.Range.fromExplicitRange(freqSample.timestamp,freqEnd);var intersection=freqRange.findIntersection(range);if(!(freqSample.value in stats))stats[freqSample.value]=0;stats[freqSample.value]+=intersection.duration;}var freqCounter=this.getCounter('','Clock Frequency');if(freqCounter!==undefined){var freqSeries=freqCounter.getSeries(0);if(!freqSeries)return;tr.b.iterateOverIntersectingIntervals(freqSeries.samples_,function(x){return x.timestamp;},function(x,index){return index<freqSeries.length-1?freqSeries.samples_[index+1].timestamp:range.max;},range.min,range.max,addStatsForFreq);}return stats;}};Cpu.compare=function(x,y){return x.cpuNumber-y.cpuNumber;};return{Cpu:Cpu};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/range.js":52,"./counter.js":115,"./cpu_slice.js":119,"./process_base.js":149,"./thread_time_slice.js":163}],119:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/range.js":53,"./counter.js":116,"./cpu_slice.js":120,"./process_base.js":150,"./thread_time_slice.js":164}],120:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/range.js");require("./thread_time_slice.js");'use strict';global.tr.exportTo('tr.model',function(){var Slice=tr.model.Slice;function CpuSlice(cat,title,colorId,start,args,opt_duration){Slice.apply(this,arguments);this.threadThatWasRunning=undefined;this.cpu=undefined;}CpuSlice.prototype={__proto__:Slice.prototype,get analysisTypeName(){return'tr.ui.analysis.CpuSlice';},getAssociatedTimeslice:function(){if(!this.threadThatWasRunning)return undefined;var timeSlices=this.threadThatWasRunning.timeSlices;for(var i=0;i<timeSlices.length;i++){var timeSlice=timeSlices[i];if(timeSlice.start!==this.start)continue;if(timeSlice.duration!==this.duration)continue;return timeSlice;}return undefined;}};tr.model.EventRegistry.register(CpuSlice,{name:'cpuSlice',pluralName:'cpuSlices'});return{CpuSlice:CpuSlice};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/range.js":52,"./thread_time_slice.js":163}],120:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/range.js":53,"./thread_time_slice.js":164}],121:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/guid.js");require("../base/range.js");require("./event_container.js");require("./power_series.js");'use strict';global.tr.exportTo('tr.model',function(){function Device(model){if(!model)throw new Error('Must provide a model.');tr.model.EventContainer.call(this);this.powerSeries_=undefined;this.vSyncTimestamps_=[];};Device.compare=function(x,y){return x.guid-y.guid;};Device.prototype={__proto__:tr.model.EventContainer.prototype,compareTo:function(that){return Device.compare(this,that);},get userFriendlyName(){return'Device';},get userFriendlyDetails(){return'Device';},get stableId(){return'Device';},getSettingsKey:function(){return'device';},get powerSeries(){return this.powerSeries_;},set powerSeries(powerSeries){this.powerSeries_=powerSeries;},get vSyncTimestamps(){return this.vSyncTimestamps_;},set vSyncTimestamps(value){this.vSyncTimestamps_=value;},updateBounds:function(){this.bounds.reset();for(var child of this.childEventContainers()){child.updateBounds();this.bounds.addRange(child.bounds);}},shiftTimestampsForward:function(amount){for(var child of this.childEventContainers()){child.shiftTimestampsForward(amount);}for(var i=0;i<this.vSyncTimestamps_.length;i++)this.vSyncTimestamps_[i]+=amount;},addCategoriesToDict:function(categoriesDict){},childEventContainers:function*(){if(this.powerSeries_)yield this.powerSeries_;}};return{Device:Device};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/guid.js":44,"../base/range.js":52,"./event_container.js":122,"./power_series.js":147}],121:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/guid.js":45,"../base/range.js":53,"./event_container.js":123,"./power_series.js":148}],122:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/guid.js");require("../base/range.js");require("./event_set.js");require("./selectable_item.js");require("./selection_state.js");'use strict';global.tr.exportTo('tr.model',function(){var SelectableItem=tr.model.SelectableItem;var SelectionState=tr.model.SelectionState;var IMMUTABLE_EMPTY_SET=tr.model.EventSet.IMMUTABLE_EMPTY_SET;function Event(){SelectableItem.call(this,this);this.guid_=tr.b.GUID.allocateSimple();this.selectionState=SelectionState.NONE;this.info=undefined;}Event.prototype={__proto__:SelectableItem.prototype,get guid(){return this.guid_;},get stableId(){return undefined;},get range(){var range=new tr.b.Range();this.addBoundsToRange(range);return range;},associatedAlerts:IMMUTABLE_EMPTY_SET,addAssociatedAlert:function(alert){if(this.associatedAlerts===IMMUTABLE_EMPTY_SET)this.associatedAlerts=new tr.model.EventSet();this.associatedAlerts.push(alert);},addBoundsToRange:function(range){}};return{Event:Event};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/guid.js":44,"../base/range.js":52,"./event_set.js":125,"./selectable_item.js":154,"./selection_state.js":155}],122:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/guid.js":45,"../base/range.js":53,"./event_set.js":126,"./selectable_item.js":155,"./selection_state.js":156}],123:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/base.js");require("../base/guid.js");require("../base/range.js");'use strict';global.tr.exportTo('tr.model',function(){function EventContainer(){this.guid_=tr.b.GUID.allocateSimple();this.important=true;this.bounds_=new tr.b.Range();}EventContainer.prototype={get guid(){return this.guid_;},get stableId(){throw new Error('Not implemented');},get bounds(){return this.bounds_;},updateBounds:function(){throw new Error('Not implemented');},shiftTimestampsForward:function(amount){throw new Error('Not implemented');},childEvents:function*(){},getDescendantEvents:function*(){yield*this.childEvents();for(var container of this.childEventContainers())yield*container.getDescendantEvents();},childEventContainers:function*(){},getDescendantEventContainers:function*(){yield this;for(var container of this.childEventContainers())yield*container.getDescendantEventContainers();},findTopmostSlicesInThisContainer:function*(eventPredicate,opt_this){},findTopmostSlices:function*(eventPredicate){for(var ec of this.getDescendantEventContainers())yield*ec.findTopmostSlicesInThisContainer(eventPredicate);},findTopmostSlicesNamed:function*(name){yield*this.findTopmostSlices(e=>e.title===name);}};return{EventContainer:EventContainer};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/base.js":33,"../base/guid.js":44,"../base/range.js":52}],123:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/base.js":34,"../base/guid.js":45,"../base/range.js":53}],124:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/color_scheme.js");'use strict';global.tr.exportTo('tr.model',function(){var ColorScheme=tr.b.ColorScheme;function EventInfo(title,description,docLinks){this.title=title;this.description=description;this.docLinks=docLinks;this.colorId=ColorScheme.getColorIdForGeneralPurposeString(title);}return{EventInfo:EventInfo};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/color_scheme.js":37}],124:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/color_scheme.js":38}],125:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/extension_registry.js");'use strict';global.tr.exportTo('tr.model',function(){function EventRegistry(){}var options=new tr.b.ExtensionRegistryOptions(tr.b.BASIC_REGISTRY_MODE);tr.b.decorateExtensionRegistry(EventRegistry,options);EventRegistry.addEventListener('will-register',function(e){var metadata=e.typeInfo.metadata;if(metadata.name===undefined)throw new Error('Registered events must provide name metadata');if(metadata.pluralName===undefined)throw new Error('Registered events must provide pluralName metadata');if(metadata.subTypes===undefined){metadata.subTypes={};var options=new tr.b.ExtensionRegistryOptions(tr.b.TYPE_BASED_REGISTRY_MODE);options.mandatoryBaseClass=e.typeInfo.constructor;options.defaultConstructor=e.typeInfo.constructor;tr.b.decorateExtensionRegistry(metadata.subTypes,options);}else{if(!metadata.subTypes.register)throw new Error('metadata.subTypes must be an extension registry.');}e.typeInfo.constructor.subTypes=metadata.subTypes;});var eventsByTypeName=undefined;EventRegistry.getEventTypeInfoByTypeName=function(typeName){if(eventsByTypeName===undefined){eventsByTypeName={};EventRegistry.getAllRegisteredTypeInfos().forEach(function(typeInfo){eventsByTypeName[typeInfo.metadata.name]=typeInfo;});}return eventsByTypeName[typeName];};EventRegistry.addEventListener('registry-changed',function(){eventsByTypeName=undefined;});function convertCamelCaseToTitleCase(name){var result=name.replace(/[A-Z]/g,' $&');result=result.charAt(0).toUpperCase()+result.slice(1);return result;}EventRegistry.getUserFriendlySingularName=function(typeName){var typeInfo=EventRegistry.getEventTypeInfoByTypeName(typeName);var str=typeInfo.metadata.name;return convertCamelCaseToTitleCase(str);};EventRegistry.getUserFriendlyPluralName=function(typeName){var typeInfo=EventRegistry.getEventTypeInfoByTypeName(typeName);var str=typeInfo.metadata.pluralName;return convertCamelCaseToTitleCase(str);};return{EventRegistry:EventRegistry};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/extension_registry.js":40}],125:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/extension_registry.js":41}],126:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/event.js");require("../base/guid.js");require("../base/iteration_helpers.js");require("../base/range.js");require("./event_registry.js");'use strict';global.tr.exportTo('tr.model',function(){var EventRegistry=tr.model.EventRegistry;var RequestSelectionChangeEvent=tr.b.Event.bind(undefined,'requestSelectionChange',true,false);function EventSet(opt_events){this.bounds_=new tr.b.Range();this.events_=new Set();if(opt_events){if(opt_events instanceof Array){for(var event of opt_events)this.push(event);}else if(opt_events instanceof EventSet){this.addEventSet(opt_events);}else{this.push(opt_events);}}}EventSet.prototype={__proto__:Object.prototype,get bounds(){return this.bounds_;},get duration(){if(this.bounds_.isEmpty)return 0;return this.bounds_.max-this.bounds_.min;},get length(){return this.events_.size;},get guid(){return this.guid_;},*[Symbol.iterator](){for(var event of this.events_)yield event;},clear:function(){this.bounds_=new tr.b.Range();this.events_.clear();},push:function(event){if(event.guid==undefined)throw new Error('Event must have a GUID');if(!this.events_.has(event)){this.events_.add(event);if(event.addBoundsToRange)if(this.bounds_!==undefined)event.addBoundsToRange(this.bounds_);}return event;},contains:function(event){if(this.events_.has(event))return event;else return undefined;},addEventSet:function(eventSet){for(var event of eventSet)this.push(event);},intersectionIsEmpty:function(otherEventSet){return!this.some(event=>otherEventSet.contains(event));},equals:function(that){if(this.length!==that.length)return false;return this.every(event=>that.contains(event));},sortEvents:function(compare){var ary=this.toArray();ary.sort(compare);this.clear();for(var event of ary)this.push(event);},getEventsOrganizedByBaseType:function(opt_pruneEmpty){var allTypeInfos=EventRegistry.getAllRegisteredTypeInfos();var events=this.getEventsOrganizedByCallback(function(event){var maxEventIndex=-1;var maxEventTypeInfo=undefined;allTypeInfos.forEach(function(eventTypeInfo,eventIndex){if(!(event instanceof eventTypeInfo.constructor))return;if(eventIndex>maxEventIndex){maxEventIndex=eventIndex;maxEventTypeInfo=eventTypeInfo;}});if(maxEventIndex==-1){console.log(event);throw new Error('Unrecognized event type');}return maxEventTypeInfo.metadata.name;});if(!opt_pruneEmpty){allTypeInfos.forEach(function(eventTypeInfo){if(events[eventTypeInfo.metadata.name]===undefined)events[eventTypeInfo.metadata.name]=new EventSet();});}return events;},getEventsOrganizedByTitle:function(){return this.getEventsOrganizedByCallback(function(event){if(event.title===undefined)throw new Error('An event didn\'t have a title!');return event.title;});},getEventsOrganizedByCallback:function(cb,opt_this){var groupedEvents=tr.b.group(this,cb,opt_this||this);return tr.b.mapItems(groupedEvents,(_,events)=>new EventSet(events));},enumEventsOfType:function(type,func){for(var event of this)if(event instanceof type)func(event);},get userFriendlyName(){if(this.length===0){throw new Error('Empty event set');}var eventsByBaseType=this.getEventsOrganizedByBaseType(true);var eventTypeName=tr.b.dictionaryKeys(eventsByBaseType)[0];if(this.length===1){var tmp=EventRegistry.getUserFriendlySingularName(eventTypeName);return tr.b.getOnlyElement(this.events_).userFriendlyName;}var numEventTypes=tr.b.dictionaryLength(eventsByBaseType);if(numEventTypes!==1){return this.length+' events of various types';}var tmp=EventRegistry.getUserFriendlyPluralName(eventTypeName);return this.length+' '+tmp;},filter:function(fn,opt_this){var res=new EventSet();for(var event of this)if(fn.call(opt_this,event))res.push(event);return res;},toArray:function(){var ary=[];for(var event of this)ary.push(event);return ary;},forEach:function(fn,opt_this){for(var event of this)fn.call(opt_this,event);},map:function(fn,opt_this){var res=[];for(var event of this)res.push(fn.call(opt_this,event));return res;},every:function(fn,opt_this){for(var event of this)if(!fn.call(opt_this,event))return false;return true;},some:function(fn,opt_this){for(var event of this)if(fn.call(opt_this,event))return true;return false;},asDict:function(){var stableIds=[];for(var event of this)stableIds.push(event.stableId);return{'events':stableIds};},asSet:function(){return this.events_;}};EventSet.IMMUTABLE_EMPTY_SET=function(){var s=new EventSet();s.push=function(){throw new Error('Cannot push to an immutable event set');};s.addEventSet=function(){throw new Error('Cannot add to an immutable event set');};Object.freeze(s);return s;}();return{EventSet:EventSet,RequestSelectionChangeEvent:RequestSelectionChangeEvent};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/event.js":38,"../base/guid.js":44,"../base/iteration_helpers.js":46,"../base/range.js":52,"./event_registry.js":124}],126:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/event.js":39,"../base/guid.js":45,"../base/iteration_helpers.js":47,"../base/range.js":53,"./event_registry.js":125}],127:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/unit.js");require("./timed_event.js");'use strict';global.tr.exportTo('tr.model',function(){function FlowEvent(category,id,title,colorId,start,args,opt_duration){tr.model.TimedEvent.call(this,start);this.category=category||'';this.title=title;this.colorId=colorId;this.start=start;this.args=args;this.id=id;this.startSlice=undefined;this.endSlice=undefined;this.startStackFrame=undefined;this.endStackFrame=undefined;if(opt_duration!==undefined)this.duration=opt_duration;}FlowEvent.prototype={__proto__:tr.model.TimedEvent.prototype,get userFriendlyName(){return'Flow event named '+this.title+' at '+tr.b.Unit.byName.timeStampInMs.format(this.timestamp);}};tr.model.EventRegistry.register(FlowEvent,{name:'flowEvent',pluralName:'flowEvents'});return{FlowEvent:FlowEvent};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/unit.js":62,"./timed_event.js":165}],127:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/unit.js":63,"./timed_event.js":166}],128:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/color_scheme.js");require("../base/statistics.js");require("./event.js");require("./event_set.js");'use strict';global.tr.exportTo('tr.model',function(){var ColorScheme=tr.b.ColorScheme;var Statistics=tr.b.Statistics;var FRAME_PERF_CLASS={GOOD:'good',BAD:'bad',TERRIBLE:'terrible',NEUTRAL:'generic_work'};function Frame(associatedEvents,threadTimeRanges,opt_args){tr.model.Event.call(this);this.threadTimeRanges=threadTimeRanges;this.associatedEvents=new tr.model.EventSet(associatedEvents);this.args=opt_args||{};this.title='Frame';this.start=Statistics.min(threadTimeRanges,function(x){return x.start;});this.end=Statistics.max(threadTimeRanges,function(x){return x.end;});this.totalDuration=Statistics.sum(threadTimeRanges,function(x){return x.end-x.start;});this.perfClass=FRAME_PERF_CLASS.NEUTRAL;};Frame.prototype={__proto__:tr.model.Event.prototype,set perfClass(perfClass){this.colorId=ColorScheme.getColorIdForReservedName(perfClass);this.perfClass_=perfClass;},get perfClass(){return this.perfClass_;},shiftTimestampsForward:function(amount){this.start+=amount;this.end+=amount;for(var i=0;i<this.threadTimeRanges.length;i++){this.threadTimeRanges[i].start+=amount;this.threadTimeRanges[i].end+=amount;}},addBoundsToRange:function(range){range.addValue(this.start);range.addValue(this.end);}};tr.model.EventRegistry.register(Frame,{name:'frame',pluralName:'frames'});return{Frame:Frame,FRAME_PERF_CLASS:FRAME_PERF_CLASS};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/color_scheme.js":37,"../base/statistics.js":58,"./event.js":121,"./event_set.js":125}],128:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/color_scheme.js":38,"../base/statistics.js":59,"./event.js":122,"./event_set.js":126}],129:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/iteration_helpers.js");require("../base/unit.js");require("./container_memory_dump.js");require("./event_registry.js");require("./memory_allocator_dump.js");require("../value/numeric.js");'use strict';global.tr.exportTo('tr.model',function(){function GlobalMemoryDump(model,start){tr.model.ContainerMemoryDump.call(this,start);this.model=model;this.processMemoryDumps={};}var SIZE_NUMERIC_NAME=tr.model.MemoryAllocatorDump.SIZE_NUMERIC_NAME;var EFFECTIVE_SIZE_NUMERIC_NAME=tr.model.MemoryAllocatorDump.EFFECTIVE_SIZE_NUMERIC_NAME;var MemoryAllocatorDumpInfoType=tr.model.MemoryAllocatorDumpInfoType;var PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN=MemoryAllocatorDumpInfoType.PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN;var PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER=MemoryAllocatorDumpInfoType.PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER;function inPlaceFilter(array,predicate,opt_this){opt_this=opt_this||this;var nextPosition=0;for(var i=0;i<array.length;i++){if(!predicate.call(opt_this,array[i],i))continue;if(nextPosition<i)array[nextPosition]=array[i];nextPosition++;}if(nextPosition<array.length)array.length=nextPosition;}function getSize(dump){var numeric=dump.numerics[SIZE_NUMERIC_NAME];if(numeric===undefined)return 0;return numeric.value;}function hasSize(dump){return dump.numerics[SIZE_NUMERIC_NAME]!==undefined;}function optional(value,defaultValue){if(value===undefined)return defaultValue;return value;}GlobalMemoryDump.prototype={__proto__:tr.model.ContainerMemoryDump.prototype,get userFriendlyName(){return'Global memory dump at '+tr.b.Unit.byName.timeStampInMs.format(this.start);},get containerName(){return'global space';},finalizeGraph:function(){this.removeWeakDumps();this.setUpTracingOverheadOwnership();this.aggregateNumerics();this.calculateSizes();this.calculateEffectiveSizes();this.discountTracingOverheadFromVmRegions();this.forceRebuildingMemoryAllocatorDumpByFullNameIndices();},removeWeakDumps:function(){this.traverseAllocatorDumpsInDepthFirstPreOrder(function(dump){if(dump.weak)return;if(dump.owns!==undefined&&dump.owns.target.weak||dump.parent!==undefined&&dump.parent.weak){dump.weak=true;}});function removeWeakDumpsFromListRecursively(dumps){inPlaceFilter(dumps,function(dump){if(dump.weak){return false;}removeWeakDumpsFromListRecursively(dump.children);inPlaceFilter(dump.ownedBy,function(ownershipLink){return!ownershipLink.source.weak;});return true;});}this.iterateContainerDumps(function(containerDump){var memoryAllocatorDumps=containerDump.memoryAllocatorDumps;if(memoryAllocatorDumps!==undefined)removeWeakDumpsFromListRecursively(memoryAllocatorDumps);});},calculateSizes:function(){this.traverseAllocatorDumpsInDepthFirstPostOrder(this.calculateMemoryAllocatorDumpSize_.bind(this));},calculateMemoryAllocatorDumpSize_:function(dump){var shouldDefineSize=false;function getDependencySize(dependencyDump){var numeric=dependencyDump.numerics[SIZE_NUMERIC_NAME];if(numeric===undefined)return 0;shouldDefineSize=true;return numeric.value;}var sizeNumeric=dump.numerics[SIZE_NUMERIC_NAME];var size=0;var checkDependencySizeIsConsistent=function(){};if(sizeNumeric!==undefined){size=sizeNumeric.value;shouldDefineSize=true;if(sizeNumeric.unit!==tr.b.Unit.byName.sizeInBytes_smallerIsBetter){this.model.importWarning({type:'memory_dump_parse_error',message:'Invalid unit of \'size\' numeric of memory allocator '+'dump '+dump.quantifiedName+': '+sizeNumeric.unit.unitName+'.'});}checkDependencySizeIsConsistent=function(dependencySize,dependencyInfoType,dependencyName){if(size>=dependencySize)return;this.model.importWarning({type:'memory_dump_parse_error',message:'Size provided by memory allocator dump \''+dump.fullName+'\''+tr.b.Unit.byName.sizeInBytes.format(size)+') is less than '+dependencyName+' ('+tr.b.Unit.byName.sizeInBytes.format(dependencySize)+').'});dump.infos.push({type:dependencyInfoType,providedSize:size,dependencySize:dependencySize});}.bind(this);}var aggregatedChildrenSize=0;var allOverlaps={};dump.children.forEach(function(childDump){function aggregateDescendantDump(descendantDump){var ownedDumpLink=descendantDump.owns;if(ownedDumpLink!==undefined&&ownedDumpLink.target.isDescendantOf(dump)){var ownedChildDump=ownedDumpLink.target;while(ownedChildDump.parent!==dump)ownedChildDump=ownedChildDump.parent;if(childDump!==ownedChildDump){var ownedBySiblingSize=getDependencySize(descendantDump);if(ownedBySiblingSize>0){var previousTotalOwnedBySiblingSize=ownedChildDump.ownedBySiblingSizes.get(childDump)||0;var updatedTotalOwnedBySiblingSize=previousTotalOwnedBySiblingSize+ownedBySiblingSize;ownedChildDump.ownedBySiblingSizes.set(childDump,updatedTotalOwnedBySiblingSize);}}return;}if(descendantDump.children.length===0){aggregatedChildrenSize+=getDependencySize(descendantDump);return;}descendantDump.children.forEach(aggregateDescendantDump);}aggregateDescendantDump(childDump);});checkDependencySizeIsConsistent(aggregatedChildrenSize,PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN,'the aggregated size of its children');var largestOwnerSize=0;dump.ownedBy.forEach(function(ownershipLink){var owner=ownershipLink.source;var ownerSize=getDependencySize(owner);largestOwnerSize=Math.max(largestOwnerSize,ownerSize);});checkDependencySizeIsConsistent(largestOwnerSize,PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER,'the size of its largest owner');if(!shouldDefineSize){delete dump.numerics[SIZE_NUMERIC_NAME];return;}size=Math.max(size,aggregatedChildrenSize,largestOwnerSize);dump.numerics[SIZE_NUMERIC_NAME]=new tr.v.ScalarNumeric(tr.b.Unit.byName.sizeInBytes_smallerIsBetter,size);if(aggregatedChildrenSize<size&&dump.children!==undefined&&dump.children.length>0){var virtualChild=new tr.model.MemoryAllocatorDump(dump.containerMemoryDump,dump.fullName+'/<unspecified>');virtualChild.parent=dump;dump.children.unshift(virtualChild);virtualChild.numerics[SIZE_NUMERIC_NAME]=new tr.v.ScalarNumeric(tr.b.Unit.byName.sizeInBytes_smallerIsBetter,size-aggregatedChildrenSize);}},calculateEffectiveSizes:function(){this.traverseAllocatorDumpsInDepthFirstPostOrder(this.calculateDumpSubSizes_.bind(this));this.traverseAllocatorDumpsInDepthFirstPostOrder(this.calculateDumpOwnershipCoefficient_.bind(this));this.traverseAllocatorDumpsInDepthFirstPreOrder(this.calculateDumpCumulativeOwnershipCoefficient_.bind(this));this.traverseAllocatorDumpsInDepthFirstPostOrder(this.calculateDumpEffectiveSize_.bind(this));},calculateDumpSubSizes_:function(dump){if(!hasSize(dump))return;if(dump.children===undefined||dump.children.length===0){var size=getSize(dump);dump.notOwningSubSize_=size;dump.notOwnedSubSize_=size;return;}var notOwningSubSize=0;dump.children.forEach(function(childDump){if(childDump.owns!==undefined)return;notOwningSubSize+=optional(childDump.notOwningSubSize_,0);});dump.notOwningSubSize_=notOwningSubSize;var notOwnedSubSize=0;dump.children.forEach(function(childDump){if(childDump.ownedBy.length===0){notOwnedSubSize+=optional(childDump.notOwnedSubSize_,0);return;}var largestChildOwnerSize=0;childDump.ownedBy.forEach(function(ownershipLink){largestChildOwnerSize=Math.max(largestChildOwnerSize,getSize(ownershipLink.source));});notOwnedSubSize+=getSize(childDump)-largestChildOwnerSize;});dump.notOwnedSubSize_=notOwnedSubSize;},calculateDumpOwnershipCoefficient_:function(dump){if(!hasSize(dump))return;if(dump.ownedBy.length===0)return;var owners=dump.ownedBy.map(function(ownershipLink){return{dump:ownershipLink.source,importance:optional(ownershipLink.importance,0),notOwningSubSize:optional(ownershipLink.source.notOwningSubSize_,0)};});owners.sort(function(a,b){if(a.importance===b.importance)return a.notOwningSubSize-b.notOwningSubSize;return b.importance-a.importance;});var currentImportanceStartPos=0;var alreadyAttributedSubSize=0;while(currentImportanceStartPos<owners.length){var currentImportance=owners[currentImportanceStartPos].importance;var nextImportanceStartPos=currentImportanceStartPos+1;while(nextImportanceStartPos<owners.length&&owners[nextImportanceStartPos].importance===currentImportance){nextImportanceStartPos++;}var attributedNotOwningSubSize=0;for(var pos=currentImportanceStartPos;pos<nextImportanceStartPos;pos++){var owner=owners[pos];var notOwningSubSize=owner.notOwningSubSize;if(notOwningSubSize>alreadyAttributedSubSize){attributedNotOwningSubSize+=(notOwningSubSize-alreadyAttributedSubSize)/(nextImportanceStartPos-pos);alreadyAttributedSubSize=notOwningSubSize;}var owningCoefficient=0;if(notOwningSubSize!==0)owningCoefficient=attributedNotOwningSubSize/notOwningSubSize;owner.dump.owningCoefficient_=owningCoefficient;}currentImportanceStartPos=nextImportanceStartPos;}var notOwnedSubSize=optional(dump.notOwnedSubSize_,0);var remainderSubSize=notOwnedSubSize-alreadyAttributedSubSize;var ownedCoefficient=0;if(notOwnedSubSize!==0)ownedCoefficient=remainderSubSize/notOwnedSubSize;dump.ownedCoefficient_=ownedCoefficient;},calculateDumpCumulativeOwnershipCoefficient_:function(dump){if(!hasSize(dump))return;var cumulativeOwnedCoefficient=optional(dump.ownedCoefficient_,1);var parent=dump.parent;if(dump.parent!==undefined)cumulativeOwnedCoefficient*=dump.parent.cumulativeOwnedCoefficient_;dump.cumulativeOwnedCoefficient_=cumulativeOwnedCoefficient;var cumulativeOwningCoefficient;if(dump.owns!==undefined){cumulativeOwningCoefficient=dump.owningCoefficient_*dump.owns.target.cumulativeOwningCoefficient_;}else if(dump.parent!==undefined){cumulativeOwningCoefficient=dump.parent.cumulativeOwningCoefficient_;}else{cumulativeOwningCoefficient=1;}dump.cumulativeOwningCoefficient_=cumulativeOwningCoefficient;},calculateDumpEffectiveSize_:function(dump){if(!hasSize(dump)){delete dump.numerics[EFFECTIVE_SIZE_NUMERIC_NAME];return;}var effectiveSize;if(dump.children===undefined||dump.children.length===0){effectiveSize=getSize(dump)*dump.cumulativeOwningCoefficient_*dump.cumulativeOwnedCoefficient_;}else{effectiveSize=0;dump.children.forEach(function(childDump){if(!hasSize(childDump))return;effectiveSize+=childDump.numerics[EFFECTIVE_SIZE_NUMERIC_NAME].value;});}dump.numerics[EFFECTIVE_SIZE_NUMERIC_NAME]=new tr.v.ScalarNumeric(tr.b.Unit.byName.sizeInBytes_smallerIsBetter,effectiveSize);},aggregateNumerics:function(){this.iterateRootAllocatorDumps(function(dump){dump.aggregateNumericsRecursively(this.model);});this.iterateRootAllocatorDumps(this.propagateNumericsAndDiagnosticsRecursively);tr.b.iterItems(this.processMemoryDumps,function(pid,processMemoryDump){processMemoryDump.iterateRootAllocatorDumps(function(dump){dump.aggregateNumericsRecursively(this.model);},this);},this);},propagateNumericsAndDiagnosticsRecursively:function(globalAllocatorDump){['numerics','diagnostics'].forEach(function(field){tr.b.iterItems(globalAllocatorDump[field],function(name,value){globalAllocatorDump.ownedBy.forEach(function(ownershipLink){var processAllocatorDump=ownershipLink.source;if(processAllocatorDump[field][name]!==undefined){return;}processAllocatorDump[field][name]=value;});});});globalAllocatorDump.children.forEach(this.propagateNumericsAndDiagnosticsRecursively,this);},setUpTracingOverheadOwnership:function(){tr.b.iterItems(this.processMemoryDumps,function(pid,dump){dump.setUpTracingOverheadOwnership(this.model);},this);},discountTracingOverheadFromVmRegions:function(){tr.b.iterItems(this.processMemoryDumps,function(pid,dump){dump.discountTracingOverheadFromVmRegions(this.model);},this);},forceRebuildingMemoryAllocatorDumpByFullNameIndices:function(){this.iterateContainerDumps(function(containerDump){containerDump.forceRebuildingMemoryAllocatorDumpByFullNameIndex();});},iterateContainerDumps:function(fn){fn.call(this,this);tr.b.iterItems(this.processMemoryDumps,function(pid,processDump){fn.call(this,processDump);},this);},iterateAllRootAllocatorDumps:function(fn){this.iterateContainerDumps(function(containerDump){containerDump.iterateRootAllocatorDumps(fn,this);});},traverseAllocatorDumpsInDepthFirstPostOrder:function(fn){var visitedDumps=new WeakSet();var openDumps=new WeakSet();function visit(dump){if(visitedDumps.has(dump))return;if(openDumps.has(dump))throw new Error(dump.userFriendlyName+' contains a cycle');openDumps.add(dump);dump.ownedBy.forEach(function(ownershipLink){visit.call(this,ownershipLink.source);},this);dump.children.forEach(visit,this);fn.call(this,dump);visitedDumps.add(dump);openDumps.delete(dump);}this.iterateAllRootAllocatorDumps(visit);},traverseAllocatorDumpsInDepthFirstPreOrder:function(fn){var visitedDumps=new WeakSet();function visit(dump){if(visitedDumps.has(dump))return;if(dump.owns!==undefined&&!visitedDumps.has(dump.owns.target))return;if(dump.parent!==undefined&&!visitedDumps.has(dump.parent))return;fn.call(this,dump);visitedDumps.add(dump);dump.ownedBy.forEach(function(ownershipLink){visit.call(this,ownershipLink.source);},this);dump.children.forEach(visit,this);}this.iterateAllRootAllocatorDumps(visit);}};tr.model.EventRegistry.register(GlobalMemoryDump,{name:'globalMemoryDump',pluralName:'globalMemoryDumps'});return{GlobalMemoryDump:GlobalMemoryDump};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/iteration_helpers.js":46,"../base/unit.js":62,"../value/numeric.js":195,"./container_memory_dump.js":114,"./event_registry.js":124,"./memory_allocator_dump.js":139}],129:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/iteration_helpers.js":47,"../base/unit.js":63,"../value/numeric.js":196,"./container_memory_dump.js":115,"./event_registry.js":125,"./memory_allocator_dump.js":140}],130:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/base.js");'use strict';global.tr.exportTo('tr.model',function(){function HeapEntry(heapDump,leafStackFrame,objectTypeName,size,count){this.heapDump=heapDump;this.leafStackFrame=leafStackFrame;this.objectTypeName=objectTypeName;this.size=size;this.count=count;}function HeapDump(processMemoryDump,allocatorName){this.processMemoryDump=processMemoryDump;this.allocatorName=allocatorName;this.entries=[];}HeapDump.prototype={addEntry:function(leafStackFrame,objectTypeName,size,count){var entry=new HeapEntry(this,leafStackFrame,objectTypeName,size,count);this.entries.push(entry);return entry;}};return{HeapEntry:HeapEntry,HeapDump:HeapDump};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/base.js":33}],130:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/base.js":34}],131:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/iteration_helpers.js");require("./chrome_process_helper.js");'use strict';global.tr.exportTo('tr.model.helpers',function(){function ChromeBrowserHelper(modelHelper,process){tr.model.helpers.ChromeProcessHelper.call(this,modelHelper,process);this.mainThread_=process.findAtMostOneThreadNamed('CrBrowserMain');if(!process.name)process.name=ChromeBrowserHelper.PROCESS_NAME;}ChromeBrowserHelper.PROCESS_NAME='Browser';ChromeBrowserHelper.isBrowserProcess=function(process){return!!process.findAtMostOneThreadNamed('CrBrowserMain');};ChromeBrowserHelper.prototype={__proto__:tr.model.helpers.ChromeProcessHelper.prototype,get browserName(){var hasInProcessRendererThread=this.process.findAllThreadsNamed('Chrome_InProcRendererThread').length>0;return hasInProcessRendererThread?'webview':'chrome';},get rendererHelpers(){return this.modelHelper.rendererHelpers;},getLoadingEventsInRange:function(rangeOfInterest){return this.getAllAsyncSlicesMatching(function(slice){return slice.title.indexOf('WebContentsImpl Loading')===0&&rangeOfInterest.intersectsExplicitRangeInclusive(slice.start,slice.end);});},getCommitProvisionalLoadEventsInRange:function(rangeOfInterest){return this.getAllAsyncSlicesMatching(function(slice){return slice.title==='RenderFrameImpl::didCommitProvisionalLoad'&&rangeOfInterest.intersectsExplicitRangeInclusive(slice.start,slice.end);});},get hasLatencyEvents(){var hasLatency=false;for(var thread of this.modelHelper.model.getAllThreads())for(var event of thread.getDescendantEvents()){if(!event.isTopLevel)continue;if(!(event instanceof tr.e.cc.InputLatencyAsyncSlice))continue;hasLatency=true;}return hasLatency;},getLatencyEventsInRange:function(rangeOfInterest){return this.getAllAsyncSlicesMatching(function(slice){return slice.title.indexOf('InputLatency')===0&&rangeOfInterest.intersectsExplicitRangeInclusive(slice.start,slice.end);});},getAllAsyncSlicesMatching:function(pred,opt_this){var events=[];this.iterAllThreads(function(thread){for(var slice of thread.getDescendantEvents())if(pred.call(opt_this,slice))events.push(slice);});return events;},getAllNetworkEventsInRange:function(rangeOfInterest){var networkEvents=[];this.modelHelper.model.getAllThreads().forEach(function(thread){thread.asyncSliceGroup.slices.forEach(function(slice){var match=false;if(slice.category=='net'||slice.category=='disabled-by-default-netlog'||slice.category=='netlog'){match=true;}if(!match)return;if(rangeOfInterest.intersectsExplicitRangeInclusive(slice.start,slice.end))networkEvents.push(slice);});});return networkEvents;},iterAllThreads:function(func,opt_this){tr.b.iterItems(this.process.threads,function(tid,thread){func.call(opt_this,thread);});tr.b.iterItems(this.rendererHelpers,function(pid,rendererHelper){var rendererProcess=rendererHelper.process;tr.b.iterItems(rendererProcess.threads,function(tid,thread){func.call(opt_this,thread);});},this);}};return{ChromeBrowserHelper:ChromeBrowserHelper};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/iteration_helpers.js":46,"./chrome_process_helper.js":133}],131:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/iteration_helpers.js":47,"./chrome_process_helper.js":134}],132:[function(require,module,exports){
+(function (global){
 "use strict";require("./chrome_process_helper.js");'use strict';global.tr.exportTo('tr.model.helpers',function(){function ChromeGpuHelper(modelHelper,process){tr.model.helpers.ChromeProcessHelper.call(this,modelHelper,process);this.mainThread_=process.findAtMostOneThreadNamed('CrGpuMain');if(!process.name)process.name=ChromeGpuHelper.PROCESS_NAME;};ChromeGpuHelper.PROCESS_NAME='GPU Process';ChromeGpuHelper.isGpuProcess=function(process){if(process.findAtMostOneThreadNamed('CrBrowserMain')||process.findAtMostOneThreadNamed('CrRendererMain'))return false;return process.findAtMostOneThreadNamed('CrGpuMain');};ChromeGpuHelper.prototype={__proto__:tr.model.helpers.ChromeProcessHelper.prototype,get mainThread(){return this.mainThread_;}};return{ChromeGpuHelper:ChromeGpuHelper};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./chrome_process_helper.js":133}],132:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./chrome_process_helper.js":134}],133:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/guid.js");require("../../base/iteration_helpers.js");require("./chrome_browser_helper.js");require("./chrome_gpu_helper.js");require("./chrome_renderer_helper.js");'use strict';global.tr.exportTo('tr.model.helpers',function(){function findChromeBrowserProcesses(model){return model.getAllProcesses(tr.model.helpers.ChromeBrowserHelper.isBrowserProcess);}function findChromeRenderProcesses(model){return model.getAllProcesses(tr.model.helpers.ChromeRendererHelper.isRenderProcess);}function findChromeGpuProcess(model){var gpuProcesses=model.getAllProcesses(tr.model.helpers.ChromeGpuHelper.isGpuProcess);if(gpuProcesses.length!==1)return undefined;return gpuProcesses[0];}function ChromeModelHelper(model){this.model_=model;var browserProcesses=findChromeBrowserProcesses(model);this.browserHelpers_=browserProcesses.map(p=>new tr.model.helpers.ChromeBrowserHelper(this,p));var gpuProcess=findChromeGpuProcess(model);if(gpuProcess){this.gpuHelper_=new tr.model.helpers.ChromeGpuHelper(this,gpuProcess);}else{this.gpuHelper_=undefined;}var rendererProcesses_=findChromeRenderProcesses(model);this.rendererHelpers_={};rendererProcesses_.forEach(function(renderProcess){var rendererHelper=new tr.model.helpers.ChromeRendererHelper(this,renderProcess);this.rendererHelpers_[rendererHelper.pid]=rendererHelper;},this);}ChromeModelHelper.guid=tr.b.GUID.allocateSimple();ChromeModelHelper.supportsModel=function(model){if(findChromeBrowserProcesses(model).length)return true;if(findChromeRenderProcesses(model).length)return true;return false;};ChromeModelHelper.prototype={get pid(){throw new Error('woah');},get process(){throw new Error('woah');},get model(){return this.model_;},get browserProcess(){if(this.browserHelper===undefined)return undefined;return this.browserHelper.process;},get browserHelper(){return this.browserHelpers_[0];},get browserHelpers(){return this.browserHelpers_;},get gpuHelper(){return this.gpuHelper_;},get rendererHelpers(){return this.rendererHelpers_;}};return{ChromeModelHelper:ChromeModelHelper};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/guid.js":44,"../../base/iteration_helpers.js":46,"./chrome_browser_helper.js":130,"./chrome_gpu_helper.js":131,"./chrome_renderer_helper.js":134}],133:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/guid.js":45,"../../base/iteration_helpers.js":47,"./chrome_browser_helper.js":131,"./chrome_gpu_helper.js":132,"./chrome_renderer_helper.js":135}],134:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/base.js");'use strict';global.tr.exportTo('tr.model.helpers',function(){var MAIN_FRAMETIME_TYPE='main_frametime_type';var IMPL_FRAMETIME_TYPE='impl_frametime_type';var MAIN_RENDERING_STATS='BenchmarkInstrumentation::MainThreadRenderingStats';var IMPL_RENDERING_STATS='BenchmarkInstrumentation::ImplThreadRenderingStats';function getSlicesIntersectingRange(rangeOfInterest,slices){var slicesInFilterRange=[];for(var i=0;i<slices.length;i++){var slice=slices[i];if(rangeOfInterest.intersectsExplicitRangeInclusive(slice.start,slice.end))slicesInFilterRange.push(slice);}return slicesInFilterRange;}function ChromeProcessHelper(modelHelper,process){this.modelHelper=modelHelper;this.process=process;}ChromeProcessHelper.prototype={get pid(){return this.process.pid;},getFrameEventsInRange:function(frametimeType,range){var titleToGet=frametimeType===MAIN_FRAMETIME_TYPE?MAIN_RENDERING_STATS:IMPL_RENDERING_STATS;var frameEvents=[];for(var event of this.process.getDescendantEvents())if(event.title===titleToGet)if(range.intersectsExplicitRangeInclusive(event.start,event.end))frameEvents.push(event);frameEvents.sort(function(a,b){return a.start-b.start;});return frameEvents;}};function getFrametimeDataFromEvents(frameEvents){var frametimeData=[];for(var i=1;i<frameEvents.length;i++){var diff=frameEvents[i].start-frameEvents[i-1].start;frametimeData.push({'x':frameEvents[i].start,'frametime':diff});}return frametimeData;}return{ChromeProcessHelper:ChromeProcessHelper,MAIN_FRAMETIME_TYPE:MAIN_FRAMETIME_TYPE,IMPL_FRAMETIME_TYPE:IMPL_FRAMETIME_TYPE,MAIN_RENDERING_STATS:MAIN_RENDERING_STATS,IMPL_RENDERING_STATS:IMPL_RENDERING_STATS,getSlicesIntersectingRange:getSlicesIntersectingRange,getFrametimeDataFromEvents:getFrametimeDataFromEvents};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/base.js":33}],134:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/base.js":34}],135:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/range.js");require("../../extras/chrome/chrome_user_friendly_category_driver.js");require("./chrome_process_helper.js");'use strict';global.tr.exportTo('tr.model.helpers',function(){function ChromeRendererHelper(modelHelper,process){tr.model.helpers.ChromeProcessHelper.call(this,modelHelper,process);this.mainThread_=process.findAtMostOneThreadNamed('CrRendererMain');this.compositorThread_=process.findAtMostOneThreadNamed('Compositor');this.rasterWorkerThreads_=process.findAllThreadsMatching(function(t){if(t.name===undefined)return false;if(t.name.indexOf('CompositorTileWorker')===0)return true;if(t.name.indexOf('CompositorRasterWorker')===0)return true;return false;});this.isChromeTracingUI_=process.labels!==undefined&&process.labels.length===1&&process.labels[0]==='chrome://tracing';if(!process.name)process.name=ChromeRendererHelper.PROCESS_NAME;}ChromeRendererHelper.PROCESS_NAME='Renderer';ChromeRendererHelper.isRenderProcess=function(process){if(process.findAtMostOneThreadNamed('CrRendererMain'))return true;if(process.findAtMostOneThreadNamed('Compositor'))return true;return false;};ChromeRendererHelper.prototype={__proto__:tr.model.helpers.ChromeProcessHelper.prototype,get mainThread(){return this.mainThread_;},get compositorThread(){return this.compositorThread_;},get rasterWorkerThreads(){return this.rasterWorkerThreads_;},get isChromeTracingUI(){return this.isChromeTracingUI_;},generateTimeBreakdownTree:function(start,end){if(this.mainThread===null)return;var breakdownMap={};var range=tr.b.Range.fromExplicitRange(start,end);for(var title of tr.e.chrome.ChromeUserFriendlyCategoryDriver.ALL_TITLES){breakdownMap[title]={total:0,events:{}};}breakdownMap['idle']={total:0,events:{}};var totalIdleTime=end-start;for(var event of this.mainThread.getDescendantEvents()){if(!range.intersectsExplicitRangeExclusive(event.start,event.end))continue;if(event.selfTime===undefined)continue;var title=tr.e.chrome.ChromeUserFriendlyCategoryDriver.fromEvent(event);var wallTimeIntersectionRatio=0;if(event.duration>0){wallTimeIntersectionRatio=range.findExplicitIntersectionDuration(event.start,event.end)/event.duration;}var v8Runtime=event.args['runtime-call-stat'];if(v8Runtime!==undefined){try{var v8RuntimeObject=JSON.parse(v8Runtime);for(var runtimeCall in v8RuntimeObject){if(v8RuntimeObject[runtimeCall].length==2){if(breakdownMap['v8_runtime'].events[runtimeCall]===undefined){breakdownMap['v8_runtime'].events[runtimeCall]=0;}var runtimeTime=v8RuntimeObject[runtimeCall][1]*wallTimeIntersectionRatio/1000;breakdownMap['v8_runtime'].total+=runtimeTime;breakdownMap['v8_runtime'].events[runtimeCall]+=runtimeTime;}}}catch(e){console.warn(e);}}var approximatedSelfTimeContribution=event.selfTime*wallTimeIntersectionRatio;breakdownMap[title].total+=approximatedSelfTimeContribution;if(breakdownMap[title].events[event.title]===undefined)breakdownMap[title].events[event.title]=0;breakdownMap[title].events[event.title]+=approximatedSelfTimeContribution;totalIdleTime-=approximatedSelfTimeContribution;}breakdownMap['idle'].total=totalIdleTime;return breakdownMap;}};return{ChromeRendererHelper:ChromeRendererHelper};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/range.js":52,"../../extras/chrome/chrome_user_friendly_category_driver.js":68,"./chrome_process_helper.js":133}],135:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/range.js":53,"../../extras/chrome/chrome_user_friendly_category_driver.js":69,"./chrome_process_helper.js":134}],136:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/unit.js");require("./timed_event.js");'use strict';global.tr.exportTo('tr.model',function(){var InstantEventType={GLOBAL:1,PROCESS:2};function InstantEvent(category,title,colorId,start,args){tr.model.TimedEvent.call(this,start);this.category=category||'';this.title=title;this.colorId=colorId;this.args=args;this.type=undefined;}InstantEvent.prototype={__proto__:tr.model.TimedEvent.prototype};function GlobalInstantEvent(category,title,colorId,start,args){InstantEvent.apply(this,arguments);this.type=InstantEventType.GLOBAL;}GlobalInstantEvent.prototype={__proto__:InstantEvent.prototype,get userFriendlyName(){return'Global instant event '+this.title+' @ '+tr.b.Unit.byName.timeStampInMs.format(start);}};function ProcessInstantEvent(category,title,colorId,start,args){InstantEvent.apply(this,arguments);this.type=InstantEventType.PROCESS;}ProcessInstantEvent.prototype={__proto__:InstantEvent.prototype,get userFriendlyName(){return'Process-level instant event '+this.title+' @ '+tr.b.Unit.byName.timeStampInMs.format(start);}};tr.model.EventRegistry.register(InstantEvent,{name:'instantEvent',pluralName:'instantEvents'});return{GlobalInstantEvent:GlobalInstantEvent,ProcessInstantEvent:ProcessInstantEvent,InstantEventType:InstantEventType,InstantEvent:InstantEvent};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/unit.js":62,"./timed_event.js":165}],136:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/unit.js":63,"./timed_event.js":166}],137:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/iteration_helpers.js");require("./event_set.js");'use strict';global.tr.exportTo('tr.model',function(){function getAssociatedEvents(irs){var allAssociatedEvents=new tr.model.EventSet();irs.forEach(function(ir){ir.associatedEvents.forEach(function(event){if(event instanceof tr.model.FlowEvent)return;allAssociatedEvents.push(event);});});return allAssociatedEvents;}function getUnassociatedEvents(model,associatedEvents){var unassociatedEvents=new tr.model.EventSet();for(var proc of model.getAllProcesses())for(var thread of tr.b.dictionaryValues(proc.threads))for(var event of thread.sliceGroup.getDescendantEvents())if(!associatedEvents.contains(event))unassociatedEvents.push(event);return unassociatedEvents;}function getTotalCpuDuration(events){var cpuMs=0;events.forEach(function(event){if(event.cpuSelfTime)cpuMs+=event.cpuSelfTime;});return cpuMs;}function getIRCoverageFromModel(model){var associatedEvents=getAssociatedEvents(model.userModel.expectations);if(!associatedEvents.length)return undefined;var unassociatedEvents=getUnassociatedEvents(model,associatedEvents);var associatedCpuMs=getTotalCpuDuration(associatedEvents);var unassociatedCpuMs=getTotalCpuDuration(unassociatedEvents);var totalEventCount=associatedEvents.length+unassociatedEvents.length;var totalCpuMs=associatedCpuMs+unassociatedCpuMs;var coveredEventsCpuTimeRatio=undefined;if(totalCpuMs!==0)coveredEventsCpuTimeRatio=associatedCpuMs/totalCpuMs;return{associatedEventsCount:associatedEvents.length,unassociatedEventsCount:unassociatedEvents.length,associatedEventsCpuTimeMs:associatedCpuMs,unassociatedEventsCpuTimeMs:unassociatedCpuMs,coveredEventsCountRatio:associatedEvents.length/totalEventCount,coveredEventsCpuTimeRatio:coveredEventsCpuTimeRatio};}return{getIRCoverageFromModel:getIRCoverageFromModel,getAssociatedEvents:getAssociatedEvents,getUnassociatedEvents:getUnassociatedEvents};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/iteration_helpers.js":46,"./event_set.js":125}],137:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/iteration_helpers.js":47,"./event_set.js":126}],138:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/iteration_helpers.js");require("./cpu.js");require("./process_base.js");'use strict';global.tr.exportTo('tr.model',function(){var Cpu=tr.model.Cpu;var ProcessBase=tr.model.ProcessBase;function Kernel(model){ProcessBase.call(this,model);this.cpus={};this.softwareMeasuredCpuCount_=undefined;};Kernel.compare=function(x,y){return 0;};Kernel.prototype={__proto__:ProcessBase.prototype,compareTo:function(that){return Kernel.compare(this,that);},get userFriendlyName(){return'Kernel';},get userFriendlyDetails(){return'Kernel';},get stableId(){return'Kernel';},getOrCreateCpu:function(cpuNumber){if(!this.cpus[cpuNumber])this.cpus[cpuNumber]=new Cpu(this,cpuNumber);return this.cpus[cpuNumber];},get softwareMeasuredCpuCount(){return this.softwareMeasuredCpuCount_;},set softwareMeasuredCpuCount(softwareMeasuredCpuCount){if(this.softwareMeasuredCpuCount_!==undefined&&this.softwareMeasuredCpuCount_!==softwareMeasuredCpuCount){throw new Error('Cannot change the softwareMeasuredCpuCount once it is set');}this.softwareMeasuredCpuCount_=softwareMeasuredCpuCount;},get bestGuessAtCpuCount(){var realCpuCount=tr.b.dictionaryLength(this.cpus);if(realCpuCount!==0)return realCpuCount;return this.softwareMeasuredCpuCount;},updateBounds:function(){ProcessBase.prototype.updateBounds.call(this);for(var cpuNumber in this.cpus){var cpu=this.cpus[cpuNumber];cpu.updateBounds();this.bounds.addRange(cpu.bounds);}},createSubSlices:function(){ProcessBase.prototype.createSubSlices.call(this);for(var cpuNumber in this.cpus){var cpu=this.cpus[cpuNumber];cpu.createSubSlices();}},addCategoriesToDict:function(categoriesDict){ProcessBase.prototype.addCategoriesToDict.call(this,categoriesDict);for(var cpuNumber in this.cpus)this.cpus[cpuNumber].addCategoriesToDict(categoriesDict);},getSettingsKey:function(){return'kernel';},childEventContainers:function*(){yield*ProcessBase.prototype.childEventContainers.call(this);yield*tr.b.dictionaryValues(this.cpus);}};return{Kernel:Kernel};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/iteration_helpers.js":46,"./cpu.js":118,"./process_base.js":149}],138:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/iteration_helpers.js":47,"./cpu.js":119,"./process_base.js":150}],139:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/base.js");'use strict';global.tr.exportTo('tr.model',function(){function YComponent(stableId,yPercentOffset){this.stableId=stableId;this.yPercentOffset=yPercentOffset;}YComponent.prototype={toDict:function(){return{stableId:this.stableId,yPercentOffset:this.yPercentOffset};}};function Location(xWorld,yComponents){this.xWorld_=xWorld;this.yComponents_=yComponents;};Location.fromViewCoordinates=function(viewport,viewX,viewY){var dt=viewport.currentDisplayTransform;var xWorld=dt.xViewToWorld(viewX);var yComponents=[];var elem=document.elementFromPoint(viewX+viewport.modelTrackContainer.canvas.offsetLeft,viewY+viewport.modelTrackContainer.canvas.offsetTop);while(elem instanceof tr.ui.tracks.Track){if(elem.eventContainer){var boundRect=elem.getBoundingClientRect();var yPercentOffset=(viewY-boundRect.top)/boundRect.height;yComponents.push(new YComponent(elem.eventContainer.stableId,yPercentOffset));}elem=elem.parentElement;}if(yComponents.length==0)return;return new Location(xWorld,yComponents);};Location.fromStableIdAndTimestamp=function(viewport,stableId,ts){var xWorld=ts;var yComponents=[];var containerToTrack=viewport.containerToTrackMap;var elem=containerToTrack.getTrackByStableId(stableId);if(!elem)return;var firstY=elem.getBoundingClientRect().top;while(elem instanceof tr.ui.tracks.Track){if(elem.eventContainer){var boundRect=elem.getBoundingClientRect();var yPercentOffset=(firstY-boundRect.top)/boundRect.height;yComponents.push(new YComponent(elem.eventContainer.stableId,yPercentOffset));}elem=elem.parentElement;}if(yComponents.length==0)return;return new Location(xWorld,yComponents);};Location.prototype={get xWorld(){return this.xWorld_;},getContainingTrack:function(viewport){var containerToTrack=viewport.containerToTrackMap;for(var i in this.yComponents_){var yComponent=this.yComponents_[i];var track=containerToTrack.getTrackByStableId(yComponent.stableId);if(track!==undefined)return track;}},toViewCoordinates:function(viewport){var dt=viewport.currentDisplayTransform;var containerToTrack=viewport.containerToTrackMap;var viewX=dt.xWorldToView(this.xWorld_);var viewY=-1;for(var index in this.yComponents_){var yComponent=this.yComponents_[index];var track=containerToTrack.getTrackByStableId(yComponent.stableId);if(track!==undefined){var boundRect=track.getBoundingClientRect();viewY=yComponent.yPercentOffset*boundRect.height+boundRect.top;break;}}return{viewX:viewX,viewY:viewY};},toDict:function(){return{xWorld:this.xWorld_,yComponents:this.yComponents_};}};return{Location:Location};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/base.js":33}],139:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/base.js":34}],140:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/iteration_helpers.js");require("../base/unit.js");require("../value/numeric.js");'use strict';global.tr.exportTo('tr.model',function(){function MemoryAllocatorDump(containerMemoryDump,fullName,opt_guid){this.fullName=fullName;this.parent=undefined;this.children=[];this.numerics={};this.diagnostics={};this.containerMemoryDump=containerMemoryDump;this.owns=undefined;this.ownedBy=[];this.ownedBySiblingSizes=new Map();this.retains=[];this.retainedBy=[];this.weak=false;this.infos=[];this.guid=opt_guid;}MemoryAllocatorDump.SIZE_NUMERIC_NAME='size';MemoryAllocatorDump.EFFECTIVE_SIZE_NUMERIC_NAME='effective_size';MemoryAllocatorDump.RESIDENT_SIZE_NUMERIC_NAME='resident_size';MemoryAllocatorDump.DISPLAYED_SIZE_NUMERIC_NAME=MemoryAllocatorDump.EFFECTIVE_SIZE_NUMERIC_NAME;MemoryAllocatorDump.prototype={get name(){return this.fullName.substring(this.fullName.lastIndexOf('/')+1);},get quantifiedName(){return'\''+this.fullName+'\' in '+this.containerMemoryDump.containerName;},getDescendantDumpByFullName:function(fullName){return this.containerMemoryDump.getMemoryAllocatorDumpByFullName(this.fullName+'/'+fullName);},isDescendantOf:function(otherDump){var dump=this;while(dump!==undefined){if(dump===otherDump)return true;dump=dump.parent;}return false;},addNumeric:function(name,numeric){if(!(numeric instanceof tr.v.ScalarNumeric))throw new Error('Numeric value must be an instance of ScalarNumeric.');if(name in this.numerics)throw new Error('Duplicate numeric name: '+name+'.');this.numerics[name]=numeric;},addDiagnostic:function(name,text){if(typeof text!=='string')throw new Error('Diagnostic text must be a string.');if(name in this.diagnostics)throw new Error('Duplicate diagnostic name: '+name+'.');this.diagnostics[name]=text;},aggregateNumericsRecursively:function(opt_model){var numericNames=new Set();this.children.forEach(function(child){child.aggregateNumericsRecursively(opt_model);tr.b.iterItems(child.numerics,numericNames.add,numericNames);},this);numericNames.forEach(function(numericName){if(numericName===MemoryAllocatorDump.SIZE_NUMERIC_NAME||numericName===MemoryAllocatorDump.EFFECTIVE_SIZE_NUMERIC_NAME||this.numerics[numericName]!==undefined){return;}this.numerics[numericName]=MemoryAllocatorDump.aggregateNumerics(this.children.map(function(child){return child.numerics[numericName];}),opt_model);},this);}};MemoryAllocatorDump.aggregateNumerics=function(numerics,opt_model){var shouldLogWarning=!!opt_model;var aggregatedUnit=undefined;var aggregatedValue=0;numerics.forEach(function(numeric){if(numeric===undefined)return;var unit=numeric.unit;if(aggregatedUnit===undefined){aggregatedUnit=unit;}else if(aggregatedUnit!==unit){if(shouldLogWarning){opt_model.importWarning({type:'numeric_parse_error',message:'Multiple units provided for numeric: \''+aggregatedUnit.unitName+'\' and \''+unit.unitName+'\'.'});shouldLogWarning=false;}aggregatedUnit=tr.b.Unit.byName.unitlessNumber_smallerIsBetter;}aggregatedValue+=numeric.value;},this);if(aggregatedUnit===undefined)return undefined;return new tr.v.ScalarNumeric(aggregatedUnit,aggregatedValue);};function MemoryAllocatorDumpLink(source,target,opt_importance){this.source=source;this.target=target;this.importance=opt_importance;this.size=undefined;}var MemoryAllocatorDumpInfoType={PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN:0,PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER:1};return{MemoryAllocatorDump:MemoryAllocatorDump,MemoryAllocatorDumpLink:MemoryAllocatorDumpLink,MemoryAllocatorDumpInfoType:MemoryAllocatorDumpInfoType};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/iteration_helpers.js":46,"../base/unit.js":62,"../value/numeric.js":195}],140:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/iteration_helpers.js":47,"../base/unit.js":63,"../value/numeric.js":196}],141:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/base.js");require("../base/event.js");require("../base/interval_tree.js");require("../base/quad.js");require("../base/range.js");require("../base/task.js");require("../base/time_display_modes.js");require("../base/unit.js");require("../core/auditor.js");require("../core/filter.js");require("./alert.js");require("./clock_sync_manager.js");require("./constants.js");require("./device.js");require("./flow_event.js");require("./frame.js");require("./global_memory_dump.js");require("./instant_event.js");require("./kernel.js");require("./model_indices.js");require("./model_stats.js");require("./object_snapshot.js");require("./process.js");require("./process_memory_dump.js");require("./sample.js");require("./stack_frame.js");require("./user_model/user_expectation.js");require("./user_model/user_model.js");'use strict';global.tr.exportTo('tr',function(){var Process=tr.model.Process;var Device=tr.model.Device;var Kernel=tr.model.Kernel;var GlobalMemoryDump=tr.model.GlobalMemoryDump;var GlobalInstantEvent=tr.model.GlobalInstantEvent;var FlowEvent=tr.model.FlowEvent;var Alert=tr.model.Alert;var Sample=tr.model.Sample;function Model(){tr.model.EventContainer.call(this);tr.b.EventTarget.decorate(this);this.timestampShiftToZeroAmount_=0;this.faviconHue='blue';this.device=new Device(this);this.kernel=new Kernel(this);this.processes={};this.metadata=[];this.categories=[];this.instantEvents=[];this.flowEvents=[];this.clockSyncManager=new tr.model.ClockSyncManager();this.intrinsicTimeUnit_=undefined;this.stackFrames={};this.samples=[];this.alerts=[];this.userModel=new tr.model.um.UserModel(this);this.flowIntervalTree=new tr.b.IntervalTree(f=>f.start,f=>f.end);this.globalMemoryDumps=[];this.userFriendlyCategoryDrivers_=[];this.annotationsByGuid_={};this.modelIndices=undefined;this.stats=new tr.model.ModelStats();this.importWarnings_=[];this.reportedImportWarnings_={};this.isTimeHighResolution_=true;this.patchupsToApply_=[];this.doesHelperGUIDSupportThisModel_={};this.helpersByConstructorGUID_={};this.eventsByStableId_=undefined;}Model.prototype={__proto__:tr.model.EventContainer.prototype,getEventByStableId:function(stableId){if(this.eventsByStableId_===undefined){this.eventsByStableId_={};for(var event of this.getDescendantEvents()){this.eventsByStableId_[event.stableId]=event;}}return this.eventsByStableId_[stableId];},getOrCreateHelper:function(constructor){if(!constructor.guid)throw new Error('Helper constructors must have GUIDs');if(this.helpersByConstructorGUID_[constructor.guid]===undefined){if(this.doesHelperGUIDSupportThisModel_[constructor.guid]===undefined){this.doesHelperGUIDSupportThisModel_[constructor.guid]=constructor.supportsModel(this);}if(!this.doesHelperGUIDSupportThisModel_[constructor.guid])return undefined;this.helpersByConstructorGUID_[constructor.guid]=new constructor(this);}return this.helpersByConstructorGUID_[constructor.guid];},childEvents:function*(){yield*this.globalMemoryDumps;yield*this.instantEvents;yield*this.flowEvents;yield*this.alerts;yield*this.samples;},childEventContainers:function*(){yield this.userModel;yield this.device;yield this.kernel;yield*tr.b.dictionaryValues(this.processes);},iterateAllPersistableObjects:function(callback){this.kernel.iterateAllPersistableObjects(callback);for(var pid in this.processes)this.processes[pid].iterateAllPersistableObjects(callback);},updateBounds:function(){this.bounds.reset();var bounds=this.bounds;for(var ec of this.childEventContainers()){ec.updateBounds();bounds.addRange(ec.bounds);}for(var event of this.childEvents())event.addBoundsToRange(bounds);},shiftWorldToZero:function(){var shiftAmount=-this.bounds.min;this.timestampShiftToZeroAmount_=shiftAmount;for(var ec of this.childEventContainers())ec.shiftTimestampsForward(shiftAmount);for(var event of this.childEvents())event.start+=shiftAmount;this.updateBounds();},convertTimestampToModelTime:function(sourceClockDomainName,ts){if(sourceClockDomainName!=='traceEventClock')throw new Error('Only traceEventClock is supported.');return tr.b.Unit.timestampFromUs(ts)+this.timestampShiftToZeroAmount_;},get numProcesses(){var n=0;for(var p in this.processes)n++;return n;},getProcess:function(pid){return this.processes[pid];},getOrCreateProcess:function(pid){if(!this.processes[pid])this.processes[pid]=new Process(this,pid);return this.processes[pid];},addStackFrame:function(stackFrame){if(this.stackFrames[stackFrame.id])throw new Error('Stack frame already exists');this.stackFrames[stackFrame.id]=stackFrame;return stackFrame;},updateCategories_:function(){var categoriesDict={};this.userModel.addCategoriesToDict(categoriesDict);this.device.addCategoriesToDict(categoriesDict);this.kernel.addCategoriesToDict(categoriesDict);for(var pid in this.processes)this.processes[pid].addCategoriesToDict(categoriesDict);this.categories=[];for(var category in categoriesDict)if(category!='')this.categories.push(category);},getAllThreads:function(){var threads=[];for(var tid in this.kernel.threads){threads.push(process.threads[tid]);}for(var pid in this.processes){var process=this.processes[pid];for(var tid in process.threads){threads.push(process.threads[tid]);}}return threads;},getAllProcesses:function(opt_predicate){var processes=[];for(var pid in this.processes){var process=this.processes[pid];if(opt_predicate===undefined||opt_predicate(process))processes.push(process);}return processes;},getAllCounters:function(){var counters=[];counters.push.apply(counters,tr.b.dictionaryValues(this.device.counters));counters.push.apply(counters,tr.b.dictionaryValues(this.kernel.counters));for(var pid in this.processes){var process=this.processes[pid];for(var tid in process.counters){counters.push(process.counters[tid]);}}return counters;},getAnnotationByGUID:function(guid){return this.annotationsByGuid_[guid];},addAnnotation:function(annotation){if(!annotation.guid)throw new Error('Annotation with undefined guid given');this.annotationsByGuid_[annotation.guid]=annotation;tr.b.dispatchSimpleEvent(this,'annotationChange');},removeAnnotation:function(annotation){this.annotationsByGuid_[annotation.guid].onRemove();delete this.annotationsByGuid_[annotation.guid];tr.b.dispatchSimpleEvent(this,'annotationChange');},getAllAnnotations:function(){return tr.b.dictionaryValues(this.annotationsByGuid_);},addUserFriendlyCategoryDriver:function(ufcd){this.userFriendlyCategoryDrivers_.push(ufcd);},getUserFriendlyCategoryFromEvent:function(event){for(var i=0;i<this.userFriendlyCategoryDrivers_.length;i++){var ufc=this.userFriendlyCategoryDrivers_[i].fromEvent(event);if(ufc!==undefined)return ufc;}return undefined;},findAllThreadsNamed:function(name){var namedThreads=[];namedThreads.push.apply(namedThreads,this.kernel.findAllThreadsNamed(name));for(var pid in this.processes){namedThreads.push.apply(namedThreads,this.processes[pid].findAllThreadsNamed(name));}return namedThreads;},get importOptions(){return this.importOptions_;},set importOptions(options){this.importOptions_=options;},get intrinsicTimeUnit(){if(this.intrinsicTimeUnit_===undefined)return tr.b.TimeDisplayModes.ms;return this.intrinsicTimeUnit_;},set intrinsicTimeUnit(value){if(this.intrinsicTimeUnit_===value)return;if(this.intrinsicTimeUnit_!==undefined)throw new Error('Intrinsic time unit already set');this.intrinsicTimeUnit_=value;},get isTimeHighResolution(){return this.isTimeHighResolution_;},set isTimeHighResolution(value){this.isTimeHighResolution_=value;},get canonicalUrl(){return this.canonicalUrl_;},set canonicalUrl(value){if(this.canonicalUrl_===value)return;if(this.canonicalUrl_!==undefined)throw new Error('canonicalUrl already set');this.canonicalUrl_=value;},importWarning:function(data){data.showToUser=!!data.showToUser;this.importWarnings_.push(data);if(this.reportedImportWarnings_[data.type]===true)return;if(this.importOptions_.showImportWarnings)console.warn(data.message);this.reportedImportWarnings_[data.type]=true;},get hasImportWarnings(){return this.importWarnings_.length>0;},get importWarnings(){return this.importWarnings_;},get importWarningsThatShouldBeShownToUser(){return this.importWarnings_.filter(function(warning){return warning.showToUser;});},autoCloseOpenSlices:function(){this.samples.sort(function(x,y){return x.start-y.start;});this.updateBounds();this.kernel.autoCloseOpenSlices();for(var pid in this.processes)this.processes[pid].autoCloseOpenSlices();},createSubSlices:function(){this.kernel.createSubSlices();for(var pid in this.processes)this.processes[pid].createSubSlices();},preInitializeObjects:function(){for(var pid in this.processes)this.processes[pid].preInitializeObjects();},initializeObjects:function(){for(var pid in this.processes)this.processes[pid].initializeObjects();},pruneEmptyContainers:function(){this.kernel.pruneEmptyContainers();for(var pid in this.processes)this.processes[pid].pruneEmptyContainers();},mergeKernelWithUserland:function(){for(var pid in this.processes)this.processes[pid].mergeKernelWithUserland();},computeWorldBounds:function(shiftWorldToZero){this.updateBounds();this.updateCategories_();if(shiftWorldToZero)this.shiftWorldToZero();},buildFlowEventIntervalTree:function(){for(var i=0;i<this.flowEvents.length;++i){var flowEvent=this.flowEvents[i];this.flowIntervalTree.insert(flowEvent);}this.flowIntervalTree.updateHighValues();},cleanupUndeletedObjects:function(){for(var pid in this.processes)this.processes[pid].autoDeleteObjects(this.bounds.max);},sortMemoryDumps:function(){this.globalMemoryDumps.sort(function(x,y){return x.start-y.start;});for(var pid in this.processes)this.processes[pid].sortMemoryDumps();},finalizeMemoryGraphs:function(){this.globalMemoryDumps.forEach(function(dump){dump.finalizeGraph();});},buildEventIndices:function(){this.modelIndices=new tr.model.ModelIndices(this);},sortAlerts:function(){this.alerts.sort(function(x,y){return x.start-y.start;});},applyObjectRefPatchups:function(){var unresolved=[];this.patchupsToApply_.forEach(function(patchup){if(patchup.pidRef in this.processes){var snapshot=this.processes[patchup.pidRef].objects.getSnapshotAt(patchup.scopedId,patchup.ts);if(snapshot){patchup.object[patchup.field]=snapshot;snapshot.referencedAt(patchup.item,patchup.object,patchup.field);return;}}unresolved.push(patchup);},this);this.patchupsToApply_=unresolved;},replacePIDRefsInPatchups:function(oldPidRef,newPidRef){this.patchupsToApply_.forEach(function(patchup){if(patchup.pidRef===oldPidRef){patchup.pidRef=newPidRef;}});},joinRefs:function(){this.joinObjectRefs_();this.applyObjectRefPatchups();},joinObjectRefs_:function(){tr.b.iterItems(this.processes,function(pid,process){this.joinObjectRefsForProcess_(pid,process);},this);},joinObjectRefsForProcess_:function(pid,process){tr.b.iterItems(process.threads,function(tid,thread){thread.asyncSliceGroup.slices.forEach(function(item){this.searchItemForIDRefs_(pid,'start',item);},this);thread.sliceGroup.slices.forEach(function(item){this.searchItemForIDRefs_(pid,'start',item);},this);},this);process.objects.iterObjectInstances(function(instance){instance.snapshots.forEach(function(item){this.searchItemForIDRefs_(pid,'ts',item);},this);},this);},searchItemForIDRefs_:function(pid,itemTimestampField,item){if(!item.args&&!item.contexts)return;var patchupsToApply=this.patchupsToApply_;function handleField(object,fieldName,fieldValue){if(!fieldValue||!fieldValue.id_ref&&!fieldValue.idRef)return;var scope=fieldValue.scope||tr.model.OBJECT_DEFAULT_SCOPE;var idRef=fieldValue.id_ref||fieldValue.idRef;var scopedId=new tr.model.ScopedId(scope,idRef);var pidRef=fieldValue.pid_ref||fieldValue.pidRef||pid;var ts=item[itemTimestampField];patchupsToApply.push({item:item,object:object,field:fieldName,pidRef:pidRef,scopedId:scopedId,ts:ts});}function iterObjectFieldsRecursively(object){if(!(object instanceof Object))return;if(object instanceof tr.model.ObjectSnapshot||object instanceof Float32Array||object instanceof tr.b.Quad)return;if(object instanceof Array){for(var i=0;i<object.length;i++){handleField(object,i,object[i]);iterObjectFieldsRecursively(object[i]);}return;}for(var key in object){var value=object[key];handleField(object,key,value);iterObjectFieldsRecursively(value);}}iterObjectFieldsRecursively(item.args);iterObjectFieldsRecursively(item.contexts);}};return{Model:Model};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/base.js":33,"../base/event.js":38,"../base/interval_tree.js":45,"../base/quad.js":50,"../base/range.js":52,"../base/task.js":59,"../base/time_display_modes.js":60,"../base/unit.js":62,"../core/auditor.js":65,"../core/filter.js":66,"./alert.js":106,"./clock_sync_manager.js":110,"./constants.js":113,"./device.js":120,"./flow_event.js":126,"./frame.js":127,"./global_memory_dump.js":128,"./instant_event.js":135,"./kernel.js":137,"./model_indices.js":141,"./model_stats.js":142,"./object_snapshot.js":145,"./process.js":148,"./process_memory_dump.js":150,"./sample.js":152,"./stack_frame.js":160,"./user_model/user_expectation.js":171,"./user_model/user_model.js":172}],141:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/base.js":34,"../base/event.js":39,"../base/interval_tree.js":46,"../base/quad.js":51,"../base/range.js":53,"../base/task.js":60,"../base/time_display_modes.js":61,"../base/unit.js":63,"../core/auditor.js":66,"../core/filter.js":67,"./alert.js":107,"./clock_sync_manager.js":111,"./constants.js":114,"./device.js":121,"./flow_event.js":127,"./frame.js":128,"./global_memory_dump.js":129,"./instant_event.js":136,"./kernel.js":138,"./model_indices.js":142,"./model_stats.js":143,"./object_snapshot.js":146,"./process.js":149,"./process_memory_dump.js":151,"./sample.js":153,"./stack_frame.js":161,"./user_model/user_expectation.js":172,"./user_model/user_model.js":173}],142:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/base.js");'use strict';global.tr.exportTo('tr.model',function(){function ModelIndices(model){this.flowEventsById_={};model.flowEvents.forEach(function(fe){if(fe.id!==undefined){if(!this.flowEventsById_.hasOwnProperty(fe.id)){this.flowEventsById_[fe.id]=new Array();}this.flowEventsById_[fe.id].push(fe);}},this);}ModelIndices.prototype={addEventWithId:function(id,event){if(!this.flowEventsById_.hasOwnProperty(id)){this.flowEventsById_[id]=new Array();}this.flowEventsById_[id].push(event);},getFlowEventsWithId:function(id){if(!this.flowEventsById_.hasOwnProperty(id))return[];return this.flowEventsById_[id];}};return{ModelIndices:ModelIndices};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/base.js":33}],142:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/base.js":34}],143:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/unit.js");'use strict';global.tr.exportTo('tr.model',function(){function ModelStats(){this.traceEventCountsByKey_=new Map();this.allTraceEventStats_=[];this.traceEventStatsInTimeIntervals_=new Map();this.allTraceEventStatsInTimeIntervals_=[];this.hasEventSizesinBytes_=false;}ModelStats.prototype={TIME_INTERVAL_SIZE_IN_MS:100,willProcessBasicTraceEvent:function(phase,category,title,ts,opt_eventSizeinBytes){var key=phase+'/'+category+'/'+title;var eventStats=this.traceEventCountsByKey_.get(key);if(eventStats===undefined){eventStats={phase:phase,category:category,title:title,numEvents:0,totalEventSizeinBytes:0};this.traceEventCountsByKey_.set(key,eventStats);this.allTraceEventStats_.push(eventStats);}eventStats.numEvents++;var timeIntervalKey=Math.floor(tr.b.Unit.timestampFromUs(ts)/this.TIME_INTERVAL_SIZE_IN_MS);var eventStatsByTimeInverval=this.traceEventStatsInTimeIntervals_.get(timeIntervalKey);if(eventStatsByTimeInverval===undefined){eventStatsByTimeInverval={timeInterval:timeIntervalKey,numEvents:0,totalEventSizeinBytes:0};this.traceEventStatsInTimeIntervals_.set(timeIntervalKey,eventStatsByTimeInverval);this.allTraceEventStatsInTimeIntervals_.push(eventStatsByTimeInverval);}eventStatsByTimeInverval.numEvents++;if(opt_eventSizeinBytes!==undefined){this.hasEventSizesinBytes_=true;eventStats.totalEventSizeinBytes+=opt_eventSizeinBytes;eventStatsByTimeInverval.totalEventSizeinBytes+=opt_eventSizeinBytes;}},get allTraceEventStats(){return this.allTraceEventStats_;},get allTraceEventStatsInTimeIntervals(){return this.allTraceEventStatsInTimeIntervals_;},get hasEventSizesinBytes(){return this.hasEventSizesinBytes_;}};return{ModelStats:ModelStats};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/unit.js":62}],143:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/unit.js":63}],144:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/range.js");require("../base/sorted_array_utils.js");require("../base/utils.js");require("./event_container.js");require("./object_instance.js");require("./time_to_object_instance_map.js");'use strict';global.tr.exportTo('tr.model',function(){var ObjectInstance=tr.model.ObjectInstance;var ObjectSnapshot=tr.model.ObjectSnapshot;function ObjectCollection(parent){tr.model.EventContainer.call(this);this.parent=parent;this.instanceMapsByScopedId_={};this.instancesByTypeName_={};this.createObjectInstance_=this.createObjectInstance_.bind(this);}ObjectCollection.prototype={__proto__:tr.model.EventContainer.prototype,childEvents:function*(){for(var instance of this.getAllObjectInstances()){yield instance;yield*instance.snapshots;}},createObjectInstance_:function(parent,scopedId,category,name,creationTs,opt_baseTypeName){var constructor=tr.model.ObjectInstance.subTypes.getConstructor(category,name);var instance=new constructor(parent,scopedId,category,name,creationTs,opt_baseTypeName);var typeName=instance.typeName;var instancesOfTypeName=this.instancesByTypeName_[typeName];if(!instancesOfTypeName){instancesOfTypeName=[];this.instancesByTypeName_[typeName]=instancesOfTypeName;}instancesOfTypeName.push(instance);return instance;},getOrCreateInstanceMap_:function(scopedId){var dict;if(scopedId.scope in this.instanceMapsByScopedId_){dict=this.instanceMapsByScopedId_[scopedId.scope];}else{dict={};this.instanceMapsByScopedId_[scopedId.scope]=dict;}var instanceMap=dict[scopedId.id];if(instanceMap)return instanceMap;instanceMap=new tr.model.TimeToObjectInstanceMap(this.createObjectInstance_,this.parent,scopedId);dict[scopedId.id]=instanceMap;return instanceMap;},idWasCreated:function(scopedId,category,name,ts){var instanceMap=this.getOrCreateInstanceMap_(scopedId);return instanceMap.idWasCreated(category,name,ts);},addSnapshot:function(scopedId,category,name,ts,args,opt_baseTypeName){var instanceMap=this.getOrCreateInstanceMap_(scopedId);var snapshot=instanceMap.addSnapshot(category,name,ts,args,opt_baseTypeName);if(snapshot.objectInstance.category!=category){var msg='Added snapshot name='+name+' with cat='+category+' impossible. It instance was created/snapshotted with cat='+snapshot.objectInstance.category+' name='+snapshot.objectInstance.name;throw new Error(msg);}if(opt_baseTypeName&&snapshot.objectInstance.baseTypeName!=opt_baseTypeName){throw new Error('Could not add snapshot with baseTypeName='+opt_baseTypeName+'. It '+'was previously created with name='+snapshot.objectInstance.baseTypeName);}if(snapshot.objectInstance.name!=name){throw new Error('Could not add snapshot with name='+name+'. It '+'was previously created with name='+snapshot.objectInstance.name);}return snapshot;},idWasDeleted:function(scopedId,category,name,ts){var instanceMap=this.getOrCreateInstanceMap_(scopedId);var deletedInstance=instanceMap.idWasDeleted(category,name,ts);if(!deletedInstance)return;if(deletedInstance.category!=category){var msg='Deleting object '+deletedInstance.name+' with a different category '+'than when it was created. It previous had cat='+deletedInstance.category+' but the delete command '+'had cat='+category;throw new Error(msg);}if(deletedInstance.baseTypeName!=name){throw new Error('Deletion requested for name='+name+' could not proceed: '+'An existing object with baseTypeName='+deletedInstance.baseTypeName+' existed.');}},autoDeleteObjects:function(maxTimestamp){tr.b.iterItems(this.instanceMapsByScopedId_,function(scope,imapById){tr.b.iterItems(imapById,function(id,i2imap){var lastInstance=i2imap.lastInstance;if(lastInstance.deletionTs!=Number.MAX_VALUE)return;i2imap.idWasDeleted(lastInstance.category,lastInstance.name,maxTimestamp);lastInstance.deletionTsWasExplicit=false;});});},getObjectInstanceAt:function(scopedId,ts){var instanceMap;if(scopedId.scope in this.instanceMapsByScopedId_)instanceMap=this.instanceMapsByScopedId_[scopedId.scope][scopedId.id];if(!instanceMap)return undefined;return instanceMap.getInstanceAt(ts);},getSnapshotAt:function(scopedId,ts){var instance=this.getObjectInstanceAt(scopedId,ts);if(!instance)return undefined;return instance.getSnapshotAt(ts);},iterObjectInstances:function(iter,opt_this){opt_this=opt_this||this;tr.b.iterItems(this.instanceMapsByScopedId_,function(scope,imapById){tr.b.iterItems(imapById,function(id,i2imap){i2imap.instances.forEach(iter,opt_this);});});},getAllObjectInstances:function(){var instances=[];this.iterObjectInstances(function(i){instances.push(i);});return instances;},getAllInstancesNamed:function(name){return this.instancesByTypeName_[name];},getAllInstancesByTypeName:function(){return this.instancesByTypeName_;},preInitializeAllObjects:function(){this.iterObjectInstances(function(instance){instance.preInitialize();});},initializeAllObjects:function(){this.iterObjectInstances(function(instance){instance.initialize();});},initializeInstances:function(){this.iterObjectInstances(function(instance){instance.initialize();});},updateBounds:function(){this.bounds.reset();this.iterObjectInstances(function(instance){instance.updateBounds();this.bounds.addRange(instance.bounds);},this);},shiftTimestampsForward:function(amount){this.iterObjectInstances(function(instance){instance.shiftTimestampsForward(amount);});},addCategoriesToDict:function(categoriesDict){this.iterObjectInstances(function(instance){categoriesDict[instance.category]=true;});}};return{ObjectCollection:ObjectCollection};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/range.js":52,"../base/sorted_array_utils.js":57,"../base/utils.js":64,"./event_container.js":122,"./object_instance.js":144,"./time_to_object_instance_map.js":164}],144:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/range.js":53,"../base/sorted_array_utils.js":58,"../base/utils.js":65,"./event_container.js":123,"./object_instance.js":145,"./time_to_object_instance_map.js":165}],145:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/range.js");require("../base/sorted_array_utils.js");require("./event.js");require("./object_snapshot.js");'use strict';global.tr.exportTo('tr.model',function(){var ObjectSnapshot=tr.model.ObjectSnapshot;function ObjectInstance(parent,scopedId,category,name,creationTs,opt_baseTypeName){tr.model.Event.call(this);this.parent=parent;this.scopedId=scopedId;this.category=category;this.baseTypeName=opt_baseTypeName?opt_baseTypeName:name;this.name=name;this.creationTs=creationTs;this.creationTsWasExplicit=false;this.deletionTs=Number.MAX_VALUE;this.deletionTsWasExplicit=false;this.colorId=0;this.bounds=new tr.b.Range();this.snapshots=[];this.hasImplicitSnapshots=false;}ObjectInstance.prototype={__proto__:tr.model.Event.prototype,get typeName(){return this.name;},addBoundsToRange:function(range){range.addRange(this.bounds);},addSnapshot:function(ts,args,opt_name,opt_baseTypeName){if(ts<this.creationTs)throw new Error('Snapshots must be >= instance.creationTs');if(ts>=this.deletionTs)throw new Error('Snapshots cannot be added after '+'an objects deletion timestamp.');var lastSnapshot;if(this.snapshots.length>0){lastSnapshot=this.snapshots[this.snapshots.length-1];if(lastSnapshot.ts==ts)throw new Error('Snapshots already exists at this time!');if(ts<lastSnapshot.ts){throw new Error('Snapshots must be added in increasing timestamp order');}}if(opt_name&&this.name!=opt_name){if(!opt_baseTypeName)throw new Error('Must provide base type name for name update');if(this.baseTypeName!=opt_baseTypeName)throw new Error('Cannot update type name: base types dont match');this.name=opt_name;}var snapshotConstructor=tr.model.ObjectSnapshot.subTypes.getConstructor(this.category,this.name);var snapshot=new snapshotConstructor(this,ts,args);this.snapshots.push(snapshot);return snapshot;},wasDeleted:function(ts){var lastSnapshot;if(this.snapshots.length>0){lastSnapshot=this.snapshots[this.snapshots.length-1];if(lastSnapshot.ts>ts)throw new Error('Instance cannot be deleted at ts='+ts+'. A snapshot exists that is older.');}this.deletionTs=ts;this.deletionTsWasExplicit=true;},preInitialize:function(){for(var i=0;i<this.snapshots.length;i++)this.snapshots[i].preInitialize();},initialize:function(){for(var i=0;i<this.snapshots.length;i++)this.snapshots[i].initialize();},isAliveAt:function(ts){if(ts<this.creationTs&&this.creationTsWasExplicit)return false;if(ts>this.deletionTs)return false;return true;},getSnapshotAt:function(ts){if(ts<this.creationTs){if(this.creationTsWasExplicit)throw new Error('ts must be within lifetime of this instance');return this.snapshots[0];}if(ts>this.deletionTs)throw new Error('ts must be within lifetime of this instance');var snapshots=this.snapshots;var i=tr.b.findIndexInSortedIntervals(snapshots,function(snapshot){return snapshot.ts;},function(snapshot,i){if(i==snapshots.length-1)return snapshots[i].objectInstance.deletionTs;return snapshots[i+1].ts-snapshots[i].ts;},ts);if(i<0){return this.snapshots[0];}if(i>=this.snapshots.length)return this.snapshots[this.snapshots.length-1];return this.snapshots[i];},updateBounds:function(){this.bounds.reset();this.bounds.addValue(this.creationTs);if(this.deletionTs!=Number.MAX_VALUE)this.bounds.addValue(this.deletionTs);else if(this.snapshots.length>0)this.bounds.addValue(this.snapshots[this.snapshots.length-1].ts);},shiftTimestampsForward:function(amount){this.creationTs+=amount;if(this.deletionTs!=Number.MAX_VALUE)this.deletionTs+=amount;this.snapshots.forEach(function(snapshot){snapshot.ts+=amount;});},get userFriendlyName(){return this.typeName+' object '+this.scopedId;}};tr.model.EventRegistry.register(ObjectInstance,{name:'objectInstance',pluralName:'objectInstances'});return{ObjectInstance:ObjectInstance};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/range.js":52,"../base/sorted_array_utils.js":57,"./event.js":121,"./object_snapshot.js":145}],145:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/range.js":53,"../base/sorted_array_utils.js":58,"./event.js":122,"./object_snapshot.js":146}],146:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/unit.js");require("./event.js");'use strict';global.tr.exportTo('tr.model',function(){function ObjectSnapshot(objectInstance,ts,args){tr.model.Event.call(this);this.objectInstance=objectInstance;this.ts=ts;this.args=args;}ObjectSnapshot.prototype={__proto__:tr.model.Event.prototype,preInitialize:function(){},initialize:function(){},referencedAt:function(item,object,field){},addBoundsToRange:function(range){range.addValue(this.ts);},get userFriendlyName(){return'Snapshot of '+this.objectInstance.typeName+' '+this.objectInstance.id+' @ '+tr.b.Unit.byName.timeStampInMs.format(this.ts);}};tr.model.EventRegistry.register(ObjectSnapshot,{name:'objectSnapshot',pluralName:'objectSnapshots'});return{ObjectSnapshot:ObjectSnapshot};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/unit.js":62,"./event.js":121}],146:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/unit.js":63,"./event.js":122}],147:[function(require,module,exports){
+(function (global){
 "use strict";require("./event.js");require("./event_registry.js");'use strict';global.tr.exportTo('tr.model',function(){var Event=tr.model.Event;var EventRegistry=tr.model.EventRegistry;function PowerSample(series,start,powerInW){Event.call(this);this.series_=series;this.start_=start;this.powerInW_=powerInW;}PowerSample.prototype={__proto__:Event.prototype,get series(){return this.series_;},get start(){return this.start_;},set start(value){this.start_=value;},get powerInW(){return this.powerInW_;},set powerInW(value){this.powerInW_=value;},addBoundsToRange:function(range){range.addValue(this.start);}};EventRegistry.register(PowerSample,{name:'powerSample',pluralName:'powerSamples'});return{PowerSample:PowerSample};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./event.js":121,"./event_registry.js":124}],147:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./event.js":122,"./event_registry.js":125}],148:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/range.js");require("../base/sorted_array_utils.js");require("../base/unit_scale.js");require("./event_container.js");require("./power_sample.js");'use strict';global.tr.exportTo('tr.model',function(){var PowerSample=tr.model.PowerSample;function PowerSeries(device){tr.model.EventContainer.call(this);this.device_=device;this.samples_=[];}PowerSeries.prototype={__proto__:tr.model.EventContainer.prototype,get device(){return this.device_;},get samples(){return this.samples_;},get stableId(){return this.device_.stableId+'.PowerSeries';},addPowerSample:function(ts,val){var sample=new PowerSample(this,ts,val);this.samples_.push(sample);return sample;},getEnergyConsumedInJ:function(start,end){var measurementRange=tr.b.Range.fromExplicitRange(start,end);var energyConsumedInJ=0;var startIndex=tr.b.findLowIndexInSortedArray(this.samples,x=>x.start,start)-1;var endIndex=tr.b.findLowIndexInSortedArray(this.samples,x=>x.start,end);if(startIndex<0)startIndex=0;for(var i=startIndex;i<endIndex;i++){var sample=this.samples[i];var nextSample=this.samples[i+1];var sampleRange=new tr.b.Range();sampleRange.addValue(sample.start);sampleRange.addValue(nextSample?nextSample.start:sample.start);var intersectionRangeInMs=measurementRange.findIntersection(sampleRange);var durationInS=tr.b.convertUnit(intersectionRangeInMs.duration,tr.b.UnitScale.Metric.MILLI,tr.b.UnitScale.Metric.NONE);energyConsumedInJ+=durationInS*sample.powerInW;}return energyConsumedInJ;},getSamplesWithinRange:function(start,end){var startIndex=tr.b.findLowIndexInSortedArray(this.samples,x=>x.start,start);var endIndex=tr.b.findLowIndexInSortedArray(this.samples,x=>x.start,end);return this.samples.slice(startIndex,endIndex);},shiftTimestampsForward:function(amount){for(var i=0;i<this.samples_.length;++i)this.samples_[i].start+=amount;},updateBounds:function(){this.bounds.reset();if(this.samples_.length===0)return;this.bounds.addValue(this.samples_[0].start);this.bounds.addValue(this.samples_[this.samples_.length-1].start);},childEvents:function*(){yield*this.samples_;}};return{PowerSeries:PowerSeries};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/range.js":52,"../base/sorted_array_utils.js":57,"../base/unit_scale.js":63,"./event_container.js":122,"./power_sample.js":146}],148:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/range.js":53,"../base/sorted_array_utils.js":58,"../base/unit_scale.js":64,"./event_container.js":123,"./power_sample.js":147}],149:[function(require,module,exports){
+(function (global){
 "use strict";require("./process_base.js");require("./process_memory_dump.js");'use strict';global.tr.exportTo('tr.model',function(){var ProcessBase=tr.model.ProcessBase;var ProcessInstantEvent=tr.model.ProcessInstantEvent;var Frame=tr.model.Frame;var ProcessMemoryDump=tr.model.ProcessMemoryDump;function Process(model,pid){if(model===undefined)throw new Error('model must be provided');if(pid===undefined)throw new Error('pid must be provided');tr.model.ProcessBase.call(this,model);this.pid=pid;this.name=undefined;this.labels=[];this.instantEvents=[];this.memoryDumps=[];this.frames=[];this.activities=[];};Process.compare=function(x,y){var tmp=tr.model.ProcessBase.compare(x,y);if(tmp)return tmp;tmp=tr.b.comparePossiblyUndefinedValues(x.name,y.name,function(x,y){return x.localeCompare(y);});if(tmp)return tmp;tmp=tr.b.compareArrays(x.labels,y.labels,function(x,y){return x.localeCompare(y);});if(tmp)return tmp;return x.pid-y.pid;};Process.prototype={__proto__:tr.model.ProcessBase.prototype,get stableId(){return this.pid;},compareTo:function(that){return Process.compare(this,that);},childEvents:function*(){yield*ProcessBase.prototype.childEvents.call(this);yield*this.instantEvents;yield*this.frames;yield*this.memoryDumps;},addLabelIfNeeded:function(labelName){for(var i=0;i<this.labels.length;i++){if(this.labels[i]===labelName)return;}this.labels.push(labelName);},get userFriendlyName(){var res;if(this.name)res=this.name+' (pid '+this.pid+')';else res='Process '+this.pid;if(this.labels.length)res+=': '+this.labels.join(', ');return res;},get userFriendlyDetails(){if(this.name)return this.name+' (pid '+this.pid+')';return'pid: '+this.pid;},getSettingsKey:function(){if(!this.name)return undefined;if(!this.labels.length)return'processes.'+this.name;return'processes.'+this.name+'.'+this.labels.join('.');},shiftTimestampsForward:function(amount){for(var i=0;i<this.instantEvents.length;i++)this.instantEvents[i].start+=amount;for(var i=0;i<this.frames.length;i++)this.frames[i].shiftTimestampsForward(amount);for(var i=0;i<this.memoryDumps.length;i++)this.memoryDumps[i].shiftTimestampsForward(amount);for(var i=0;i<this.activities.length;i++)this.activities[i].shiftTimestampsForward(amount);tr.model.ProcessBase.prototype.shiftTimestampsForward.apply(this,arguments);},updateBounds:function(){tr.model.ProcessBase.prototype.updateBounds.apply(this);for(var i=0;i<this.frames.length;i++)this.frames[i].addBoundsToRange(this.bounds);for(var i=0;i<this.memoryDumps.length;i++)this.memoryDumps[i].addBoundsToRange(this.bounds);for(var i=0;i<this.activities.length;i++)this.activities[i].addBoundsToRange(this.bounds);},sortMemoryDumps:function(){this.memoryDumps.sort(function(x,y){return x.start-y.start;});tr.model.ProcessMemoryDump.hookUpMostRecentVmRegionsLinks(this.memoryDumps);}};return{Process:Process};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./process_base.js":149,"./process_memory_dump.js":150}],149:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./process_base.js":150,"./process_memory_dump.js":151}],150:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/guid.js");require("../base/range.js");require("./counter.js");require("./event_container.js");require("./object_collection.js");require("./thread.js");'use strict';global.tr.exportTo('tr.model',function(){var Thread=tr.model.Thread;var Counter=tr.model.Counter;function ProcessBase(model){if(!model)throw new Error('Must provide a model');tr.model.EventContainer.call(this);this.model=model;this.threads={};this.counters={};this.objects=new tr.model.ObjectCollection(this);this.sortIndex=0;};ProcessBase.compare=function(x,y){return x.sortIndex-y.sortIndex;};ProcessBase.prototype={__proto__:tr.model.EventContainer.prototype,get stableId(){throw new Error('Not implemented');},childEventContainers:function*(){yield*tr.b.dictionaryValues(this.threads);yield*tr.b.dictionaryValues(this.counters);yield this.objects;},iterateAllPersistableObjects:function(cb){cb(this);for(var tid in this.threads)this.threads[tid].iterateAllPersistableObjects(cb);},get numThreads(){var n=0;for(var p in this.threads){n++;}return n;},shiftTimestampsForward:function(amount){for(var child of this.childEventContainers())child.shiftTimestampsForward(amount);},autoCloseOpenSlices:function(){for(var tid in this.threads){var thread=this.threads[tid];thread.autoCloseOpenSlices();}},autoDeleteObjects:function(maxTimestamp){this.objects.autoDeleteObjects(maxTimestamp);},preInitializeObjects:function(){this.objects.preInitializeAllObjects();},initializeObjects:function(){this.objects.initializeAllObjects();},mergeKernelWithUserland:function(){for(var tid in this.threads){var thread=this.threads[tid];thread.mergeKernelWithUserland();}},updateBounds:function(){this.bounds.reset();for(var tid in this.threads){this.threads[tid].updateBounds();this.bounds.addRange(this.threads[tid].bounds);}for(var id in this.counters){this.counters[id].updateBounds();this.bounds.addRange(this.counters[id].bounds);}this.objects.updateBounds();this.bounds.addRange(this.objects.bounds);},addCategoriesToDict:function(categoriesDict){for(var tid in this.threads)this.threads[tid].addCategoriesToDict(categoriesDict);for(var id in this.counters)categoriesDict[this.counters[id].category]=true;this.objects.addCategoriesToDict(categoriesDict);},findAllThreadsMatching:function(predicate,opt_this){var threads=[];for(var tid in this.threads){var thread=this.threads[tid];if(predicate.call(opt_this,thread))threads.push(thread);}return threads;},findAllThreadsNamed:function(name){var threads=this.findAllThreadsMatching(function(thread){if(!thread.name)return false;return thread.name===name;});return threads;},findAtMostOneThreadNamed:function(name){var threads=this.findAllThreadsNamed(name);if(threads.length===0)return undefined;if(threads.length>1)throw new Error('Expected no more than one '+name);return threads[0];},pruneEmptyContainers:function(){var threadsToKeep={};for(var tid in this.threads){var thread=this.threads[tid];if(!thread.isEmpty)threadsToKeep[tid]=thread;}this.threads=threadsToKeep;},getThread:function(tid){return this.threads[tid];},getOrCreateThread:function(tid){if(!this.threads[tid])this.threads[tid]=new Thread(this,tid);return this.threads[tid];},getOrCreateCounter:function(cat,name){var id=cat+'.'+name;if(!this.counters[id])this.counters[id]=new Counter(this,id,cat,name);return this.counters[id];},getSettingsKey:function(){throw new Error('Not implemented');},createSubSlices:function(){for(var tid in this.threads)this.threads[tid].createSubSlices();}};return{ProcessBase:ProcessBase};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/guid.js":44,"../base/range.js":52,"./counter.js":115,"./event_container.js":122,"./object_collection.js":143,"./thread.js":161}],150:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/guid.js":45,"../base/range.js":53,"./counter.js":116,"./event_container.js":123,"./object_collection.js":144,"./thread.js":162}],151:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/unit.js");require("./container_memory_dump.js");require("./memory_allocator_dump.js");require("./vm_region.js");'use strict';global.tr.exportTo('tr.model',function(){var DISCOUNTED_ALLOCATOR_NAMES=['winheap','malloc'];var TRACING_OVERHEAD_PATH=['allocated_objects','tracing_overhead'];var SIZE_NUMERIC_NAME=tr.model.MemoryAllocatorDump.SIZE_NUMERIC_NAME;var RESIDENT_SIZE_NUMERIC_NAME=tr.model.MemoryAllocatorDump.RESIDENT_SIZE_NUMERIC_NAME;function getSizeNumericValue(dump,sizeNumericName){var sizeNumeric=dump.numerics[sizeNumericName];if(sizeNumeric===undefined)return 0;return sizeNumeric.value;}function ProcessMemoryDump(globalMemoryDump,process,start){tr.model.ContainerMemoryDump.call(this,start);this.process=process;this.globalMemoryDump=globalMemoryDump;this.totals=undefined;this.vmRegions=undefined;this.heapDumps=undefined;this.tracingOverheadOwnershipSetUp_=false;this.tracingOverheadDiscountedFromVmRegions_=false;}ProcessMemoryDump.prototype={__proto__:tr.model.ContainerMemoryDump.prototype,get userFriendlyName(){return'Process memory dump at '+tr.b.Unit.byName.timeStampInMs.format(this.start);},get containerName(){return this.process.userFriendlyName;},get processMemoryDumps(){var dumps={};dumps[this.process.pid]=this;return dumps;},get hasOwnVmRegions(){return this.vmRegions!==undefined;},setUpTracingOverheadOwnership:function(opt_model){if(this.tracingOverheadOwnershipSetUp_)return;this.tracingOverheadOwnershipSetUp_=true;var tracingDump=this.getMemoryAllocatorDumpByFullName('tracing');if(tracingDump===undefined||tracingDump.owns!==undefined){return;}if(tracingDump.owns!==undefined)return;var hasDiscountedFromAllocatorDumps=DISCOUNTED_ALLOCATOR_NAMES.some(function(allocatorName){var allocatorDump=this.getMemoryAllocatorDumpByFullName(allocatorName);if(allocatorDump===undefined)return false;var nextPathIndex=0;var currentDump=allocatorDump;var currentFullName=allocatorName;for(;nextPathIndex<TRACING_OVERHEAD_PATH.length;nextPathIndex++){var childFullName=currentFullName+'/'+TRACING_OVERHEAD_PATH[nextPathIndex];var childDump=this.getMemoryAllocatorDumpByFullName(childFullName);if(childDump===undefined)break;currentDump=childDump;currentFullName=childFullName;}for(;nextPathIndex<TRACING_OVERHEAD_PATH.length;nextPathIndex++){var childFullName=currentFullName+'/'+TRACING_OVERHEAD_PATH[nextPathIndex];var childDump=new tr.model.MemoryAllocatorDump(currentDump.containerMemoryDump,childFullName);childDump.parent=currentDump;currentDump.children.push(childDump);currentFullName=childFullName;currentDump=childDump;}var ownershipLink=new tr.model.MemoryAllocatorDumpLink(tracingDump,currentDump);tracingDump.owns=ownershipLink;currentDump.ownedBy.push(ownershipLink);return true;},this);if(hasDiscountedFromAllocatorDumps)this.forceRebuildingMemoryAllocatorDumpByFullNameIndex();},discountTracingOverheadFromVmRegions:function(opt_model){if(this.tracingOverheadDiscountedFromVmRegions_)return;this.tracingOverheadDiscountedFromVmRegions_=true;var tracingDump=this.getMemoryAllocatorDumpByFullName('tracing');if(tracingDump===undefined)return;var discountedSize=getSizeNumericValue(tracingDump,SIZE_NUMERIC_NAME);var discountedResidentSize=getSizeNumericValue(tracingDump,RESIDENT_SIZE_NUMERIC_NAME);if(discountedSize<=0&&discountedResidentSize<=0)return;if(this.totals!==undefined){if(this.totals.residentBytes!==undefined)this.totals.residentBytes-=discountedResidentSize;if(this.totals.peakResidentBytes!==undefined)this.totals.peakResidentBytes-=discountedResidentSize;}if(this.vmRegions!==undefined){var hasSizeInBytes=this.vmRegions.sizeInBytes!==undefined;var hasPrivateDirtyResident=this.vmRegions.byteStats.privateDirtyResident!==undefined;var hasProportionalResident=this.vmRegions.byteStats.proportionalResident!==undefined;if(hasSizeInBytes&&discountedSize>0||(hasPrivateDirtyResident||hasProportionalResident)&&discountedResidentSize>0){var byteStats={};if(hasPrivateDirtyResident)byteStats.privateDirtyResident=-discountedResidentSize;if(hasProportionalResident)byteStats.proportionalResident=-discountedResidentSize;this.vmRegions.addRegion(tr.model.VMRegion.fromDict({mappedFile:'[discounted tracing overhead]',sizeInBytes:hasSizeInBytes?-discountedSize:undefined,byteStats:byteStats}));}}}};ProcessMemoryDump.hookUpMostRecentVmRegionsLinks=function(processDumps){var mostRecentVmRegions=undefined;processDumps.forEach(function(processDump){if(processDump.vmRegions!==undefined)mostRecentVmRegions=processDump.vmRegions;processDump.mostRecentVmRegions=mostRecentVmRegions;});};tr.model.EventRegistry.register(ProcessMemoryDump,{name:'processMemoryDump',pluralName:'processMemoryDumps'});return{ProcessMemoryDump:ProcessMemoryDump};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/unit.js":62,"./container_memory_dump.js":114,"./memory_allocator_dump.js":139,"./vm_region.js":173}],151:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/unit.js":63,"./container_memory_dump.js":115,"./memory_allocator_dump.js":140,"./vm_region.js":174}],152:[function(require,module,exports){
+(function (global){
 "use strict";require("./location.js");require("./annotation.js");require("../ui/annotations/rect_annotation_view.js");'use strict';global.tr.exportTo('tr.model',function(){function RectAnnotation(start,end){tr.model.Annotation.apply(this,arguments);this.startLocation_=start;this.endLocation_=end;this.fillStyle='rgba(255, 180, 0, 0.3)';}RectAnnotation.fromDict=function(dict){var args=dict.args;var startLoc=new tr.model.Location(args.start.xWorld,args.start.yComponents);var endLoc=new tr.model.Location(args.end.xWorld,args.end.yComponents);return new tr.model.RectAnnotation(startLoc,endLoc);};RectAnnotation.prototype={__proto__:tr.model.Annotation.prototype,get startLocation(){return this.startLocation_;},get endLocation(){return this.endLocation_;},toDict:function(){return{typeName:'rect',args:{start:this.startLocation.toDict(),end:this.endLocation.toDict()}};},createView_:function(viewport){return new tr.ui.annotations.RectAnnotationView(viewport,this);}};tr.model.Annotation.register(RectAnnotation,{typeName:'rect'});return{RectAnnotation:RectAnnotation};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../ui/annotations/rect_annotation_view.js":177,"./annotation.js":107,"./location.js":138}],152:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../ui/annotations/rect_annotation_view.js":178,"./annotation.js":108,"./location.js":139}],153:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/unit.js");require("./timed_event.js");'use strict';global.tr.exportTo('tr.model',function(){function Sample(cpu,thread,title,start,leafStackFrame,opt_weight,opt_args){tr.model.TimedEvent.call(this,start);this.title=title;this.cpu=cpu;this.thread=thread;this.leafStackFrame=leafStackFrame;this.weight=opt_weight;this.args=opt_args||{};}Sample.prototype={__proto__:tr.model.TimedEvent.prototype,get colorId(){return this.leafStackFrame.colorId;},get stackTrace(){return this.leafStackFrame.stackTrace;},getUserFriendlyStackTrace:function(){return this.leafStackFrame.getUserFriendlyStackTrace();},get userFriendlyName(){return'Sample at '+tr.b.Unit.byName.timeStampInMs.format(this.start);}};tr.model.EventRegistry.register(Sample,{name:'sample',pluralName:'samples'});return{Sample:Sample};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/unit.js":62,"./timed_event.js":165}],153:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/unit.js":63,"./timed_event.js":166}],154:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/base.js");require("./constants.js");'use strict';global.tr.exportTo('tr.model',function(){function ScopedId(scope,id){if(scope===undefined){throw new Error('Scope should be defined. Use \''+tr.model.OBJECT_DEFAULT_SCOPE+'\' as the default scope.');}this.scope=scope;this.id=id;}ScopedId.prototype={toString:function(){return'{scope: '+this.scope+', id: '+this.id+'}';}};return{ScopedId:ScopedId};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/base.js":33,"./constants.js":113}],154:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/base.js":34,"./constants.js":114}],155:[function(require,module,exports){
+(function (global){
 "use strict";require("./selection_state.js");'use strict';global.tr.exportTo('tr.model',function(){var SelectionState=tr.model.SelectionState;function SelectableItem(modelItem){this.modelItem_=modelItem;}SelectableItem.prototype={get modelItem(){return this.modelItem_;},get selected(){return this.selectionState===SelectionState.SELECTED;},addToSelection:function(selection){var modelItem=this.modelItem_;if(!modelItem)return;selection.push(modelItem);},addToTrackMap:function(eventToTrackMap,track){var modelItem=this.modelItem_;if(!modelItem)return;eventToTrackMap.addEvent(modelItem,track);}};return{SelectableItem:SelectableItem};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./selection_state.js":155}],155:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./selection_state.js":156}],156:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/base.js");require("../base/color_scheme.js");'use strict';global.tr.exportTo('tr.model',function(){var ColorScheme=tr.b.ColorScheme;var SelectionState={NONE:0,SELECTED:ColorScheme.properties.brightenedOffsets[0],HIGHLIGHTED:ColorScheme.properties.brightenedOffsets[1],DIMMED:ColorScheme.properties.dimmedOffsets[0],BRIGHTENED0:ColorScheme.properties.brightenedOffsets[0],BRIGHTENED1:ColorScheme.properties.brightenedOffsets[1],BRIGHTENED2:ColorScheme.properties.brightenedOffsets[2],DIMMED0:ColorScheme.properties.dimmedOffsets[0],DIMMED1:ColorScheme.properties.dimmedOffsets[1],DIMMED2:ColorScheme.properties.dimmedOffsets[2]};var brighteningLevels=[SelectionState.NONE,SelectionState.BRIGHTENED0,SelectionState.BRIGHTENED1,SelectionState.BRIGHTENED2];SelectionState.getFromBrighteningLevel=function(level){return brighteningLevels[level];};var dimmingLevels=[SelectionState.DIMMED0,SelectionState.DIMMED1,SelectionState.DIMMED2];SelectionState.getFromDimmingLevel=function(level){return dimmingLevels[level];};return{SelectionState:SelectionState};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/base.js":33,"../base/color_scheme.js":37}],156:[function(require,module,exports){
-(function(global){
-"use strict";require("../base/unit.js");require("./timed_event.js");'use strict';global.tr.exportTo('tr.model',function(){function Slice(category,title,colorId,start,args,opt_duration,opt_cpuStart,opt_cpuDuration,opt_argsStripped,opt_bindId){if(!(this instanceof Slice)){throw new Error("Can't instantiate pure virtual class Slice");}tr.model.TimedEvent.call(this,start);this.category=category||'';this.title=title;this.colorId=colorId;this.args=args;this.startStackFrame=undefined;this.endStackFrame=undefined;this.didNotFinish=false;this.inFlowEvents=[];this.outFlowEvents=[];this.subSlices=[];this.selfTime=undefined;this.cpuSelfTime=undefined;this.important=false;this.parentContainer=undefined;this.argsStripped=false;this.bind_id_=opt_bindId;this.parentSlice=undefined;this.isTopLevel=false;if(opt_duration!==undefined)this.duration=opt_duration;if(opt_cpuStart!==undefined)this.cpuStart=opt_cpuStart;if(opt_cpuDuration!==undefined)this.cpuDuration=opt_cpuDuration;if(opt_argsStripped!==undefined)this.argsStripped=true;}Slice.prototype={__proto__:tr.model.TimedEvent.prototype,get analysisTypeName(){return this.title;},get userFriendlyName(){return'Slice '+this.title+' at '+tr.b.Unit.byName.timeStampInMs.format(this.start);},get stableId(){var parentSliceGroup=this.parentContainer.sliceGroup;return parentSliceGroup.stableId+'.'+parentSliceGroup.slices.indexOf(this);},findDescendentSlice:function(targetTitle){if(!this.subSlices)return undefined;for(var i=0;i<this.subSlices.length;i++){if(this.subSlices[i].title==targetTitle)return this.subSlices[i];var slice=this.subSlices[i].findDescendentSlice(targetTitle);if(slice)return slice;}return undefined;},get mostTopLevelSlice(){var curSlice=this;while(curSlice.parentSlice)curSlice=curSlice.parentSlice;return curSlice;},getProcess:function(){var thread=this.parentContainer;if(thread&&thread.getProcess)return thread.getProcess();return undefined;},get model(){var process=this.getProcess();if(process!==undefined)return this.getProcess().model;return undefined;},findTopmostSlicesRelativeToThisSlice:function*(eventPredicate){if(eventPredicate(this)){yield this;return;}for(var s of this.subSlices)yield*s.findTopmostSlicesRelativeToThisSlice(eventPredicate);},iterateAllSubsequentSlices:function(callback,opt_this){var parentStack=[];var started=false;var topmostSlice=this.mostTopLevelSlice;parentStack.push(topmostSlice);while(parentStack.length!==0){var curSlice=parentStack.pop();if(started)callback.call(opt_this,curSlice);else started=curSlice.guid===this.guid;for(var i=curSlice.subSlices.length-1;i>=0;i--){parentStack.push(curSlice.subSlices[i]);}}},get subsequentSlices(){var res=[];this.iterateAllSubsequentSlices(function(subseqSlice){res.push(subseqSlice);});return res;},enumerateAllAncestors:function*(){var curSlice=this;while(curSlice.parentSlice){curSlice=curSlice.parentSlice;yield curSlice;}},get ancestorSlices(){var res=[];for(var slice of this.enumerateAllAncestors())res.push(slice);return res;},iterateEntireHierarchy:function(callback,opt_this){var mostTopLevelSlice=this.mostTopLevelSlice;callback.call(opt_this,mostTopLevelSlice);mostTopLevelSlice.iterateAllSubsequentSlices(callback,opt_this);},get entireHierarchy(){var res=[];this.iterateEntireHierarchy(function(slice){res.push(slice);});return res;},get ancestorAndSubsequentSlices(){var res=[];res.push(this);for(var aSlice of this.enumerateAllAncestors())res.push(aSlice);this.iterateAllSubsequentSlices(function(sSlice){res.push(sSlice);});return res;},enumerateAllDescendents:function*(){for(var slice of this.subSlices)yield slice;for(var slice of this.subSlices)yield*slice.enumerateAllDescendents();},get descendentSlices(){var res=[];for(var slice of this.enumerateAllDescendents())res.push(slice);return res;}};return{Slice:Slice};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/unit.js":62,"./timed_event.js":165}],157:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/base.js":34,"../base/color_scheme.js":38}],157:[function(require,module,exports){
+(function (global){
+"use strict";require("../base/unit.js");require("./timed_event.js");'use strict';global.tr.exportTo('tr.model',function(){function Slice(category,title,colorId,start,args,opt_duration,opt_cpuStart,opt_cpuDuration,opt_argsStripped,opt_bindId){if(new.target){throw new Error("Can't instantiate pure virtual class Slice");}tr.model.TimedEvent.call(this,start);this.category=category||'';this.title=title;this.colorId=colorId;this.args=args;this.startStackFrame=undefined;this.endStackFrame=undefined;this.didNotFinish=false;this.inFlowEvents=[];this.outFlowEvents=[];this.subSlices=[];this.selfTime=undefined;this.cpuSelfTime=undefined;this.important=false;this.parentContainer=undefined;this.argsStripped=false;this.bind_id_=opt_bindId;this.parentSlice=undefined;this.isTopLevel=false;if(opt_duration!==undefined)this.duration=opt_duration;if(opt_cpuStart!==undefined)this.cpuStart=opt_cpuStart;if(opt_cpuDuration!==undefined)this.cpuDuration=opt_cpuDuration;if(opt_argsStripped!==undefined)this.argsStripped=true;}Slice.prototype={__proto__:tr.model.TimedEvent.prototype,get analysisTypeName(){return this.title;},get userFriendlyName(){return'Slice '+this.title+' at '+tr.b.Unit.byName.timeStampInMs.format(this.start);},get stableId(){var parentSliceGroup=this.parentContainer.sliceGroup;return parentSliceGroup.stableId+'.'+parentSliceGroup.slices.indexOf(this);},findDescendentSlice:function(targetTitle){if(!this.subSlices)return undefined;for(var i=0;i<this.subSlices.length;i++){if(this.subSlices[i].title==targetTitle)return this.subSlices[i];var slice=this.subSlices[i].findDescendentSlice(targetTitle);if(slice)return slice;}return undefined;},get mostTopLevelSlice(){var curSlice=this;while(curSlice.parentSlice)curSlice=curSlice.parentSlice;return curSlice;},getProcess:function(){var thread=this.parentContainer;if(thread&&thread.getProcess)return thread.getProcess();return undefined;},get model(){var process=this.getProcess();if(process!==undefined)return this.getProcess().model;return undefined;},findTopmostSlicesRelativeToThisSlice:function*(eventPredicate){if(eventPredicate(this)){yield this;return;}for(var s of this.subSlices)yield*s.findTopmostSlicesRelativeToThisSlice(eventPredicate);},iterateAllSubsequentSlices:function(callback,opt_this){var parentStack=[];var started=false;var topmostSlice=this.mostTopLevelSlice;parentStack.push(topmostSlice);while(parentStack.length!==0){var curSlice=parentStack.pop();if(started)callback.call(opt_this,curSlice);else started=curSlice.guid===this.guid;for(var i=curSlice.subSlices.length-1;i>=0;i--){parentStack.push(curSlice.subSlices[i]);}}},get subsequentSlices(){var res=[];this.iterateAllSubsequentSlices(function(subseqSlice){res.push(subseqSlice);});return res;},enumerateAllAncestors:function*(){var curSlice=this;while(curSlice.parentSlice){curSlice=curSlice.parentSlice;yield curSlice;}},get ancestorSlices(){var res=[];for(var slice of this.enumerateAllAncestors())res.push(slice);return res;},iterateEntireHierarchy:function(callback,opt_this){var mostTopLevelSlice=this.mostTopLevelSlice;callback.call(opt_this,mostTopLevelSlice);mostTopLevelSlice.iterateAllSubsequentSlices(callback,opt_this);},get entireHierarchy(){var res=[];this.iterateEntireHierarchy(function(slice){res.push(slice);});return res;},get ancestorAndSubsequentSlices(){var res=[];res.push(this);for(var aSlice of this.enumerateAllAncestors())res.push(aSlice);this.iterateAllSubsequentSlices(function(sSlice){res.push(sSlice);});return res;},enumerateAllDescendents:function*(){for(var slice of this.subSlices)yield slice;for(var slice of this.subSlices)yield*slice.enumerateAllDescendents();},get descendentSlices(){var res=[];for(var slice of this.enumerateAllDescendents())res.push(slice);return res;}};return{Slice:Slice};});
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/unit.js":63,"./timed_event.js":166}],158:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/color_scheme.js");require("../base/guid.js");require("../base/sorted_array_utils.js");require("../core/filter.js");require("./event_container.js");require("./thread_slice.js");'use strict';global.tr.exportTo('tr.model',function(){var ColorScheme=tr.b.ColorScheme;var ThreadSlice=tr.model.ThreadSlice;function getSliceLo(s){return s.start;}function getSliceHi(s){return s.end;}function SliceGroup(parentContainer,opt_sliceConstructor,opt_name){tr.model.EventContainer.call(this);this.parentContainer_=parentContainer;var sliceConstructor=opt_sliceConstructor||ThreadSlice;this.sliceConstructor=sliceConstructor;this.sliceConstructorSubTypes=this.sliceConstructor.subTypes;if(!this.sliceConstructorSubTypes)throw new Error('opt_sliceConstructor must have a subtype registry.');this.openPartialSlices_=[];this.slices=[];this.topLevelSlices=[];this.haveTopLevelSlicesBeenBuilt=false;this.name_=opt_name;if(this.model===undefined)throw new Error('SliceGroup must have model defined.');}SliceGroup.prototype={__proto__:tr.model.EventContainer.prototype,get parentContainer(){return this.parentContainer_;},get model(){return this.parentContainer_.model;},get stableId(){return this.parentContainer_.stableId+'.SliceGroup';},getSettingsKey:function(){if(!this.name_)return undefined;var parentKey=this.parentContainer_.getSettingsKey();if(!parentKey)return undefined;return parentKey+'.'+this.name;},get length(){return this.slices.length;},pushSlice:function(slice){this.haveTopLevelSlicesBeenBuilt=false;slice.parentContainer=this.parentContainer_;this.slices.push(slice);return slice;},pushSlices:function(slices){this.haveTopLevelSlicesBeenBuilt=false;slices.forEach(function(slice){slice.parentContainer=this.parentContainer_;this.slices.push(slice);},this);},beginSlice:function(category,title,ts,opt_args,opt_tts,opt_argsStripped,opt_colorId){if(this.openPartialSlices_.length){var prevSlice=this.openPartialSlices_[this.openPartialSlices_.length-1];if(ts<prevSlice.start)throw new Error('Slices must be added in increasing timestamp order');}var colorId=opt_colorId||ColorScheme.getColorIdForGeneralPurposeString(title);var sliceConstructorSubTypes=this.sliceConstructorSubTypes;var sliceType=sliceConstructorSubTypes.getConstructor(category,title);var slice=new sliceType(category,title,colorId,ts,opt_args?opt_args:{},null,opt_tts,undefined,opt_argsStripped);this.openPartialSlices_.push(slice);slice.didNotFinish=true;this.pushSlice(slice);return slice;},isTimestampValidForBeginOrEnd:function(ts){if(!this.openPartialSlices_.length)return true;var top=this.openPartialSlices_[this.openPartialSlices_.length-1];return ts>=top.start;},get openSliceCount(){return this.openPartialSlices_.length;},get mostRecentlyOpenedPartialSlice(){if(!this.openPartialSlices_.length)return undefined;return this.openPartialSlices_[this.openPartialSlices_.length-1];},endSlice:function(ts,opt_tts,opt_colorId){if(!this.openSliceCount)throw new Error('endSlice called without an open slice');var slice=this.openPartialSlices_[this.openSliceCount-1];this.openPartialSlices_.splice(this.openSliceCount-1,1);if(ts<slice.start)throw new Error('Slice '+slice.title+' end time is before its start.');slice.duration=ts-slice.start;slice.didNotFinish=false;slice.colorId=opt_colorId||slice.colorId;if(opt_tts&&slice.cpuStart!==undefined)slice.cpuDuration=opt_tts-slice.cpuStart;return slice;},pushCompleteSlice:function(category,title,ts,duration,tts,cpuDuration,opt_args,opt_argsStripped,opt_colorId,opt_bindId){var colorId=opt_colorId||ColorScheme.getColorIdForGeneralPurposeString(title);var sliceConstructorSubTypes=this.sliceConstructorSubTypes;var sliceType=sliceConstructorSubTypes.getConstructor(category,title);var slice=new sliceType(category,title,colorId,ts,opt_args?opt_args:{},duration,tts,cpuDuration,opt_argsStripped,opt_bindId);if(duration===undefined)slice.didNotFinish=true;this.pushSlice(slice);return slice;},autoCloseOpenSlices:function(){this.updateBounds();var maxTimestamp=this.bounds.max;for(var sI=0;sI<this.slices.length;sI++){var slice=this.slices[sI];if(slice.didNotFinish)slice.duration=maxTimestamp-slice.start;}this.openPartialSlices_=[];},shiftTimestampsForward:function(amount){for(var sI=0;sI<this.slices.length;sI++){var slice=this.slices[sI];slice.start=slice.start+amount;}},updateBounds:function(){this.bounds.reset();for(var i=0;i<this.slices.length;i++){this.bounds.addValue(this.slices[i].start);this.bounds.addValue(this.slices[i].end);}},copySlice:function(slice){var sliceConstructorSubTypes=this.sliceConstructorSubTypes;var sliceType=sliceConstructorSubTypes.getConstructor(slice.category,slice.title);var newSlice=new sliceType(slice.category,slice.title,slice.colorId,slice.start,slice.args,slice.duration,slice.cpuStart,slice.cpuDuration);newSlice.didNotFinish=slice.didNotFinish;return newSlice;},findTopmostSlicesInThisContainer:function*(eventPredicate,opt_this){if(!this.haveTopLevelSlicesBeenBuilt)throw new Error('Nope');for(var s of this.topLevelSlices)yield*s.findTopmostSlicesRelativeToThisSlice(eventPredicate);},childEvents:function*(){yield*this.slices;},childEventContainers:function*(){},getSlicesOfName:function(title){var slices=[];for(var i=0;i<this.slices.length;i++){if(this.slices[i].title==title){slices.push(this.slices[i]);}}return slices;},iterSlicesInTimeRange:function(callback,start,end){var ret=[];tr.b.iterateOverIntersectingIntervals(this.topLevelSlices,function(s){return s.start;},function(s){return s.duration;},start,end,function(topLevelSlice){callback(topLevelSlice);for(var slice of topLevelSlice.enumerateAllDescendents())callback(slice);});return ret;},findFirstSlice:function(){if(!this.haveTopLevelSlicesBeenBuilt)throw new Error('Nope');if(0===this.slices.length)return undefined;return this.slices[0];},findSliceAtTs:function(ts){if(!this.haveTopLevelSlicesBeenBuilt)throw new Error('Nope');var i=tr.b.findIndexInSortedClosedIntervals(this.topLevelSlices,getSliceLo,getSliceHi,ts);if(i==-1||i==this.topLevelSlices.length)return undefined;var curSlice=this.topLevelSlices[i];while(true){var i=tr.b.findIndexInSortedClosedIntervals(curSlice.subSlices,getSliceLo,getSliceHi,ts);if(i==-1||i==curSlice.subSlices.length)return curSlice;curSlice=curSlice.subSlices[i];}},findNextSliceAfter:function(ts,refGuid){var i=tr.b.findLowIndexInSortedArray(this.slices,getSliceLo,ts);if(i===this.slices.length)return undefined;for(;i<this.slices.length;i++){var slice=this.slices[i];if(slice.start>ts)return slice;if(slice.guid<=refGuid)continue;return slice;}return undefined;},createSubSlices:function(){this.haveTopLevelSlicesBeenBuilt=true;this.createSubSlicesImpl_();if(this.parentContainer.timeSlices)this.addCpuTimeToSubslices_(this.parentContainer.timeSlices);this.slices.forEach(function(slice){var selfTime=slice.duration;for(var i=0;i<slice.subSlices.length;i++)selfTime-=slice.subSlices[i].duration;slice.selfTime=selfTime;if(slice.cpuDuration===undefined)return;var cpuSelfTime=slice.cpuDuration;for(var i=0;i<slice.subSlices.length;i++){if(slice.subSlices[i].cpuDuration!==undefined)cpuSelfTime-=slice.subSlices[i].cpuDuration;}slice.cpuSelfTime=cpuSelfTime;});},createSubSlicesImpl_:function(){var precisionUnit=this.model.intrinsicTimeUnit;function addSliceIfBounds(parent,child){if(parent.bounds(child,precisionUnit)){child.parentSlice=parent;if(parent.subSlices===undefined)parent.subSlices=[];parent.subSlices.push(child);return true;}return false;}if(!this.slices.length)return;var ops=[];for(var i=0;i<this.slices.length;i++){if(this.slices[i].subSlices)this.slices[i].subSlices.splice(0,this.slices[i].subSlices.length);ops.push(i);}var originalSlices=this.slices;ops.sort(function(ix,iy){var x=originalSlices[ix];var y=originalSlices[iy];if(x.start!=y.start)return x.start-y.start;return ix-iy;});var slices=new Array(this.slices.length);for(var i=0;i<ops.length;i++){slices[i]=originalSlices[ops[i]];}var rootSlice=slices[0];this.topLevelSlices=[];this.topLevelSlices.push(rootSlice);rootSlice.isTopLevel=true;for(var i=1;i<slices.length;i++){var slice=slices[i];while(rootSlice!==undefined&&!addSliceIfBounds(rootSlice,slice)){rootSlice=rootSlice.parentSlice;}if(rootSlice===undefined){this.topLevelSlices.push(slice);slice.isTopLevel=true;}rootSlice=slice;}this.slices=slices;},addCpuTimeToSubslices_:function(timeSlices){var SCHEDULING_STATE=tr.model.SCHEDULING_STATE;var sliceIdx=0;timeSlices.forEach(function(timeSlice){if(timeSlice.schedulingState==SCHEDULING_STATE.RUNNING){while(sliceIdx<this.topLevelSlices.length){if(this.addCpuTimeToSubslice_(this.topLevelSlices[sliceIdx],timeSlice)){sliceIdx++;}else{break;}}}},this);},addCpuTimeToSubslice_:function(slice,timeSlice){if(slice.start>timeSlice.end||slice.end<timeSlice.start)return slice.end<=timeSlice.end;var duration=timeSlice.duration;if(slice.start>timeSlice.start)duration-=slice.start-timeSlice.start;if(timeSlice.end>slice.end)duration-=timeSlice.end-slice.end;if(slice.cpuDuration){slice.cpuDuration+=duration;}else{slice.cpuDuration=duration;}for(var i=0;i<slice.subSlices.length;i++){this.addCpuTimeToSubslice_(slice.subSlices[i],timeSlice);}return slice.end<=timeSlice.end;}};SliceGroup.merge=function(groupA,groupB){if(groupA.openPartialSlices_.length>0)throw new Error('groupA has open partial slices');if(groupB.openPartialSlices_.length>0)throw new Error('groupB has open partial slices');if(groupA.parentContainer!=groupB.parentContainer)throw new Error('Different parent threads. Cannot merge');if(groupA.sliceConstructor!=groupB.sliceConstructor)throw new Error('Different slice constructors. Cannot merge');var result=new SliceGroup(groupA.parentContainer,groupA.sliceConstructor,groupA.name_);var slicesA=groupA.slices;var slicesB=groupB.slices;var idxA=0;var idxB=0;var openA=[];var openB=[];var splitOpenSlices=function(when){for(var i=0;i<openB.length;i++){var oldSlice=openB[i];var oldEnd=oldSlice.end;if(when<oldSlice.start||oldEnd<when){throw new Error('slice should not be split');}var newSlice=result.copySlice(oldSlice);newSlice.start=when;newSlice.duration=oldEnd-when;if(newSlice.title.indexOf(' (cont.)')==-1)newSlice.title+=' (cont.)';oldSlice.duration=when-oldSlice.start;openB[i]=newSlice;result.pushSlice(newSlice);}};var closeOpenSlices=function(upTo){while(openA.length>0||openB.length>0){var nextA=openA[openA.length-1];var nextB=openB[openB.length-1];var endA=nextA&&nextA.end;var endB=nextB&&nextB.end;if((endA===undefined||endA>upTo)&&(endB===undefined||endB>upTo)){return;}if(endB===undefined||endA<endB){splitOpenSlices(endA);openA.pop();}else{openB.pop();}}};while(idxA<slicesA.length||idxB<slicesB.length){var sA=slicesA[idxA];var sB=slicesB[idxB];var nextSlice,isFromB;if(sA===undefined||sB!==undefined&&sA.start>sB.start){nextSlice=result.copySlice(sB);isFromB=true;idxB++;}else{nextSlice=result.copySlice(sA);isFromB=false;idxA++;}closeOpenSlices(nextSlice.start);result.pushSlice(nextSlice);if(isFromB){openB.push(nextSlice);}else{splitOpenSlices(nextSlice.start);openA.push(nextSlice);}}closeOpenSlices();return result;};return{SliceGroup:SliceGroup};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/color_scheme.js":37,"../base/guid.js":44,"../base/sorted_array_utils.js":57,"../core/filter.js":66,"./event_container.js":122,"./thread_slice.js":162}],158:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/color_scheme.js":38,"../base/guid.js":45,"../base/sorted_array_utils.js":58,"../core/filter.js":67,"./event_container.js":123,"./thread_slice.js":163}],159:[function(require,module,exports){
+(function (global){
 "use strict";require("./source_info.js");'use strict';global.tr.exportTo('tr.model.source_info',function(){function JSSourceInfo(file,line,column,isNative,scriptId,state){tr.model.source_info.SourceInfo.call(this,file,line,column);this.isNative_=isNative;this.scriptId_=scriptId;this.state_=state;}JSSourceInfo.prototype={__proto__:tr.model.source_info.SourceInfo.prototype,get state(){return this.state_;},get isNative(){return this.isNative_;},get scriptId(){return this.scriptId_;},toString:function(){var str=this.isNative_?'[native v8] ':'';return str+tr.model.source_info.SourceInfo.prototype.toString.call(this);}};return{JSSourceInfo:JSSourceInfo,JSSourceState:{COMPILED:'compiled',OPTIMIZABLE:'optimizable',OPTIMIZED:'optimized',UNKNOWN:'unknown'}};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./source_info.js":159}],159:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./source_info.js":160}],160:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/base.js");'use strict';global.tr.exportTo('tr.model.source_info',function(){function SourceInfo(file,opt_line,opt_column){this.file_=file;this.line_=opt_line||-1;this.column_=opt_column||-1;}SourceInfo.prototype={get file(){return this.file_;},get line(){return this.line_;},get column(){return this.column_;},get domain(){if(!this.file_)return undefined;var domain=this.file_.match(/(.*:\/\/[^:\/]*)/i);return domain?domain[1]:undefined;},toString:function(){var str='';if(this.file_)str+=this.file_;if(this.line_>0)str+=':'+this.line_;if(this.column_>0)str+=':'+this.column_;return str;}};return{SourceInfo:SourceInfo};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/base.js":33}],160:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/base.js":34}],161:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/base.js");'use strict';global.tr.exportTo('tr.model',function(){function StackFrame(parentFrame,id,title,colorId,opt_sourceInfo){if(id===undefined)throw new Error('id must be given');this.parentFrame_=parentFrame;this.id=id;this.title_=title;this.colorId=colorId;this.children=[];this.sourceInfo_=opt_sourceInfo;if(this.parentFrame_)this.parentFrame_.addChild(this);}StackFrame.prototype={get parentFrame(){return this.parentFrame_;},get title(){if(this.sourceInfo_){var src=this.sourceInfo_.toString();return this.title_+(src===''?'':' '+src);}return this.title_;},get domain(){var result='unknown';if(this.sourceInfo_&&this.sourceInfo_.domain)result=this.sourceInfo_.domain;if(result==='unknown'&&this.parentFrame)result=this.parentFrame.domain;return result;},get sourceInfo(){return this.sourceInfo_;},set parentFrame(parentFrame){if(this.parentFrame_)Polymer.dom(this.parentFrame_).removeChild(this);this.parentFrame_=parentFrame;if(this.parentFrame_)this.parentFrame_.addChild(this);},addChild:function(child){this.children.push(child);},removeChild:function(child){var i=this.children.indexOf(child.id);if(i==-1)throw new Error('omg');this.children.splice(i,1);},removeAllChildren:function(){for(var i=0;i<this.children.length;i++)this.children[i].parentFrame_=undefined;this.children.splice(0,this.children.length);},get stackTrace(){var stack=[];var cur=this;while(cur){stack.push(cur);cur=cur.parentFrame;}return stack;},getUserFriendlyStackTrace:function(){return this.stackTrace.map(function(x){return x.title;});}};return{StackFrame:StackFrame};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/base.js":33}],161:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/base.js":34}],162:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/guid.js");require("../base/range.js");require("./async_slice_group.js");require("./event_container.js");require("./slice_group.js");require("./thread_slice.js");'use strict';global.tr.exportTo('tr.model',function(){var AsyncSlice=tr.model.AsyncSlice;var AsyncSliceGroup=tr.model.AsyncSliceGroup;var SliceGroup=tr.model.SliceGroup;var ThreadSlice=tr.model.ThreadSlice;var ThreadTimeSlice=tr.model.ThreadTimeSlice;function Thread(parent,tid){if(!parent)throw new Error('Parent must be provided.');tr.model.EventContainer.call(this);this.parent=parent;this.sortIndex=0;this.tid=tid;this.name=undefined;this.samples_=undefined;var that=this;this.sliceGroup=new SliceGroup(this,ThreadSlice,'slices');this.timeSlices=undefined;this.kernelSliceGroup=new SliceGroup(this,ThreadSlice,'kernel-slices');this.asyncSliceGroup=new AsyncSliceGroup(this,'async-slices');}Thread.prototype={__proto__:tr.model.EventContainer.prototype,get model(){return this.parent.model;},get stableId(){return this.parent.stableId+'.'+this.tid;},compareTo:function(that){return Thread.compare(this,that);},childEventContainers:function*(){if(this.sliceGroup.length)yield this.sliceGroup;if(this.kernelSliceGroup.length)yield this.kernelSliceGroup;if(this.asyncSliceGroup.length)yield this.asyncSliceGroup;},childEvents:function*(){if(this.timeSlices)yield*this.timeSlices;},iterateAllPersistableObjects:function(cb){cb(this);if(this.sliceGroup.length)cb(this.sliceGroup);this.asyncSliceGroup.viewSubGroups.forEach(cb);},shiftTimestampsForward:function(amount){this.sliceGroup.shiftTimestampsForward(amount);if(this.timeSlices){for(var i=0;i<this.timeSlices.length;i++){var slice=this.timeSlices[i];slice.start+=amount;}}this.kernelSliceGroup.shiftTimestampsForward(amount);this.asyncSliceGroup.shiftTimestampsForward(amount);},get isEmpty(){if(this.sliceGroup.length)return false;if(this.sliceGroup.openSliceCount)return false;if(this.timeSlices&&this.timeSlices.length)return false;if(this.kernelSliceGroup.length)return false;if(this.asyncSliceGroup.length)return false;if(this.samples_.length)return false;return true;},updateBounds:function(){this.bounds.reset();this.sliceGroup.updateBounds();this.bounds.addRange(this.sliceGroup.bounds);this.kernelSliceGroup.updateBounds();this.bounds.addRange(this.kernelSliceGroup.bounds);this.asyncSliceGroup.updateBounds();this.bounds.addRange(this.asyncSliceGroup.bounds);if(this.timeSlices&&this.timeSlices.length){this.bounds.addValue(this.timeSlices[0].start);this.bounds.addValue(this.timeSlices[this.timeSlices.length-1].end);}if(this.samples_&&this.samples_.length){this.bounds.addValue(this.samples_[0].start);this.bounds.addValue(this.samples_[this.samples_.length-1].end);}},addCategoriesToDict:function(categoriesDict){for(var i=0;i<this.sliceGroup.length;i++)categoriesDict[this.sliceGroup.slices[i].category]=true;for(var i=0;i<this.kernelSliceGroup.length;i++)categoriesDict[this.kernelSliceGroup.slices[i].category]=true;for(var i=0;i<this.asyncSliceGroup.length;i++)categoriesDict[this.asyncSliceGroup.slices[i].category]=true;if(this.samples_){for(var i=0;i<this.samples_.length;i++)categoriesDict[this.samples_[i].category]=true;}},autoCloseOpenSlices:function(){this.sliceGroup.autoCloseOpenSlices();this.kernelSliceGroup.autoCloseOpenSlices();},mergeKernelWithUserland:function(){if(this.kernelSliceGroup.length>0){var newSlices=SliceGroup.merge(this.sliceGroup,this.kernelSliceGroup);this.sliceGroup.slices=newSlices.slices;this.kernelSliceGroup=new SliceGroup(this);this.updateBounds();}},createSubSlices:function(){this.sliceGroup.createSubSlices();this.samples_=this.parent.model.samples.filter(function(sample){return sample.thread==this;},this);},get userFriendlyName(){return this.name||this.tid;},get userFriendlyDetails(){return'tid: '+this.tid+(this.name?', name: '+this.name:'');},getSettingsKey:function(){if(!this.name)return undefined;var parentKey=this.parent.getSettingsKey();if(!parentKey)return undefined;return parentKey+'.'+this.name;},getProcess:function(){return this.parent;},indexOfTimeSlice:function(timeSlice){var i=tr.b.findLowIndexInSortedArray(this.timeSlices,function(slice){return slice.start;},timeSlice.start);if(this.timeSlices[i]!==timeSlice)return undefined;return i;},getCpuStatsForRange:function(range){var stats={};stats.total=0;if(!this.timeSlices)return stats;function addStatsForSlice(threadTimeSlice){var freqRange=tr.b.Range.fromExplicitRange(threadTimeSlice.start,threadTimeSlice.end);var intersection=freqRange.findIntersection(range);if(threadTimeSlice.schedulingState==tr.model.SCHEDULING_STATE.RUNNING){var cpu=threadTimeSlice.cpuOnWhichThreadWasRunning;if(!(cpu.cpuNumber in stats))stats[cpu.cpuNumber]=0;stats[cpu.cpuNumber]+=intersection.duration;stats.total+=intersection.duration;}}tr.b.iterateOverIntersectingIntervals(this.timeSlices,function(x){return x.start;},function(x){return x.end;},range.min,range.max,addStatsForSlice);return stats;},getSchedulingStatsForRange:function(start,end){var stats={};if(!this.timeSlices)return stats;function addStatsForSlice(threadTimeSlice){var overlapStart=Math.max(threadTimeSlice.start,start);var overlapEnd=Math.min(threadTimeSlice.end,end);var schedulingState=threadTimeSlice.schedulingState;if(!(schedulingState in stats))stats[schedulingState]=0;stats[schedulingState]+=overlapEnd-overlapStart;}tr.b.iterateOverIntersectingIntervals(this.timeSlices,function(x){return x.start;},function(x){return x.end;},start,end,addStatsForSlice);return stats;},get samples(){return this.samples_;}};Thread.compare=function(x,y){var tmp=x.parent.compareTo(y.parent);if(tmp)return tmp;tmp=x.sortIndex-y.sortIndex;if(tmp)return tmp;tmp=tr.b.comparePossiblyUndefinedValues(x.name,y.name,function(x,y){return x.localeCompare(y);});if(tmp)return tmp;return x.tid-y.tid;};return{Thread:Thread};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/guid.js":44,"../base/range.js":52,"./async_slice_group.js":109,"./event_container.js":122,"./slice_group.js":157,"./thread_slice.js":162}],162:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/guid.js":45,"../base/range.js":53,"./async_slice_group.js":110,"./event_container.js":123,"./slice_group.js":158,"./thread_slice.js":163}],163:[function(require,module,exports){
+(function (global){
 "use strict";require("./slice.js");'use strict';global.tr.exportTo('tr.model',function(){var Slice=tr.model.Slice;function ThreadSlice(cat,title,colorId,start,args,opt_duration,opt_cpuStart,opt_cpuDuration,opt_argsStripped,opt_bindId){Slice.call(this,cat,title,colorId,start,args,opt_duration,opt_cpuStart,opt_cpuDuration,opt_argsStripped,opt_bindId);this.subSlices=[];}ThreadSlice.prototype={__proto__:Slice.prototype,get overlappingSamples(){var samples=new tr.model.EventSet();if(!this.parentContainer||!this.parentContainer.samples)return samples;this.parentContainer.samples.forEach(function(sample){if(this.start<=sample.start&&sample.start<=this.end)samples.push(sample);},this);return samples;}};tr.model.EventRegistry.register(ThreadSlice,{name:'slice',pluralName:'slices'});return{ThreadSlice:ThreadSlice};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./slice.js":156}],163:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./slice.js":157}],164:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/range.js");require("./slice.js");'use strict';global.tr.exportTo('tr.model',function(){var Slice=tr.model.Slice;var SCHEDULING_STATE={DEBUG:'Debug',EXIT_DEAD:'Exit Dead',RUNNABLE:'Runnable',RUNNING:'Running',SLEEPING:'Sleeping',STOPPED:'Stopped',TASK_DEAD:'Task Dead',UNINTR_SLEEP:'Uninterruptible Sleep',UNINTR_SLEEP_WAKE_KILL:'Uninterruptible Sleep | WakeKill',UNINTR_SLEEP_WAKING:'Uninterruptible Sleep | Waking',UNINTR_SLEEP_IO:'Uninterruptible Sleep - Block I/O',UNINTR_SLEEP_WAKE_KILL_IO:'Uninterruptible Sleep | WakeKill - Block I/O',UNINTR_SLEEP_WAKING_IO:'Uninterruptible Sleep | Waking - Block I/O',UNKNOWN:'UNKNOWN',WAKE_KILL:'Wakekill',WAKING:'Waking',ZOMBIE:'Zombie'};function ThreadTimeSlice(thread,schedulingState,cat,start,args,opt_duration){Slice.call(this,cat,schedulingState,this.getColorForState_(schedulingState),start,args,opt_duration);this.thread=thread;this.schedulingState=schedulingState;this.cpuOnWhichThreadWasRunning=undefined;}ThreadTimeSlice.prototype={__proto__:Slice.prototype,getColorForState_:function(state){var getColorIdForReservedName=tr.b.ColorScheme.getColorIdForReservedName;switch(state){case SCHEDULING_STATE.RUNNABLE:return getColorIdForReservedName('thread_state_runnable');case SCHEDULING_STATE.RUNNING:return getColorIdForReservedName('thread_state_running');case SCHEDULING_STATE.SLEEPING:return getColorIdForReservedName('thread_state_sleeping');case SCHEDULING_STATE.DEBUG:case SCHEDULING_STATE.EXIT_DEAD:case SCHEDULING_STATE.STOPPED:case SCHEDULING_STATE.TASK_DEAD:case SCHEDULING_STATE.UNINTR_SLEEP:case SCHEDULING_STATE.UNINTR_SLEEP_WAKE_KILL:case SCHEDULING_STATE.UNINTR_SLEEP_WAKING:case SCHEDULING_STATE.UNKNOWN:case SCHEDULING_STATE.WAKE_KILL:case SCHEDULING_STATE.WAKING:case SCHEDULING_STATE.ZOMBIE:return getColorIdForReservedName('thread_state_uninterruptible');case SCHEDULING_STATE.UNINTR_SLEEP_IO:case SCHEDULING_STATE.UNINTR_SLEEP_WAKE_KILL_IO:case SCHEDULING_STATE.UNINTR_SLEEP_WAKING_IO:return getColorIdForReservedName('thread_state_iowait');default:return getColorIdForReservedName('thread_state_unknown');}},get analysisTypeName(){return'tr.ui.analysis.ThreadTimeSlice';},getAssociatedCpuSlice:function(){if(!this.cpuOnWhichThreadWasRunning)return undefined;var cpuSlices=this.cpuOnWhichThreadWasRunning.slices;for(var i=0;i<cpuSlices.length;i++){var cpuSlice=cpuSlices[i];if(cpuSlice.start!==this.start)continue;if(cpuSlice.duration!==this.duration)continue;return cpuSlice;}return undefined;},getCpuSliceThatTookCpu:function(){if(this.cpuOnWhichThreadWasRunning)return undefined;var curIndex=this.thread.indexOfTimeSlice(this);var cpuSliceWhenLastRunning;while(curIndex>=0){var curSlice=this.thread.timeSlices[curIndex];if(!curSlice.cpuOnWhichThreadWasRunning){curIndex--;continue;}cpuSliceWhenLastRunning=curSlice.getAssociatedCpuSlice();break;}if(!cpuSliceWhenLastRunning)return undefined;var cpu=cpuSliceWhenLastRunning.cpu;var indexOfSliceOnCpuWhenLastRunning=cpu.indexOf(cpuSliceWhenLastRunning);var nextRunningSlice=cpu.slices[indexOfSliceOnCpuWhenLastRunning+1];if(!nextRunningSlice)return undefined;if(Math.abs(nextRunningSlice.start-cpuSliceWhenLastRunning.end)<0.00001)return nextRunningSlice;return undefined;}};tr.model.EventRegistry.register(ThreadTimeSlice,{name:'threadTimeSlice',pluralName:'threadTimeSlices'});return{ThreadTimeSlice:ThreadTimeSlice,SCHEDULING_STATE:SCHEDULING_STATE};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/range.js":52,"./slice.js":156}],164:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/range.js":53,"./slice.js":157}],165:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/range.js");require("../base/sorted_array_utils.js");'use strict';global.tr.exportTo('tr.model',function(){function TimeToObjectInstanceMap(createObjectInstanceFunction,parent,scopedId){this.createObjectInstanceFunction_=createObjectInstanceFunction;this.parent=parent;this.scopedId=scopedId;this.instances=[];}TimeToObjectInstanceMap.prototype={idWasCreated:function(category,name,ts){if(this.instances.length==0){this.instances.push(this.createObjectInstanceFunction_(this.parent,this.scopedId,category,name,ts));this.instances[0].creationTsWasExplicit=true;return this.instances[0];}var lastInstance=this.instances[this.instances.length-1];if(ts<lastInstance.deletionTs){throw new Error('Mutation of the TimeToObjectInstanceMap must be '+'done in ascending timestamp order.');}lastInstance=this.createObjectInstanceFunction_(this.parent,this.scopedId,category,name,ts);lastInstance.creationTsWasExplicit=true;this.instances.push(lastInstance);return lastInstance;},addSnapshot:function(category,name,ts,args,opt_baseTypeName){if(this.instances.length==0){this.instances.push(this.createObjectInstanceFunction_(this.parent,this.scopedId,category,name,ts,opt_baseTypeName));}var i=tr.b.findIndexInSortedIntervals(this.instances,function(inst){return inst.creationTs;},function(inst){return inst.deletionTs-inst.creationTs;},ts);var instance;if(i<0){instance=this.instances[0];if(ts>instance.deletionTs||instance.creationTsWasExplicit){throw new Error('At the provided timestamp, no instance was still alive');}if(instance.snapshots.length!=0){throw new Error('Cannot shift creationTs forward, '+'snapshots have been added. First snap was at ts='+instance.snapshots[0].ts+' and creationTs was '+instance.creationTs);}instance.creationTs=ts;}else if(i>=this.instances.length){instance=this.instances[this.instances.length-1];if(ts>=instance.deletionTs){instance=this.createObjectInstanceFunction_(this.parent,this.scopedId,category,name,ts,opt_baseTypeName);this.instances.push(instance);}else{var lastValidIndex;for(var i=this.instances.length-1;i>=0;i--){var tmp=this.instances[i];if(ts>=tmp.deletionTs)break;if(tmp.creationTsWasExplicit==false&&tmp.snapshots.length==0)lastValidIndex=i;}if(lastValidIndex===undefined){throw new Error('Cannot add snapshot. No instance was alive that was mutable.');}instance=this.instances[lastValidIndex];instance.creationTs=ts;}}else{instance=this.instances[i];}return instance.addSnapshot(ts,args,name,opt_baseTypeName);},get lastInstance(){if(this.instances.length==0)return undefined;return this.instances[this.instances.length-1];},idWasDeleted:function(category,name,ts){if(this.instances.length==0){this.instances.push(this.createObjectInstanceFunction_(this.parent,this.scopedId,category,name,ts));}var lastInstance=this.instances[this.instances.length-1];if(ts<lastInstance.creationTs)throw new Error('Cannot delete an id before it was created');if(lastInstance.deletionTs==Number.MAX_VALUE){lastInstance.wasDeleted(ts);return lastInstance;}if(ts<lastInstance.deletionTs)throw new Error('id was already deleted earlier.');lastInstance=this.createObjectInstanceFunction_(this.parent,this.scopedId,category,name,ts);this.instances.push(lastInstance);lastInstance.wasDeleted(ts);return lastInstance;},getInstanceAt:function(ts){var i=tr.b.findIndexInSortedIntervals(this.instances,function(inst){return inst.creationTs;},function(inst){return inst.deletionTs-inst.creationTs;},ts);if(i<0){if(this.instances[0].creationTsWasExplicit)return undefined;return this.instances[0];}else if(i>=this.instances.length){return undefined;}return this.instances[i];},logToConsole:function(){for(var i=0;i<this.instances.length;i++){var instance=this.instances[i];var cEF='';var dEF='';if(instance.creationTsWasExplicit)cEF='(explicitC)';if(instance.deletionTsWasExplicit)dEF='(explicit)';console.log(instance.creationTs,cEF,instance.deletionTs,dEF,instance.category,instance.name,instance.snapshots.length+' snapshots');}}};return{TimeToObjectInstanceMap:TimeToObjectInstanceMap};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/range.js":52,"../base/sorted_array_utils.js":57}],165:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/range.js":53,"../base/sorted_array_utils.js":58}],166:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/guid.js");require("../base/time_display_modes.js");require("./event.js");'use strict';global.tr.exportTo('tr.model',function(){function TimedEvent(start){tr.model.Event.call(this);this.start=start;this.duration=0;this.cpuStart=undefined;this.cpuDuration=undefined;this.contexts=Object.freeze([]);}TimedEvent.prototype={__proto__:tr.model.Event.prototype,get end(){return this.start+this.duration;},addBoundsToRange:function(range){range.addValue(this.start);range.addValue(this.end);},bounds:function(that,opt_precisionUnit){if(opt_precisionUnit===undefined)opt_precisionUnit=tr.b.TimeDisplayModes.ms;var startsBefore=opt_precisionUnit.roundedLess(that.start,this.start);var endsAfter=opt_precisionUnit.roundedLess(this.end,that.end);return!startsBefore&&!endsAfter;}};return{TimedEvent:TimedEvent};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/guid.js":44,"../base/time_display_modes.js":60,"./event.js":121}],166:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/guid.js":45,"../base/time_display_modes.js":61,"./event.js":122}],167:[function(require,module,exports){
+(function (global){
 "use strict";require("./user_expectation.js");'use strict';global.tr.exportTo('tr.model.um',function(){function AnimationExpectation(parentModel,initiatorTitle,start,duration){tr.model.um.UserExpectation.call(this,parentModel,initiatorTitle,start,duration);this.frameEvents_=undefined;}AnimationExpectation.prototype={__proto__:tr.model.um.UserExpectation.prototype,constructor:AnimationExpectation,get frameEvents(){if(this.frameEvents_)return this.frameEvents_;this.frameEvents_=new tr.model.EventSet();this.associatedEvents.forEach(function(event){if(event.title===tr.model.helpers.IMPL_RENDERING_STATS)this.frameEvents_.push(event);},this);return this.frameEvents_;}};tr.model.um.UserExpectation.subTypes.register(AnimationExpectation,{stageTitle:'Animation',colorId:tr.b.ColorScheme.getColorIdForReservedName('rail_animation')});return{AnimationExpectation:AnimationExpectation};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./user_expectation.js":171}],167:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./user_expectation.js":172}],168:[function(require,module,exports){
+(function (global){
 "use strict";require("./user_expectation.js");'use strict';global.tr.exportTo('tr.model.um',function(){function IdleExpectation(parentModel,start,duration){var initiatorTitle='';tr.model.um.UserExpectation.call(this,parentModel,initiatorTitle,start,duration);}IdleExpectation.prototype={__proto__:tr.model.um.UserExpectation.prototype,constructor:IdleExpectation};tr.model.um.UserExpectation.subTypes.register(IdleExpectation,{stageTitle:'Idle',colorId:tr.b.ColorScheme.getColorIdForReservedName('rail_idle')});return{IdleExpectation:IdleExpectation};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./user_expectation.js":171}],168:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./user_expectation.js":172}],169:[function(require,module,exports){
+(function (global){
 "use strict";require("./user_expectation.js");'use strict';global.tr.exportTo('tr.model.um',function(){var LOAD_SUBTYPE_NAMES={SUCCESSFUL:'Successful',FAILED:'Failed'};var DOES_LOAD_SUBTYPE_NAME_EXIST={};for(var key in LOAD_SUBTYPE_NAMES){DOES_LOAD_SUBTYPE_NAME_EXIST[LOAD_SUBTYPE_NAMES[key]]=true;;}function LoadExpectation(parentModel,initiatorTitle,start,duration){if(!DOES_LOAD_SUBTYPE_NAME_EXIST[initiatorTitle])throw new Error(initiatorTitle+' is not in LOAD_SUBTYPE_NAMES');tr.model.um.UserExpectation.call(this,parentModel,initiatorTitle,start,duration);this.renderProcess=undefined;this.renderMainThread=undefined;this.routingId=undefined;this.parentRoutingId=undefined;this.loadFinishedEvent=undefined;}LoadExpectation.prototype={__proto__:tr.model.um.UserExpectation.prototype,constructor:LoadExpectation};tr.model.um.UserExpectation.subTypes.register(LoadExpectation,{stageTitle:'Load',colorId:tr.b.ColorScheme.getColorIdForReservedName('rail_load')});return{LOAD_SUBTYPE_NAMES:LOAD_SUBTYPE_NAMES,LoadExpectation:LoadExpectation};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./user_expectation.js":171}],169:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./user_expectation.js":172}],170:[function(require,module,exports){
+(function (global){
 "use strict";require("./user_expectation.js");'use strict';global.tr.exportTo('tr.model.um',function(){function ResponseExpectation(parentModel,initiatorTitle,start,duration,opt_isAnimationBegin){tr.model.um.UserExpectation.call(this,parentModel,initiatorTitle,start,duration);this.isAnimationBegin=opt_isAnimationBegin||false;}ResponseExpectation.prototype={__proto__:tr.model.um.UserExpectation.prototype,constructor:ResponseExpectation};tr.model.um.UserExpectation.subTypes.register(ResponseExpectation,{stageTitle:'Response',colorId:tr.b.ColorScheme.getColorIdForReservedName('rail_response')});return{ResponseExpectation:ResponseExpectation};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./user_expectation.js":171}],170:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./user_expectation.js":172}],171:[function(require,module,exports){
+(function (global){
 "use strict";require("./user_expectation.js");'use strict';global.tr.exportTo('tr.model.um',function(){function StartupExpectation(parentModel,start,duration){tr.model.um.UserExpectation.call(this,parentModel,'',start,duration);}StartupExpectation.prototype={__proto__:tr.model.um.UserExpectation.prototype,constructor:StartupExpectation};tr.model.um.UserExpectation.subTypes.register(StartupExpectation,{stageTitle:'Startup',colorId:tr.b.ColorScheme.getColorIdForReservedName('startup')});return{StartupExpectation:StartupExpectation};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./user_expectation.js":171}],171:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./user_expectation.js":172}],172:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/range_utils.js");require("../../base/statistics.js");require("../../base/unit.js");require("../compound_event_selection_state.js");require("../event_set.js");require("../timed_event.js");'use strict';global.tr.exportTo('tr.model.um',function(){var CompoundEventSelectionState=tr.model.CompoundEventSelectionState;function UserExpectation(parentModel,initiatorTitle,start,duration){tr.model.TimedEvent.call(this,start);this.associatedEvents=new tr.model.EventSet();this.duration=duration;this.initiatorTitle_=initiatorTitle;this.parentModel=parentModel;this.typeInfo_=undefined;this.sourceEvents=new tr.model.EventSet();}UserExpectation.prototype={__proto__:tr.model.TimedEvent.prototype,computeCompoundEvenSelectionState:function(selection){var cess=CompoundEventSelectionState.NOT_SELECTED;if(selection.contains(this))cess|=CompoundEventSelectionState.EVENT_SELECTED;if(this.associatedEvents.intersectionIsEmpty(selection))return cess;var allContained=this.associatedEvents.every(function(event){return selection.contains(event);});if(allContained)cess|=CompoundEventSelectionState.ALL_ASSOCIATED_EVENTS_SELECTED;else cess|=CompoundEventSelectionState.SOME_ASSOCIATED_EVENTS_SELECTED;return cess;},get associatedSamples(){var samples=new tr.model.EventSet();this.associatedEvents.forEach(function(event){if(event instanceof tr.model.ThreadSlice)samples.addEventSet(event.overlappingSamples);});return samples;},get userFriendlyName(){return this.title+' User Expectation at '+tr.b.Unit.byName.timeStampInMs.format(this.start);},get stableId(){return'UserExpectation.'+this.guid;},get typeInfo(){if(!this.typeInfo_){this.typeInfo_=UserExpectation.subTypes.findTypeInfo(this.constructor);}if(!this.typeInfo_)throw new Error('Unregistered UserExpectation');return this.typeInfo_;},get colorId(){return this.typeInfo.metadata.colorId;},get stageTitle(){return this.typeInfo.metadata.stageTitle;},get initiatorTitle(){return this.initiatorTitle_;},get title(){if(!this.initiatorTitle)return this.stageTitle;return this.initiatorTitle+' '+this.stageTitle;},get totalCpuMs(){var cpuMs=0;this.associatedEvents.forEach(function(event){if(event.cpuSelfTime)cpuMs+=event.cpuSelfTime;});return cpuMs;}};var subTypes={};var options=new tr.b.ExtensionRegistryOptions(tr.b.BASIC_REGISTRY_MODE);tr.b.decorateExtensionRegistry(subTypes,options);subTypes.addEventListener('will-register',function(e){var metadata=e.typeInfo.metadata;if(metadata.stageTitle===undefined){throw new Error('Registered UserExpectations must provide '+'stageTitle');}if(metadata.colorId===undefined){throw new Error('Registered UserExpectations must provide '+'colorId');}});tr.model.EventRegistry.register(UserExpectation,{name:'userExpectation',pluralName:'userExpectations',subTypes:subTypes});return{UserExpectation:UserExpectation};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/range_utils.js":53,"../../base/statistics.js":58,"../../base/unit.js":62,"../compound_event_selection_state.js":112,"../event_set.js":125,"../timed_event.js":165}],172:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/range_utils.js":54,"../../base/statistics.js":59,"../../base/unit.js":63,"../compound_event_selection_state.js":113,"../event_set.js":126,"../timed_event.js":166}],173:[function(require,module,exports){
+(function (global){
 "use strict";require("../event_container.js");'use strict';global.tr.exportTo('tr.model.um',function(){function UserModel(parentModel){tr.model.EventContainer.call(this);this.parentModel_=parentModel;this.expectations_=new tr.model.EventSet();}UserModel.prototype={__proto__:tr.model.EventContainer.prototype,get stableId(){return'UserModel';},get parentModel(){return this.parentModel_;},sortExpectations:function(){this.expectations_.sortEvents((x,y)=>x.start-y.start);},get expectations(){return this.expectations_;},shiftTimestampsForward:function(amount){},addCategoriesToDict:function(categoriesDict){},childEvents:function*(){yield*this.expectations;},childEventContainers:function*(){},updateBounds:function(){this.bounds.reset();this.expectations.forEach(function(expectation){expectation.addBoundsToRange(this.bounds);},this);}};return{UserModel:UserModel};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../event_container.js":122}],173:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../event_container.js":123}],174:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/base.js");'use strict';global.tr.exportTo('tr.model',function(){function VMRegion(startAddress,sizeInBytes,protectionFlags,mappedFile,byteStats){this.startAddress=startAddress;this.sizeInBytes=sizeInBytes;this.protectionFlags=protectionFlags;this.mappedFile=mappedFile||'';this.byteStats=byteStats||{};};VMRegion.PROTECTION_FLAG_READ=4;VMRegion.PROTECTION_FLAG_WRITE=2;VMRegion.PROTECTION_FLAG_EXECUTE=1;VMRegion.PROTECTION_FLAG_MAYSHARE=128;VMRegion.prototype={get uniqueIdWithinProcess(){return this.mappedFile+'#'+this.startAddress;},get protectionFlagsToString(){if(this.protectionFlags===undefined)return undefined;return(this.protectionFlags&VMRegion.PROTECTION_FLAG_READ?'r':'-')+(this.protectionFlags&VMRegion.PROTECTION_FLAG_WRITE?'w':'-')+(this.protectionFlags&VMRegion.PROTECTION_FLAG_EXECUTE?'x':'-')+(this.protectionFlags&VMRegion.PROTECTION_FLAG_MAYSHARE?'s':'p');}};VMRegion.fromDict=function(dict){return new VMRegion(dict.startAddress,dict.sizeInBytes,dict.protectionFlags,dict.mappedFile,dict.byteStats);};function VMRegionClassificationNode(opt_rule){this.rule_=opt_rule||VMRegionClassificationNode.CLASSIFICATION_RULES;this.hasRegions=false;this.sizeInBytes=undefined;this.byteStats={};this.children_=undefined;this.regions_=[];}VMRegionClassificationNode.CLASSIFICATION_RULES={name:'Total',children:[{name:'Android',file:/^\/dev\/ashmem(?!\/libc malloc)/,children:[{name:'Java runtime',file:/^\/dev\/ashmem\/dalvik-/,children:[{name:'Spaces',file:/\/dalvik-(alloc|main|large object|non moving|zygote) space/,children:[{name:'Normal',file:/\/dalvik-(alloc|main)/},{name:'Large',file:/\/dalvik-large object/},{name:'Zygote',file:/\/dalvik-zygote/},{name:'Non-moving',file:/\/dalvik-non moving/}]},{name:'Linear Alloc',file:/\/dalvik-LinearAlloc/},{name:'Indirect Reference Table',file:/\/dalvik-indirect.ref/},{name:'Cache',file:/\/dalvik-jit-code-cache/},{name:'Accounting'}]},{name:'Cursor',file:/\/CursorWindow/},{name:'Ashmem'}]},{name:'Native heap',file:/^((\[heap\])|(\[anon:)|(\/dev\/ashmem\/libc malloc)|(\[discounted tracing overhead\])|$)/},{name:'Stack',file:/^\[stack/},{name:'Files',file:/\.((((jar)|(apk)|(ttf)|(odex)|(oat)|(art))$)|(dex)|(so))/,children:[{name:'so',file:/\.so/},{name:'jar',file:/\.jar$/},{name:'apk',file:/\.apk$/},{name:'ttf',file:/\.ttf$/},{name:'dex',file:/\.((dex)|(odex$))/},{name:'oat',file:/\.oat$/},{name:'art',file:/\.art$/}]},{name:'Devices',file:/(^\/dev\/)|(anon_inode:dmabuf)/,children:[{name:'GPU',file:/\/((nv)|(mali)|(kgsl))/},{name:'DMA',file:/anon_inode:dmabuf/}]}]};VMRegionClassificationNode.OTHER_RULE={name:'Other'};VMRegionClassificationNode.fromRegions=function(regions,opt_rules){var tree=new VMRegionClassificationNode(opt_rules);tree.regions_=regions;for(var i=0;i<regions.length;i++)tree.addStatsFromRegion_(regions[i]);return tree;};VMRegionClassificationNode.prototype={get title(){return this.rule_.name;},get children(){if(this.isLeafNode)return undefined;if(this.children_===undefined)this.buildTree_();return this.children_;},get regions(){if(!this.isLeafNode){return undefined;}return this.regions_;},get allRegionsForTesting(){if(this.regions_!==undefined){if(this.children_!==undefined){throw new Error('Internal error: a VM region classification node '+'cannot have both regions and children');}return this.regions_;}var regions=[];this.children_.forEach(function(childNode){regions=regions.concat(childNode.allRegionsForTesting);});return regions;},get isLeafNode(){var children=this.rule_.children;return children===undefined||children.length===0;},addRegion:function(region){this.addRegionRecursively_(region,true);},someRegion:function(fn,opt_this){if(this.regions_!==undefined){return this.regions_.some(fn,opt_this);}return this.children_.some(function(childNode){return childNode.someRegion(fn,opt_this);});},addRegionRecursively_:function(region,addStatsToThisNode){if(addStatsToThisNode)this.addStatsFromRegion_(region);if(this.regions_!==undefined){if(this.children_!==undefined){throw new Error('Internal error: a VM region classification node '+'cannot have both regions and children');}this.regions_.push(region);return;}function regionRowMatchesChildNide(child){var fileRegExp=child.rule_.file;if(fileRegExp===undefined)return true;return fileRegExp.test(region.mappedFile);}var matchedChild=tr.b.findFirstInArray(this.children_,regionRowMatchesChildNide);if(matchedChild===undefined){if(this.children_.length!==this.rule_.children.length)throw new Error('Internal error');matchedChild=new VMRegionClassificationNode(VMRegionClassificationNode.OTHER_RULE);this.children_.push(matchedChild);}matchedChild.addRegionRecursively_(region,true);},buildTree_:function(){var cachedRegions=this.regions_;this.regions_=undefined;this.buildChildNodesRecursively_();for(var i=0;i<cachedRegions.length;i++){this.addRegionRecursively_(cachedRegions[i],false);}},buildChildNodesRecursively_:function(){if(this.children_!==undefined){throw new Error('Internal error: Classification node already has children');}if(this.regions_!==undefined&&this.regions_.length!==0){throw new Error('Internal error: Classification node should have no regions');}if(this.isLeafNode)return;this.regions_=undefined;this.children_=this.rule_.children.map(function(childRule){var child=new VMRegionClassificationNode(childRule);child.buildChildNodesRecursively_();return child;});},addStatsFromRegion_:function(region){this.hasRegions=true;var regionSizeInBytes=region.sizeInBytes;if(regionSizeInBytes!==undefined)this.sizeInBytes=(this.sizeInBytes||0)+regionSizeInBytes;var thisByteStats=this.byteStats;var regionByteStats=region.byteStats;for(var byteStatName in regionByteStats){var regionByteStatValue=regionByteStats[byteStatName];if(regionByteStatValue===undefined)continue;thisByteStats[byteStatName]=(thisByteStats[byteStatName]||0)+regionByteStatValue;}}};return{VMRegion:VMRegion,VMRegionClassificationNode:VMRegionClassificationNode};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/base.js":33}],174:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/base.js":34}],175:[function(require,module,exports){
+(function (global){
 "use strict";require("./annotation.js");require("../ui/annotations/x_marker_annotation_view.js");'use strict';global.tr.exportTo('tr.model',function(){function XMarkerAnnotation(timestamp){tr.model.Annotation.apply(this,arguments);this.timestamp=timestamp;this.strokeStyle='rgba(0, 0, 255, 0.5)';}XMarkerAnnotation.fromDict=function(dict){return new XMarkerAnnotation(dict.args.timestamp);};XMarkerAnnotation.prototype={__proto__:tr.model.Annotation.prototype,toDict:function(){return{typeName:'xmarker',args:{timestamp:this.timestamp}};},createView_:function(viewport){return new tr.ui.annotations.XMarkerAnnotationView(viewport,this);}};tr.model.Annotation.register(XMarkerAnnotation,{typeName:'xmarker'});return{XMarkerAnnotation:XMarkerAnnotation};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../ui/annotations/x_marker_annotation_view.js":178,"./annotation.js":107}],175:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../ui/annotations/x_marker_annotation_view.js":179,"./annotation.js":108}],176:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/base.js");'use strict';global.tr.exportTo('tr.ui.annotations',function(){function AnnotationView(viewport,annotation){}AnnotationView.prototype={draw:function(ctx){throw new Error('Not implemented');}};return{AnnotationView:AnnotationView};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/base.js":33}],176:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/base.js":34}],177:[function(require,module,exports){
+(function (global){
 "use strict";require("./annotation_view.js");'use strict';global.tr.exportTo('tr.ui.annotations',function(){function CommentBoxAnnotationView(viewport,annotation){this.viewport_=viewport;this.annotation_=annotation;this.textArea_=undefined;this.styleWidth=250;this.styleHeight=50;this.fontSize=10;this.rightOffset=50;this.topOffset=25;}CommentBoxAnnotationView.prototype={__proto__:tr.ui.annotations.AnnotationView.prototype,removeTextArea:function(){Polymer.dom(Polymer.dom(this.textArea_).parentNode).removeChild(this.textArea_);},draw:function(ctx){var coords=this.annotation_.location.toViewCoordinates(this.viewport_);if(coords.viewX<0){if(this.textArea_)this.textArea_.style.visibility='hidden';return;}if(!this.textArea_){this.textArea_=document.createElement('textarea');this.textArea_.style.position='absolute';this.textArea_.readOnly=true;this.textArea_.value=this.annotation_.text;this.textArea_.style.zIndex=1;Polymer.dom(Polymer.dom(ctx.canvas).parentNode).appendChild(this.textArea_);}this.textArea_.style.width=this.styleWidth+'px';this.textArea_.style.height=this.styleHeight+'px';this.textArea_.style.fontSize=this.fontSize+'px';this.textArea_.style.visibility='visible';this.textArea_.style.left=coords.viewX+ctx.canvas.getBoundingClientRect().left+this.rightOffset+'px';this.textArea_.style.top=coords.viewY-ctx.canvas.getBoundingClientRect().top-this.topOffset+'px';ctx.strokeStyle='rgb(0, 0, 0)';ctx.lineWidth=2;ctx.beginPath();tr.ui.b.drawLine(ctx,coords.viewX,coords.viewY-ctx.canvas.getBoundingClientRect().top,coords.viewX+this.rightOffset,coords.viewY-this.topOffset-ctx.canvas.getBoundingClientRect().top);ctx.stroke();}};return{CommentBoxAnnotationView:CommentBoxAnnotationView};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./annotation_view.js":175}],177:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./annotation_view.js":176}],178:[function(require,module,exports){
+(function (global){
 "use strict";require("./annotation_view.js");'use strict';global.tr.exportTo('tr.ui.annotations',function(){function RectAnnotationView(viewport,annotation){this.viewport_=viewport;this.annotation_=annotation;}RectAnnotationView.prototype={__proto__:tr.ui.annotations.AnnotationView.prototype,draw:function(ctx){var dt=this.viewport_.currentDisplayTransform;var startCoords=this.annotation_.startLocation.toViewCoordinates(this.viewport_);var endCoords=this.annotation_.endLocation.toViewCoordinates(this.viewport_);var startY=startCoords.viewY-ctx.canvas.getBoundingClientRect().top;var sizeY=endCoords.viewY-startCoords.viewY;if(startY+sizeY<0){startY=sizeY;}else if(startY<0){startY=0;}ctx.fillStyle=this.annotation_.fillStyle;ctx.fillRect(startCoords.viewX,startY,endCoords.viewX-startCoords.viewX,sizeY);}};return{RectAnnotationView:RectAnnotationView};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./annotation_view.js":175}],178:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./annotation_view.js":176}],179:[function(require,module,exports){
+(function (global){
 "use strict";require("./annotation_view.js");'use strict';global.tr.exportTo('tr.ui.annotations',function(){function XMarkerAnnotationView(viewport,annotation){this.viewport_=viewport;this.annotation_=annotation;}XMarkerAnnotationView.prototype={__proto__:tr.ui.annotations.AnnotationView.prototype,draw:function(ctx){var dt=this.viewport_.currentDisplayTransform;var viewX=dt.xWorldToView(this.annotation_.timestamp);ctx.beginPath();tr.ui.b.drawLine(ctx,viewX,0,viewX,ctx.canvas.height);ctx.strokeStyle=this.annotation_.strokeStyle;ctx.stroke();}};return{XMarkerAnnotationView:XMarkerAnnotationView};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./annotation_view.js":175}],179:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./annotation_view.js":176}],180:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/event.js");require("../../base/utils.js");require("./ui.js");require("./utils.js");'use strict';global.tr.exportTo('tr.ui.b',function(){if(tr.isHeadless)return{};return;var Overlay=tr.ui.b.define('overlay');Overlay.prototype={__proto__:HTMLDivElement.prototype,decorate:function(){Polymer.dom(this).classList.add('overlay');this.parentEl_=this.ownerDocument.body;this.visible_=false;this.userCanClose_=true;this.onKeyDown_=this.onKeyDown_.bind(this);this.onClick_=this.onClick_.bind(this);this.onFocusIn_=this.onFocusIn_.bind(this);this.onDocumentClick_=this.onDocumentClick_.bind(this);this.onClose_=this.onClose_.bind(this);this.addEventListener('visible-change',tr.ui.b.Overlay.prototype.onVisibleChange_.bind(this),true);var createShadowRoot=this.createShadowRoot||this.webkitCreateShadowRoot;this.shadow_=createShadowRoot.call(this);Polymer.dom(this.shadow_).appendChild(tr.ui.b.instantiateTemplate('#overlay-template',THIS_DOC));this.closeBtn_=Polymer.dom(this.shadow_).querySelector('close-button');this.closeBtn_.addEventListener('click',this.onClose_);Polymer.dom(this.shadow_).querySelector('overlay-frame').addEventListener('click',this.onClick_);this.observer_=new WebKitMutationObserver(this.didButtonBarMutate_.bind(this));this.observer_.observe(Polymer.dom(this.shadow_).querySelector('button-bar'),{childList:true});Object.defineProperty(this,'title',{get:function(){return Polymer.dom(Polymer.dom(this.shadow_).querySelector('title')).textContent;},set:function(title){Polymer.dom(Polymer.dom(this.shadow_).querySelector('title')).textContent=title;}});},set userCanClose(userCanClose){this.userCanClose_=userCanClose;this.closeBtn_.style.display=userCanClose?'block':'none';},get buttons(){return Polymer.dom(this.shadow_).querySelector('button-bar');},get visible(){return this.visible_;},set visible(newValue){if(this.visible_===newValue)return;this.visible_=newValue;var e=new tr.b.Event('visible-change');this.dispatchEvent(e);},onVisibleChange_:function(){this.visible_?this.show_():this.hide_();},show_:function(){Polymer.dom(this.parentEl_).appendChild(this);if(this.userCanClose_){this.addEventListener('keydown',this.onKeyDown_.bind(this));this.addEventListener('click',this.onDocumentClick_.bind(this));this.closeBtn_.addEventListener('click',this.onClose_);}this.parentEl_.addEventListener('focusin',this.onFocusIn_);this.tabIndex=0;var focusEl=undefined;var elList=Polymer.dom(this).querySelectorAll('button, input, list, select, a');if(elList.length>0){if(elList[0]===this.closeBtn_){if(elList.length>1)focusEl=elList[1];}else{focusEl=elList[0];}}if(focusEl===undefined)focusEl=this;focusEl.focus();},hide_:function(){Polymer.dom(this.parentEl_).removeChild(this);this.parentEl_.removeEventListener('focusin',this.onFocusIn_);if(this.closeBtn_)this.closeBtn_.removeEventListener('click',this.onClose_);document.removeEventListener('keydown',this.onKeyDown_);document.removeEventListener('click',this.onDocumentClick_);},onClose_:function(e){this.visible=false;if(e.type!='keydown'||e.type==='keydown'&&e.keyCode===27)e.stopPropagation();e.preventDefault();tr.b.dispatchSimpleEvent(this,'closeclick');},onFocusIn_:function(e){if(e.target===this)return;window.setTimeout(function(){this.focus();},0);e.preventDefault();e.stopPropagation();},didButtonBarMutate_:function(e){var hasButtons=this.buttons.children.length>0;if(hasButtons){Polymer.dom(this.shadow_).querySelector('button-bar').style.display=undefined;}else{Polymer.dom(this.shadow_).querySelector('button-bar').style.display='none';}},onKeyDown_:function(e){if(e.keyCode===9&&e.shiftKey&&e.target===this){e.preventDefault();return;}if(e.keyCode!==27)return;this.onClose_(e);},onClick_:function(e){e.stopPropagation();},onDocumentClick_:function(e){if(!this.userCanClose_)return;this.onClose_(e);}};Overlay.showError=function(msg,opt_err){var o=new Overlay();o.title='Error';Polymer.dom(o).textContent=msg;if(opt_err){var e=tr.b.normalizeException(opt_err);var stackDiv=document.createElement('pre');Polymer.dom(stackDiv).textContent=e.stack;stackDiv.style.paddingLeft='8px';stackDiv.style.margin=0;Polymer.dom(o).appendChild(stackDiv);}var b=document.createElement('button');Polymer.dom(b).textContent='OK';b.addEventListener('click',function(){o.visible=false;});Polymer.dom(o.buttons).appendChild(b);o.visible=true;return o;};return{Overlay:Overlay};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/event.js":38,"../../base/utils.js":64,"./ui.js":180,"./utils.js":181}],180:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/event.js":39,"../../base/utils.js":65,"./ui.js":181,"./utils.js":182}],181:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/base.js");'use strict';global.tr.exportTo('tr.ui.b',function(){function decorate(source,constr){var elements;if(typeof source=='string')elements=Polymer.dom(tr.doc).querySelectorAll(source);else elements=[source];for(var i=0,el;el=elements[i];i++){if(!(el instanceof constr))constr.decorate(el);}}function define(className,opt_parentConstructor,opt_tagNS){if(typeof className=='function'){throw new Error('Passing functions as className is deprecated. Please '+'use (className, opt_parentConstructor) to subclass');}var className=className.toLowerCase();if(opt_parentConstructor&&!opt_parentConstructor.tagName)throw new Error('opt_parentConstructor was not '+'created by tr.ui.b.define');var tagName=className;var tagNS=undefined;if(opt_parentConstructor){if(opt_tagNS)throw new Error('Must not specify tagNS if parentConstructor is given');var parent=opt_parentConstructor;while(parent&&parent.tagName){tagName=parent.tagName;tagNS=parent.tagNS;parent=parent.parentConstructor;}}else{tagNS=opt_tagNS;}function f(){if(opt_parentConstructor&&f.prototype.__proto__!=opt_parentConstructor.prototype){throw new Error(className+' prototye\'s __proto__ field is messed up. '+'It MUST be the prototype of '+opt_parentConstructor.tagName);}var el;if(tagNS===undefined)el=tr.doc.createElement(tagName);else el=tr.doc.createElementNS(tagNS,tagName);f.decorate.call(this,el,arguments);return el;}f.decorate=function(el){el.__proto__=f.prototype;el.decorate.apply(el,arguments[1]);el.constructor=f;};f.className=className;f.tagName=tagName;f.tagNS=tagNS;f.parentConstructor=opt_parentConstructor?opt_parentConstructor:undefined;f.toString=function(){if(!f.parentConstructor)return f.tagName;return f.parentConstructor.toString()+'::'+f.className;};return f;}function elementIsChildOf(el,potentialParent){if(el==potentialParent)return false;var cur=el;while(Polymer.dom(cur).parentNode){if(cur==potentialParent)return true;cur=Polymer.dom(cur).parentNode;}return false;};return{decorate:decorate,define:define,elementIsChildOf:elementIsChildOf};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/base.js":33}],181:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/base.js":34}],182:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/base.js");require("../../base/rect.js");'use strict';global.tr.exportTo('tr.ui.b',function(){function instantiateTemplate(selector,doc){doc=doc||document;var el=Polymer.dom(doc).querySelector(selector);if(!el)throw new Error('Element not found');return doc.importNode(el.content,true);}function windowRectForElement(element){var position=[element.offsetLeft,element.offsetTop];var size=[element.offsetWidth,element.offsetHeight];var node=element.offsetParent;while(node){position[0]+=node.offsetLeft;position[1]+=node.offsetTop;node=node.offsetParent;}return tr.b.Rect.fromXYWH(position[0],position[1],size[0],size[1]);}function scrollIntoViewIfNeeded(el){var pr=el.parentElement.getBoundingClientRect();var cr=el.getBoundingClientRect();if(cr.top<pr.top){el.scrollIntoView(true);}else if(cr.bottom>pr.bottom){el.scrollIntoView(false);}}function extractUrlString(url){var extracted=url.replace(/url\((.*)\)/,'$1');extracted=extracted.replace(/\"(.*)\"/,'$1');return extracted;}function toThreeDigitLocaleString(value){return value.toLocaleString(undefined,{minimumFractionDigits:3,maximumFractionDigits:3});}function isUnknownElementName(name){return document.createElement(name)instanceof HTMLUnknownElement;}return{isUnknownElementName:isUnknownElementName,toThreeDigitLocaleString:toThreeDigitLocaleString,instantiateTemplate:instantiateTemplate,windowRectForElement:windowRectForElement,scrollIntoViewIfNeeded:scrollIntoViewIfNeeded,extractUrlString:extractUrlString};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/base.js":33,"../../base/rect.js":54}],182:[function(require,module,exports){
-(function(global){
-"use strict";var _slicedToArray=function(){function sliceIterator(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n=(_s=_i.next()).done);_n=true){_arr.push(_s.value);if(i&&_arr.length===i)break;}}catch(err){_d=true;_e=err;}finally{try{if(!_n&&_i["return"])_i["return"]();}finally{if(_d)throw _e;}}return _arr;}return function(arr,i){if(Array.isArray(arr)){return arr;}else if(Symbol.iterator in Object(arr)){return sliceIterator(arr,i);}else{throw new TypeError("Invalid attempt to destructure non-iterable instance");}};}();require("./related_value_map.js");'use strict';global.tr.exportTo('tr.v.d',function(){class Breakdown extends tr.v.d.Diagnostic{constructor(){super();this.values_=new Map();this.colorScheme=undefined;}set(name,value){if(typeof name!=='string'||typeof value!=='number'){throw new Error('Breakdown maps from strings to numbers');}this.values_.set(name,value);}get(name){return this.values_.get(name)||0;}*[Symbol.iterator](){for(var pair of this.values_)yield pair;}asDictInto_(d){d.values={};for(var _ref of this){var _ref2=_slicedToArray(_ref,2);var name=_ref2[0];var value=_ref2[1];d.values[name]=value;}if(this.colorScheme)d.colorScheme=this.colorScheme;}static fromDict(d){var breakdown=new Breakdown();tr.b.iterItems(d.values,(name,value)=>breakdown.set(name,value));if(d.colorScheme)breakdown.colorScheme=d.colorScheme;return breakdown;}}tr.v.d.Diagnostic.register(Breakdown,{elementName:'tr-v-ui-breakdown-span'});return{Breakdown:Breakdown};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./related_value_map.js":190}],183:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/base.js":34,"../../base/rect.js":55}],183:[function(require,module,exports){
+(function (global){
+"use strict";require("./related_value_map.js");'use strict';global.tr.exportTo('tr.v.d',function(){class Breakdown extends tr.v.d.Diagnostic{constructor(){super();this.values_=new Map();this.colorScheme=undefined;}set(name,value){if(typeof name!=='string'||typeof value!=='number'){throw new Error('Breakdown maps from strings to numbers');}this.values_.set(name,value);}get(name){return this.values_.get(name)||0;}*[Symbol.iterator](){for(var pair of this.values_)yield pair;}asDictInto_(d){d.values={};for(var[name,value]of this)d.values[name]=value;if(this.colorScheme)d.colorScheme=this.colorScheme;}static fromDict(d){var breakdown=new Breakdown();tr.b.iterItems(d.values,(name,value)=>breakdown.set(name,value));if(d.colorScheme)breakdown.colorScheme=d.colorScheme;return breakdown;}}tr.v.d.Diagnostic.register(Breakdown,{elementName:'tr-v-ui-breakdown-span'});return{Breakdown:Breakdown};});
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./related_value_map.js":191}],184:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/extension_registry.js");'use strict';global.tr.exportTo('tr.v.d',function(){class Diagnostic{asDict(){var result={type:this.constructor.name};this.asDictInto_(result);return result;}asDictInto_(d){throw new Error('Abstract virtual method');}static fromDict(d){var typeInfo=Diagnostic.findTypeInfoWithName(d.type);if(!typeInfo)throw new Error('Unrecognized diagnostic type: '+d.type);return typeInfo.constructor.fromDict(d);}}var options=new tr.b.ExtensionRegistryOptions(tr.b.BASIC_REGISTRY_MODE);options.defaultMetadata={};options.mandatoryBaseClass=Diagnostic;tr.b.decorateExtensionRegistry(Diagnostic,options);Diagnostic.addEventListener('will-register',function(e){var constructor=e.typeInfo.constructor;if(!(constructor.fromDict instanceof Function)||constructor.fromDict===Diagnostic.fromDict||constructor.fromDict.length!==1){throw new Error('Diagnostics must define fromDict(d)');}});return{Diagnostic:Diagnostic};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/extension_registry.js":40}],184:[function(require,module,exports){
-(function(global){
-"use strict";var _slicedToArray=function(){function sliceIterator(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n=(_s=_i.next()).done);_n=true){_arr.push(_s.value);if(i&&_arr.length===i)break;}}catch(err){_d=true;_e=err;}finally{try{if(!_n&&_i["return"])_i["return"]();}finally{if(_d)throw _e;}}return _arr;}return function(arr,i){if(Array.isArray(arr)){return arr;}else if(Symbol.iterator in Object(arr)){return sliceIterator(arr,i);}else{throw new TypeError("Invalid attempt to destructure non-iterable instance");}};}();require("./breakdown.js");require("./generic.js");require("./iteration_info.js");require("./related_event_set.js");require("./related_histogram_breakdown.js");require("./related_value_map.js");require("./related_value_set.js");require("./scalar.js");'use strict';global.tr.exportTo('tr.v.d',function(){class DiagnosticMap extends Map{set(name,diagnostic){if(typeof name!=='string')throw new Error('name must be string, not '+name);if(!(diagnostic instanceof tr.v.d.Diagnostic))throw new Error('Must be instanceof Diagnostic: '+diagnostic);Map.prototype.set.call(this,name,diagnostic);}addDicts(dict){tr.b.iterItems(dict,function(name,diagnosticDict){this.set(name,tr.v.d.Diagnostic.fromDict(diagnosticDict));},this);}asDict(){var dict={};for(var _ref of this){var _ref2=_slicedToArray(_ref,2);var name=_ref2[0];var diagnostic=_ref2[1];dict[name]=diagnostic.asDict();}return dict;}static fromDict(d){var diagnostics=new DiagnosticMap();diagnostics.addDicts(d);return diagnostics;}static fromObject(obj){var diagnostics=new DiagnosticMap();tr.b.iterItems(obj,function(name,diagnostic){diagnostics.set(name,diagnostic);});return diagnostics;}}return{DiagnosticMap:DiagnosticMap};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./breakdown.js":182,"./generic.js":186,"./iteration_info.js":187,"./related_event_set.js":188,"./related_histogram_breakdown.js":189,"./related_value_map.js":190,"./related_value_set.js":191,"./scalar.js":192}],185:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/extension_registry.js":41}],185:[function(require,module,exports){
+(function (global){
+"use strict";require("./breakdown.js");require("./generic.js");require("./iteration_info.js");require("./related_event_set.js");require("./related_histogram_breakdown.js");require("./related_value_map.js");require("./related_value_set.js");require("./scalar.js");'use strict';global.tr.exportTo('tr.v.d',function(){class DiagnosticMap extends Map{set(name,diagnostic){if(typeof name!=='string')throw new Error('name must be string, not '+name);if(!(diagnostic instanceof tr.v.d.Diagnostic))throw new Error('Must be instanceof Diagnostic: '+diagnostic);Map.prototype.set.call(this,name,diagnostic);}addDicts(dict){tr.b.iterItems(dict,function(name,diagnosticDict){this.set(name,tr.v.d.Diagnostic.fromDict(diagnosticDict));},this);}asDict(){var dict={};for(var[name,diagnostic]of this){dict[name]=diagnostic.asDict();}return dict;}static fromDict(d){var diagnostics=new DiagnosticMap();diagnostics.addDicts(d);return diagnostics;}static fromObject(obj){var diagnostics=new DiagnosticMap();tr.b.iterItems(obj,function(name,diagnostic){diagnostics.set(name,diagnostic);});return diagnostics;}}return{DiagnosticMap:DiagnosticMap};});
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./breakdown.js":183,"./generic.js":187,"./iteration_info.js":188,"./related_event_set.js":189,"./related_histogram_breakdown.js":190,"./related_value_map.js":191,"./related_value_set.js":192,"./scalar.js":193}],186:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/guid.js");'use strict';global.tr.exportTo('tr.v.d',function(){class EventRef{constructor(event){this.stableId=event.stableId;this.title=event.title;this.start=event.start;this.duration=event.duration;this.end=this.start+this.duration;this.guid=tr.b.GUID.allocateSimple();}}return{EventRef:EventRef};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/guid.js":44}],186:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/guid.js":45}],187:[function(require,module,exports){
+(function (global){
 "use strict";require("./diagnostic.js");'use strict';global.tr.exportTo('tr.v.d',function(){class Generic extends tr.v.d.Diagnostic{constructor(value){super();this.value=value;}asDictInto_(d){d.value=this.value;}static fromDict(d){return new Generic(d.value);}}tr.v.d.Diagnostic.register(Generic,{elementName:'tr-v-ui-generic-diagnostic-span'});return{Generic:Generic};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./diagnostic.js":183}],187:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./diagnostic.js":184}],188:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/utils.js");require("./diagnostic.js");'use strict';global.tr.exportTo('tr.v.d',function(){class IterationInfo extends tr.v.d.Diagnostic{constructor(opt_info){super();this.benchmarkName_='';this.benchmarkStart_=undefined;this.label_='';this.osVersion_='';this.productVersion_='';this.storyDisplayName_='';this.storyGroupingKeys_={};this.storyRepeatCounter_=undefined;this.storyUrl_='';this.storysetRepeatCounter_=undefined;if(opt_info)this.addInfo(opt_info);}addInfo(info){if(info.benchmarkName)this.benchmarkName_=info.benchmarkName;if(info.benchmarkStartMs)this.benchmarkStart_=new Date(info.benchmarkStartMs);if(info.label)this.label_=info.label;if(info.storyDisplayName)this.storyDisplayName_=info.storyDisplayName;if(info.storyGroupingKeys)this.storyGroupingKeys_=info.storyGroupingKeys;if(info.storyRepeatCounter!==undefined)this.storyRepeatCounter_=info.storyRepeatCounter;if(info.storyUrl)this.storyUrl_=info.storyUrl;if(info.storysetRepeatCounter!==undefined)this.storysetRepeatCounter_=info.storysetRepeatCounter;if(info['os-version'])this.osVersion_=info['os-version'];if(info['product-version'])this.productVersion_=info['product-version'];}addToValue(value){value.diagnostics.set(IterationInfo.NAME,this);}static getFromValue(value){return value.diagnostics.get(IterationInfo.NAME);}asDictInto_(d){d.benchmarkName=this.benchmarkName;if(this.benchmarkStart)d.benchmarkStartMs=this.benchmarkStart.getTime();d.label=this.label;d.storyDisplayName=this.storyDisplayName;d.storyGroupingKeys=this.storyGroupingKeys;d.storyRepeatCounter=this.storyRepeatCounter;d.storyUrl=this.storyUrl;d.storysetRepeatCounter=this.storysetRepeatCounter;d['os-version']=this.osVersion;d['product-version']=this.productVersion;}static fromDict(d){var info=new IterationInfo();info.addInfo(d);return info;}get displayLabel(){if(this.label)return this.label;return this.benchmarkName+' '+this.benchmarkStartString;}get osVersion(){return this.osVersion_;}get productVersion(){return this.productVersion_;}get benchmarkName(){return this.benchmarkName_;}get label(){return this.label_;}get storyGroupingKeys(){return this.storyGroupingKeys_;}get storyDisplayName(){return this.storyDisplayName_;}get storyUrl(){return this.storyUrl_;}get storyRepeatCounter(){return this.storyRepeatCounter_;}get storyRepeatCounterLabel(){return'story repeat '+this.storyRepeatCounter;}get storysetRepeatCounter(){return this.storysetRepeatCounter_;}get storysetRepeatCounterLabel(){return'storyset repeat '+this.storysetRepeatCounter;}get benchmarkStart(){return this.benchmarkStart_;}get benchmarkStartString(){if(this.benchmarkStart_===undefined)return'';return tr.b.formatDate(this.benchmarkStart);}static getField(value,fieldName,defaultValue){var iteration=tr.v.d.IterationInfo.getFromValue(value);if(!(iteration instanceof tr.v.d.IterationInfo)||!iteration[fieldName]){return defaultValue;}return iteration[fieldName];}static getStoryGroupingKeyLabel(value,storyGroupingKey){var iteration=tr.v.d.IterationInfo.getFromValue(value);if(!(iteration instanceof tr.v.d.IterationInfo))return storyGroupingKey+': undefined';return storyGroupingKey+': '+iteration.storyGroupingKeys[storyGroupingKey];}}IterationInfo.NAME='iteration';tr.v.d.Diagnostic.register(IterationInfo,{elementName:'tr-v-ui-iteration-info-span'});return{IterationInfo:IterationInfo};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/utils.js":64,"./diagnostic.js":183}],188:[function(require,module,exports){
-(function(global){
-"use strict";var _slicedToArray=function(){function sliceIterator(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n=(_s=_i.next()).done);_n=true){_arr.push(_s.value);if(i&&_arr.length===i)break;}}catch(err){_d=true;_e=err;}finally{try{if(!_n&&_i["return"])_i["return"]();}finally{if(_d)throw _e;}}return _arr;}return function(arr,i){if(Array.isArray(arr)){return arr;}else if(Symbol.iterator in Object(arr)){return sliceIterator(arr,i);}else{throw new TypeError("Invalid attempt to destructure non-iterable instance");}};}();require("../../model/event_set.js");require("./diagnostic.js");require("./event_ref.js");'use strict';global.tr.exportTo('tr.v.d',function(){class RelatedEventSet extends tr.v.d.Diagnostic{constructor(opt_events){super();this.eventsByStableId_=new Map();if(opt_events){if(opt_events instanceof tr.model.EventSet||opt_events instanceof Array){for(var event of opt_events)this.add(event);}else{this.add(opt_events);}}}add(event){this.eventsByStableId_.set(event.stableId,event);}has(event){return this.eventsByStableId_.has(event.stableId);}get length(){return this.eventsByStableId_.size;}*[Symbol.iterator](){for(var _ref of this.eventsByStableId_){var _ref2=_slicedToArray(_ref,2);var stableId=_ref2[0];var event=_ref2[1];yield event;}}resolve(model,opt_required){for(var _ref3 of this.eventsByStableId_){var _ref4=_slicedToArray(_ref3,2);var stableId=_ref4[0];var event=_ref4[1];if(!(event instanceof tr.v.d.EventRef))continue;event=model.getEventByStableId(stableId);if(event instanceof tr.model.Event)this.eventsByStableId_.set(stableId,event);else if(opt_required)throw new Error('Unable to find Event '+stableId);}}asDictInto_(d){d.events=[];for(var event of this){d.events.push({stableId:event.stableId,title:event.title,start:event.start,duration:event.duration});}}static fromDict(d){return new RelatedEventSet(d.events.map(event=>new tr.v.d.EventRef(event)));}}tr.v.d.Diagnostic.register(RelatedEventSet,{elementName:'tr-v-ui-related-event-set-span'});return{RelatedEventSet:RelatedEventSet};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../model/event_set.js":125,"./diagnostic.js":183,"./event_ref.js":185}],189:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/utils.js":65,"./diagnostic.js":184}],189:[function(require,module,exports){
+(function (global){
+"use strict";require("../../model/event_set.js");require("./diagnostic.js");require("./event_ref.js");'use strict';global.tr.exportTo('tr.v.d',function(){class RelatedEventSet extends tr.v.d.Diagnostic{constructor(opt_events){super();this.eventsByStableId_=new Map();if(opt_events){if(opt_events instanceof tr.model.EventSet||opt_events instanceof Array){for(var event of opt_events)this.add(event);}else{this.add(opt_events);}}}add(event){this.eventsByStableId_.set(event.stableId,event);}has(event){return this.eventsByStableId_.has(event.stableId);}get length(){return this.eventsByStableId_.size;}*[Symbol.iterator](){for(var[stableId,event]of this.eventsByStableId_)yield event;}resolve(model,opt_required){for(var[stableId,event]of this.eventsByStableId_){if(!(event instanceof tr.v.d.EventRef))continue;event=model.getEventByStableId(stableId);if(event instanceof tr.model.Event)this.eventsByStableId_.set(stableId,event);else if(opt_required)throw new Error('Unable to find Event '+stableId);}}asDictInto_(d){d.events=[];for(var event of this){d.events.push({stableId:event.stableId,title:event.title,start:event.start,duration:event.duration});}}static fromDict(d){return new RelatedEventSet(d.events.map(event=>new tr.v.d.EventRef(event)));}}tr.v.d.Diagnostic.register(RelatedEventSet,{elementName:'tr-v-ui-related-event-set-span'});return{RelatedEventSet:RelatedEventSet};});
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../model/event_set.js":126,"./diagnostic.js":184,"./event_ref.js":186}],190:[function(require,module,exports){
+(function (global){
 "use strict";require("./related_value_map.js");'use strict';global.tr.exportTo('tr.v.d',function(){var COLOR_SCHEME_CHROME_USER_FRIENDLY_CATEGORY_DRIVER='ChromeUserFriendlyCategory';class RelatedHistogramBreakdown extends tr.v.d.RelatedValueMap{constructor(){super();this.colorScheme=undefined;}set(name,value){if(!(value instanceof tr.v.d.ValueRef)){if(!(value instanceof tr.v.Histogram)){throw new Error('RelatedHistogramBreakdown can only contain Histograms');}if(value.name.indexOf(name)!==value.name.length-name.length){throw new Error('RelatedHistogramBreakdown name must be a suffix of value.name');}if(this.length>0&&value.unit!==tr.b.getFirstElement(this)[1].unit){throw new Error('Units mismatch',tr.b.getFirstElement(this)[1].unit,value.unit);}}tr.v.d.RelatedValueMap.prototype.set.call(this,name,value);}asDictInto_(d){tr.v.d.RelatedValueMap.prototype.asDictInto_.call(this,d);if(this.colorScheme)d.colorScheme=this.colorScheme;}static fromDict(d){var diagnostic=new RelatedHistogramBreakdown();tr.b.iterItems(d.values,function(name,guid){diagnostic.set(name,new tr.v.d.ValueRef(guid));});if(d.colorScheme)diagnostic.colorScheme=d.colorScheme;return diagnostic;}static buildFromEvents(values,namePrefix,events,categoryForEvent,unit,opt_sampleForEvent,opt_binBoundaries,opt_this){var sampleForEvent=opt_sampleForEvent||(event=>event.cpuSelfTime);var diagnostic=new RelatedHistogramBreakdown();for(var event of events){var sample=sampleForEvent.call(opt_this,event);if(sample===undefined)continue;var eventCategory=categoryForEvent.call(opt_this,event);var value=diagnostic.get(eventCategory);if(value===undefined){value=new tr.v.Histogram(namePrefix+eventCategory,unit,opt_binBoundaries);values.addHistogram(value);diagnostic.set(eventCategory,value);}value.addSample(sample,{relatedEvents:new tr.v.d.RelatedEventSet([event])});}return diagnostic;}}tr.v.d.Diagnostic.register(RelatedHistogramBreakdown,{elementName:'tr-v-ui-breakdown-span'});return{COLOR_SCHEME_CHROME_USER_FRIENDLY_CATEGORY_DRIVER:COLOR_SCHEME_CHROME_USER_FRIENDLY_CATEGORY_DRIVER,RelatedHistogramBreakdown:RelatedHistogramBreakdown};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./related_value_map.js":190}],190:[function(require,module,exports){
-(function(global){
-"use strict";var _slicedToArray=function(){function sliceIterator(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n=(_s=_i.next()).done);_n=true){_arr.push(_s.value);if(i&&_arr.length===i)break;}}catch(err){_d=true;_e=err;}finally{try{if(!_n&&_i["return"])_i["return"]();}finally{if(_d)throw _e;}}return _arr;}return function(arr,i){if(Array.isArray(arr)){return arr;}else if(Symbol.iterator in Object(arr)){return sliceIterator(arr,i);}else{throw new TypeError("Invalid attempt to destructure non-iterable instance");}};}();require("../../base/iteration_helpers.js");require("./diagnostic.js");require("./value_ref.js");'use strict';global.tr.exportTo('tr.v.d',function(){class RelatedValueMap extends tr.v.d.Diagnostic{constructor(){super();this.valuesByName_=new Map();}get(name){return this.valuesByName_.get(name);}set(name,value){if(!(value instanceof tr.v.Histogram)&&!(value instanceof tr.v.d.ValueRef))throw new Error('Must be instanceof Histogram or ValueRef: '+value);this.valuesByName_.set(name,value);}add(value){this.set(value.name,value);}get length(){return this.valuesByName_.size;}*[Symbol.iterator](){for(var pair of this.valuesByName_)yield pair;}resolve(valueSet,opt_required){for(var _ref of this){var _ref2=_slicedToArray(_ref,2);var name=_ref2[0];var value=_ref2[1];if(!(value instanceof tr.v.d.ValueRef))continue;var guid=value.guid;value=valueSet.lookup(guid);if(value instanceof tr.v.Histogram)this.valuesByName_.set(name,value);else if(opt_required)throw new Error('Unable to find Histogram '+guid);}}asDictInto_(d){d.values={};for(var _ref3 of this){var _ref4=_slicedToArray(_ref3,2);var name=_ref4[0];var value=_ref4[1];d.values[name]=value.guid;}}static fromDict(d){var map=new RelatedValueMap();tr.b.iterItems(d.values,function(name,guid){map.set(name,new tr.v.d.ValueRef(guid));});return map;}}tr.v.d.Diagnostic.register(RelatedValueMap,{elementName:'tr-v-ui-related-value-map-span'});return{RelatedValueMap:RelatedValueMap};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/iteration_helpers.js":46,"./diagnostic.js":183,"./value_ref.js":193}],191:[function(require,module,exports){
-(function(global){
-"use strict";var _slicedToArray=function(){function sliceIterator(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n=(_s=_i.next()).done);_n=true){_arr.push(_s.value);if(i&&_arr.length===i)break;}}catch(err){_d=true;_e=err;}finally{try{if(!_n&&_i["return"])_i["return"]();}finally{if(_d)throw _e;}}return _arr;}return function(arr,i){if(Array.isArray(arr)){return arr;}else if(Symbol.iterator in Object(arr)){return sliceIterator(arr,i);}else{throw new TypeError("Invalid attempt to destructure non-iterable instance");}};}();require("../../base/iteration_helpers.js");require("./diagnostic.js");require("./value_ref.js");'use strict';global.tr.exportTo('tr.v.d',function(){class RelatedValueSet extends tr.v.d.Diagnostic{constructor(opt_values){super();this.valuesByGuid_=new Map();if(opt_values)for(var value of opt_values)this.add(value);}add(value){if(!(value instanceof tr.v.Histogram)&&!(value instanceof tr.v.d.ValueRef))throw new Error('Must be instanceof Histogram or ValueRef: '+value);if(this.valuesByGuid_.get(value.guid))throw new Error('Tried to add same value twice');this.valuesByGuid_.set(value.guid,value);}has(value){return this.valuesByGuid_.has(value.guid);}get length(){return this.valuesByGuid_.size;}*[Symbol.iterator](){for(var _ref of this.valuesByGuid_){var _ref2=_slicedToArray(_ref,2);var guid=_ref2[0];var value=_ref2[1];yield value;}}resolve(valueSet,opt_required){for(var _ref3 of this.valuesByGuid_){var _ref4=_slicedToArray(_ref3,2);var guid=_ref4[0];var value=_ref4[1];if(!(value instanceof tr.v.d.ValueRef))continue;value=valueSet.lookup(guid);if(value instanceof tr.v.Histogram)this.valuesByGuid_.set(guid,value);else if(opt_required)throw new Error('Unable to find Histogram '+guid);}}asDictInto_(d){d.guids=[];for(var value of this)d.guids.push(value.guid);}static fromDict(d){return new RelatedValueSet(d.guids.map(guid=>new tr.v.d.ValueRef(guid)));}}tr.v.d.Diagnostic.register(RelatedValueSet,{elementName:'tr-v-ui-related-value-set-span'});return{RelatedValueSet:RelatedValueSet};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/iteration_helpers.js":46,"./diagnostic.js":183,"./value_ref.js":193}],192:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./related_value_map.js":191}],191:[function(require,module,exports){
+(function (global){
+"use strict";require("../../base/iteration_helpers.js");require("./diagnostic.js");require("./value_ref.js");'use strict';global.tr.exportTo('tr.v.d',function(){class RelatedValueMap extends tr.v.d.Diagnostic{constructor(){super();this.valuesByName_=new Map();}get(name){return this.valuesByName_.get(name);}set(name,value){if(!(value instanceof tr.v.Histogram)&&!(value instanceof tr.v.d.ValueRef))throw new Error('Must be instanceof Histogram or ValueRef: '+value);this.valuesByName_.set(name,value);}add(value){this.set(value.name,value);}get length(){return this.valuesByName_.size;}*[Symbol.iterator](){for(var pair of this.valuesByName_)yield pair;}resolve(valueSet,opt_required){for(var[name,value]of this){if(!(value instanceof tr.v.d.ValueRef))continue;var guid=value.guid;value=valueSet.lookup(guid);if(value instanceof tr.v.Histogram)this.valuesByName_.set(name,value);else if(opt_required)throw new Error('Unable to find Histogram '+guid);}}asDictInto_(d){d.values={};for(var[name,value]of this)d.values[name]=value.guid;}static fromDict(d){var map=new RelatedValueMap();tr.b.iterItems(d.values,function(name,guid){map.set(name,new tr.v.d.ValueRef(guid));});return map;}}tr.v.d.Diagnostic.register(RelatedValueMap,{elementName:'tr-v-ui-related-value-map-span'});return{RelatedValueMap:RelatedValueMap};});
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/iteration_helpers.js":47,"./diagnostic.js":184,"./value_ref.js":194}],192:[function(require,module,exports){
+(function (global){
+"use strict";require("../../base/iteration_helpers.js");require("./diagnostic.js");require("./value_ref.js");'use strict';global.tr.exportTo('tr.v.d',function(){class RelatedValueSet extends tr.v.d.Diagnostic{constructor(opt_values){super();this.valuesByGuid_=new Map();if(opt_values)for(var value of opt_values)this.add(value);}add(value){if(!(value instanceof tr.v.Histogram)&&!(value instanceof tr.v.d.ValueRef))throw new Error('Must be instanceof Histogram or ValueRef: '+value);if(this.valuesByGuid_.get(value.guid))throw new Error('Tried to add same value twice');this.valuesByGuid_.set(value.guid,value);}has(value){return this.valuesByGuid_.has(value.guid);}get length(){return this.valuesByGuid_.size;}*[Symbol.iterator](){for(var[guid,value]of this.valuesByGuid_)yield value;}resolve(valueSet,opt_required){for(var[guid,value]of this.valuesByGuid_){if(!(value instanceof tr.v.d.ValueRef))continue;value=valueSet.lookup(guid);if(value instanceof tr.v.Histogram)this.valuesByGuid_.set(guid,value);else if(opt_required)throw new Error('Unable to find Histogram '+guid);}}asDictInto_(d){d.guids=[];for(var value of this)d.guids.push(value.guid);}static fromDict(d){return new RelatedValueSet(d.guids.map(guid=>new tr.v.d.ValueRef(guid)));}}tr.v.d.Diagnostic.register(RelatedValueSet,{elementName:'tr-v-ui-related-value-set-span'});return{RelatedValueSet:RelatedValueSet};});
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/iteration_helpers.js":47,"./diagnostic.js":184,"./value_ref.js":194}],193:[function(require,module,exports){
+(function (global){
 "use strict";require("./diagnostic.js");require("../numeric.js");'use strict';global.tr.exportTo('tr.v.d',function(){class Scalar extends tr.v.d.Diagnostic{constructor(value){super();if(!(value instanceof tr.v.ScalarNumeric))throw new Error("expected ScalarNumeric");this.value=value;}asDictInto_(d){d.value=this.value.asDict();}static fromDict(d){return new Scalar(tr.v.ScalarNumeric.fromDict(d.value));}}tr.v.d.Diagnostic.register(Scalar,{elementName:'tr-v-ui-scalar-diagnostic-span'});return{Scalar:Scalar};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../numeric.js":195,"./diagnostic.js":183}],193:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../numeric.js":196,"./diagnostic.js":184}],194:[function(require,module,exports){
+(function (global){
 "use strict";require("../../base/base.js");'use strict';global.tr.exportTo('tr.v.d',function(){function ValueRef(guid){this.guid=guid;}return{ValueRef:ValueRef};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../base/base.js":33}],194:[function(require,module,exports){
-(function(global){
-"use strict";var _slicedToArray=function(){function sliceIterator(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n=(_s=_i.next()).done);_n=true){_arr.push(_s.value);if(i&&_arr.length===i)break;}}catch(err){_d=true;_e=err;}finally{try{if(!_n&&_i["return"])_i["return"]();}finally{if(_d)throw _e;}}return _arr;}return function(arr,i){if(Array.isArray(arr)){return arr;}else if(Symbol.iterator in Object(arr)){return sliceIterator(arr,i);}else{throw new TypeError("Invalid attempt to destructure non-iterable instance");}};}();require("../base/iteration_helpers.js");require("../base/range.js");require("../base/running_statistics.js");require("../base/sorted_array_utils.js");require("../base/statistics.js");require("../base/unit.js");require("./diagnostics/diagnostic_map.js");require("./numeric.js");'use strict';global.tr.exportTo('tr.v',function(){var MAX_DIAGNOSTIC_MAPS=16;var DEFAULT_BOUNDARIES_FOR_UNIT=new Map();class HistogramBin{constructor(range){this.range=range;this.count=0;this.diagnosticMaps=[];}addSample(value){this.count+=1;}addDiagnosticMap(diagnostics){tr.b.Statistics.uniformlySampleStream(this.diagnosticMaps,this.count,diagnostics,MAX_DIAGNOSTIC_MAPS);}addBin(other){if(!this.range.equals(other.range))throw new Error('Merging incompatible Histogram bins.');tr.b.Statistics.mergeSampledStreams(this.diagnosticMaps,this.count,other.diagnosticMaps,other.count,MAX_DIAGNOSTIC_MAPS);this.count+=other.count;}fromDict(dict){this.count=dict[0];if(dict.length>1){for(var map of dict[1]){this.diagnosticMaps.push(tr.v.d.DiagnosticMap.fromDict(map));}}}asDict(){if(!this.diagnosticMaps.length){return[this.count];}return[this.count,this.diagnosticMaps.map(d=>d.asDict())];}}var DEFAULT_SUMMARY_OPTIONS=new Map([['avg',true],['geometricMean',false],['std',true],['count',true],['sum',true],['min',true],['max',true],['nans',false]]);class Histogram{constructor(name,unit,opt_binBoundaries){var binBoundaries=opt_binBoundaries;if(!binBoundaries){var baseUnit=unit.baseUnit?unit.baseUnit:unit;binBoundaries=DEFAULT_BOUNDARIES_FOR_UNIT.get(baseUnit.unitName);}this.guid_=undefined;this.binBoundariesDict_=binBoundaries.asDict();this.centralBins=[];this.description='';this.diagnostics=new tr.v.d.DiagnosticMap();this.maxCount_=0;this.name_=name;this.nanDiagnosticMaps=[];this.numNans=0;this.running=new tr.b.RunningStatistics();this.sampleValues_=[];this.shortName=undefined;this.summaryOptions=new Map(DEFAULT_SUMMARY_OPTIONS);this.summaryOptions.set('percentile',[]);this.unit=unit;this.underflowBin=new HistogramBin(tr.b.Range.fromExplicitRange(-Number.MAX_VALUE,binBoundaries.range.min));this.overflowBin=new HistogramBin(tr.b.Range.fromExplicitRange(binBoundaries.range.max,Number.MAX_VALUE));for(var range of binBoundaries.binRanges()){this.centralBins.push(new HistogramBin(range));}this.allBins=[this.underflowBin];for(var bin of this.centralBins)this.allBins.push(bin);this.allBins.push(this.overflowBin);this.maxNumSampleValues_=this.defaultMaxNumSampleValues_;}get maxNumSampleValues(){return this.maxNumSampleValues_;}set maxNumSampleValues(n){this.maxNumSampleValues_=n;tr.b.Statistics.uniformlySampleArray(this.sampleValues_,this.maxNumSampleValues_);}get name(){return this.name_;}get guid(){if(this.guid_===undefined)this.guid_=tr.b.GUID.allocateUUID4();return this.guid_;}set guid(guid){if(this.guid_!==undefined)throw new Error('Cannot reset guid');this.guid_=guid;}static fromDict(dict){var hist=new Histogram(dict.name,tr.b.Unit.fromJSON(dict.unit),HistogramBinBoundaries.fromDict(dict.binBoundaries));hist.guid=dict.guid;if(dict.shortName){hist.shortName=dict.shortName;}if(dict.description){hist.description=dict.description;}if(dict.diagnostics){hist.diagnostics.addDicts(dict.diagnostics);}if(dict.underflowBin){hist.underflowBin.fromDict(dict.underflowBin);}if(dict.overflowBin){hist.overflowBin.fromDict(dict.overflowBin);}if(dict.centralBins){if(dict.centralBins.length!==undefined){for(var i=0;i<dict.centralBins.length;++i){hist.centralBins[i].fromDict(dict.centralBins[i]);}}else{tr.b.iterItems(dict.centralBins,(i,binDict)=>{hist.centralBins[i].fromDict(binDict);});}}for(var bin of hist.allBins){hist.maxCount_=Math.max(hist.maxCount_,bin.count);}if(dict.running){hist.running=tr.b.RunningStatistics.fromDict(dict.running);}if(dict.summaryOptions){hist.customizeSummaryOptions(dict.summaryOptions);}if(dict.maxNumSampleValues!==undefined){hist.maxNumSampleValues=dict.maxNumSampleValues;}if(dict.sampleValues){hist.sampleValues_=dict.sampleValues;}if(dict.numNans){hist.numNans=dict.numNans;}if(dict.nanDiagnostics){for(var map of dict.nanDiagnostics){hist.nanDiagnosticMaps.push(tr.v.d.DiagnosticMap.fromDict(map));}}return hist;}static buildFromSamples(unit,samples){var boundaries=HistogramBinBoundaries.createFromSamples(samples);var result=new Histogram(unit,boundaries);result.maxNumSampleValues=1000;for(var sample of samples)result.addSample(sample);return result;}get numValues(){return tr.b.Statistics.sum(this.allBins,function(e){return e.count;});}get average(){return this.running.mean;}get standardDeviation(){return this.running.stddev;}get geometricMean(){return this.running.geometricMean;}get sum(){return this.running.sum;}get maxCount(){return this.maxCount_;}getDifferenceSignificance(other,opt_alpha){if(this.unit!==other.unit)throw new Error('Cannot compare Numerics with different units');if(this.unit.improvementDirection===tr.b.ImprovementDirection.DONT_CARE){return tr.b.Statistics.Significance.DONT_CARE;}if(!(other instanceof Histogram))throw new Error('Unable to compute a p-value');var testResult=tr.b.Statistics.mwu(this.sampleValues,other.sampleValues,opt_alpha);return testResult.significance;}getApproximatePercentile(percent){if(!(percent>=0&&percent<=1))throw new Error('percent must be [0,1]');if(this.numValues==0)return 0;var valuesToSkip=Math.floor((this.numValues-1)*percent);for(var i=0;i<this.allBins.length;i++){var bin=this.allBins[i];valuesToSkip-=bin.count;if(valuesToSkip<0){if(bin===this.underflowBin)return bin.range.max;else if(bin===this.overflowBin)return bin.range.min;else return bin.range.center;}}throw new Error('Unreachable');}getBinForValue(value){var binIndex=tr.b.findHighIndexInSortedArray(this.allBins,b=>value<b.range.max?-1:1);return this.allBins[binIndex]||this.overflowBin;}addSample(value,opt_diagnostics){if(opt_diagnostics&&!(opt_diagnostics instanceof tr.v.d.DiagnosticMap))opt_diagnostics=tr.v.d.DiagnosticMap.fromObject(opt_diagnostics);if(typeof value!=='number'||isNaN(value)){this.numNans++;if(opt_diagnostics){tr.b.Statistics.uniformlySampleStream(this.nanDiagnosticMaps,this.numNans,opt_diagnostics,MAX_DIAGNOSTIC_MAPS);}}else{this.running.add(value);var bin=this.getBinForValue(value);bin.addSample(value);if(opt_diagnostics)bin.addDiagnosticMap(opt_diagnostics);if(bin.count>this.maxCount_)this.maxCount_=bin.count;}tr.b.Statistics.uniformlySampleStream(this.sampleValues_,this.numValues+this.numNans,value,this.maxNumSampleValues);}sampleValuesInto(samples){for(var sampleValue of this.sampleValues)samples.push(sampleValue);}canAddHistogram(other){if(this.unit!==other.unit)return false;if(this.allBins.length!==other.allBins.length)return false;for(var i=0;i<this.allBins.length;++i)if(!this.allBins[i].range.equals(other.allBins[i].range))return false;return true;}addHistogram(other){if(!this.canAddHistogram(other)){throw new Error('Merging incompatible Histograms');}tr.b.Statistics.mergeSampledStreams(this.nanDiagnosticMaps,this.numNans,other.nanDiagnosticMaps,other.numNans,MAX_DIAGNOSTIC_MAPS);tr.b.Statistics.mergeSampledStreams(this.sampleValues,this.numValues,other.sampleValues,other.numValues,tr.b.Statistics.mean([this.maxNumSampleValues,other.maxNumSampleValues]));this.numNans+=other.numNans;this.running=this.running.merge(other.running);for(var i=0;i<this.allBins.length;++i){this.allBins[i].addBin(other.allBins[i]);}}customizeSummaryOptions(summaryOptions){tr.b.iterItems(summaryOptions,(key,value)=>this.summaryOptions.set(key,value));}get statisticsScalars(){function statNameToKey(stat){switch(stat){case'std':return'stddev';case'avg':return'mean';}return stat;}function percentToString(percent){if(percent<0||percent>1)throw new Error('Percent must be between 0.0 and 1.0');switch(percent){case 0:return'000';case 1:return'100';}var str=percent.toString();if(str[1]!=='.')throw new Error('Unexpected percent');str=str+'0'.repeat(Math.max(4-str.length,0));if(str.length>4)str=str.slice(0,4)+'_'+str.slice(4);return'0'+str.slice(2);}var results=new Map();for(var _ref of this.summaryOptions){var _ref2=_slicedToArray(_ref,2);var stat=_ref2[0];var option=_ref2[1];if(!option){continue;}if(stat==='percentile'){for(var percent of option){var percentile=this.getApproximatePercentile(percent);results.set('pct_'+percentToString(percent),new tr.v.ScalarNumeric(this.unit,percentile));}}else if(stat==='nans'){results.set('nans',new tr.v.ScalarNumeric(tr.b.Unit.byName.count_smallerIsBetter,this.numNans));}else{var statUnit=stat==='count'?tr.b.Unit.byName.count_smallerIsBetter:this.unit;var key=statNameToKey(stat);var statValue=this.running[key];if(typeof statValue==='number'){results.set(stat,new tr.v.ScalarNumeric(statUnit,statValue));}}}return results;}get sampleValues(){return this.sampleValues_;}clone(){return Histogram.fromDict(this.asDict());}cloneEmpty(){var binBoundaries=HistogramBinBoundaries.fromDict(this.binBoundariesDict_);return new Histogram(this.name,this.unit,binBoundaries);}asDict(){var dict={};dict.binBoundaries=this.binBoundariesDict_;dict.name=this.name;dict.unit=this.unit.asJSON();dict.guid=this.guid;if(this.shortName){dict.shortName=this.shortName;}if(this.description){dict.description=this.description;}if(this.diagnostics.size){dict.diagnostics=this.diagnostics.asDict();}if(this.maxNumSampleValues!==this.defaultMaxNumSampleValues_){dict.maxNumSampleValues=this.maxNumSampleValues;}if(this.numNans){dict.numNans=this.numNans;}if(this.nanDiagnosticMaps.length){dict.nanDiagnostics=this.nanDiagnosticMaps.map(dm=>dm.asDict());}if(this.underflowBin.count){dict.underflowBin=this.underflowBin.asDict();}if(this.overflowBin.count){dict.overflowBin=this.overflowBin.asDict();}if(this.numValues){dict.sampleValues=this.sampleValues.slice();dict.running=this.running.asDict();dict.centralBins=this.centralBinsAsDict_();}var summaryOptions={};var anyOverriddenSummaryOptions=false;for(var _ref3 of this.summaryOptions){var _ref4=_slicedToArray(_ref3,2);var name=_ref4[0];var option=_ref4[1];if(name==='percentile'){if(option.length===0){continue;}option=option.slice();}else if(option===DEFAULT_SUMMARY_OPTIONS.get(name)){continue;}summaryOptions[name]=option;anyOverriddenSummaryOptions=true;}if(anyOverriddenSummaryOptions){dict.summaryOptions=summaryOptions;}return dict;}centralBinsAsDict_(){var numCentralBins=this.centralBins.length;var emptyBins=0;for(var i=0;i<numCentralBins;++i){if(this.centralBins[i].count===0){++emptyBins;}}if(emptyBins===numCentralBins){return undefined;}if(emptyBins>numCentralBins/2){var centralBinsDict={};for(var i=0;i<numCentralBins;++i){var bin=this.centralBins[i];if(bin.count>0){centralBinsDict[i]=bin.asDict();}}return centralBinsDict;}var centralBinsArray=[];for(var i=0;i<numCentralBins;++i){centralBinsArray.push(this.centralBins[i].asDict());}return centralBinsArray;}get defaultMaxNumSampleValues_(){return this.allBins.length*10;}}var HISTOGRAM_BIN_BOUNDARIES_CACHE=new Map();class HistogramBinBoundaries{static createLinear(min,max,numBins){return new HistogramBinBoundaries(min).addLinearBins(max,numBins);}static createExponential(min,max,numBins){return new HistogramBinBoundaries(min).addExponentialBins(max,numBins);}static createWithBoundaries(binBoundaries){var builder=new HistogramBinBoundaries(binBoundaries[0]);for(var boundary of binBoundaries.slice(1))builder.addBinBoundary(boundary);return builder;}static createFromSamples(samples){var range=new tr.b.Range();for(var sample of samples)if(!isNaN(Math.max(sample)))range.addValue(sample);if(range.isEmpty)range.addValue(1);if(range.min===range.max)range.addValue(range.min-1);var numBins=Math.ceil(Math.sqrt(samples.length));var builder=new HistogramBinBoundaries(range.min);builder.addLinearBins(range.max,numBins);return builder;}constructor(minBinBoundary){this.boundaries_=undefined;this.builder_=[minBinBoundary];this.range_=new tr.b.Range();this.range_.addValue(minBinBoundary);}get range(){return this.range_;}asDict(){return this.builder_.slice();}static fromDict(dict){var cacheKey=JSON.stringify(dict);if(HISTOGRAM_BIN_BOUNDARIES_CACHE.has(cacheKey)){return HISTOGRAM_BIN_BOUNDARIES_CACHE.get(cacheKey);}var binBoundaries=new HistogramBinBoundaries(dict[0]);for(var slice of dict.slice(1)){if(!(slice instanceof Array)){binBoundaries.addBinBoundary(slice);continue;}switch(slice[0]){case HistogramBinBoundaries.SLICE_TYPE.LINEAR:binBoundaries.addLinearBins(slice[1],slice[2]);break;case HistogramBinBoundaries.SLICE_TYPE.EXPONENTIAL:binBoundaries.addExponentialBins(slice[1],slice[2]);break;default:throw new Error('Unrecognized HistogramBinBoundaries slice type');}}HISTOGRAM_BIN_BOUNDARIES_CACHE.set(cacheKey,binBoundaries);return binBoundaries;}*binRanges(){if(this.boundaries_===undefined){this.build_();}for(var i=0;i<this.boundaries_.length-1;++i){yield tr.b.Range.fromExplicitRange(this.boundaries_[i],this.boundaries_[i+1]);}}build_(){if(typeof this.builder_[0]!=='number'){throw new Error('Invalid start of builder_');}this.boundaries_=[this.builder_[0]];for(var slice of this.builder_.slice(1)){if(!(slice instanceof Array)){this.boundaries_.push(slice);continue;}var nextMaxBinBoundary=slice[1];var binCount=slice[2];var curMaxBinBoundary=this.boundaries_[this.boundaries_.length-1];switch(slice[0]){case HistogramBinBoundaries.SLICE_TYPE.LINEAR:var binWidth=(nextMaxBinBoundary-curMaxBinBoundary)/binCount;for(var i=1;i<binCount;i++){var boundary=curMaxBinBoundary+i*binWidth;this.boundaries_.push(boundary);}break;case HistogramBinBoundaries.SLICE_TYPE.EXPONENTIAL:var binExponentWidth=Math.log(nextMaxBinBoundary/curMaxBinBoundary)/binCount;for(var i=1;i<binCount;i++){var boundary=curMaxBinBoundary*Math.exp(i*binExponentWidth);this.boundaries_.push(boundary);}break;default:throw new Error('Unrecognized HistogramBinBoundaries slice type');}this.boundaries_.push(nextMaxBinBoundary);}}addBinBoundary(nextMaxBinBoundary){if(nextMaxBinBoundary<=this.range.max){throw new Error('The added max bin boundary must be larger than '+'the current max boundary');}this.boundaries_=undefined;this.builder_.push(nextMaxBinBoundary);this.range.addValue(nextMaxBinBoundary);return this;}addLinearBins(nextMaxBinBoundary,binCount){if(binCount<=0)throw new Error('Bin count must be positive');if(nextMaxBinBoundary<=this.range.max){throw new Error('The new max bin boundary must be greater than '+'the previous max bin boundary');}this.boundaries_=undefined;this.builder_.push([HistogramBinBoundaries.SLICE_TYPE.LINEAR,nextMaxBinBoundary,binCount]);this.range.addValue(nextMaxBinBoundary);return this;}addExponentialBins(nextMaxBinBoundary,binCount){if(binCount<=0){throw new Error('Bin count must be positive');}if(this.range.max<=0){throw new Error('Current max bin boundary must be positive');}if(this.range.max>=nextMaxBinBoundary){throw new Error('The last added max boundary must be greater than '+'the current max boundary boundary');}this.boundaries_=undefined;this.builder_.push([HistogramBinBoundaries.SLICE_TYPE.EXPONENTIAL,nextMaxBinBoundary,binCount]);this.range.addValue(nextMaxBinBoundary);return this;}}HistogramBinBoundaries.SLICE_TYPE={LINEAR:0,EXPONENTIAL:1};DEFAULT_BOUNDARIES_FOR_UNIT.set(tr.b.Unit.byName.timeDurationInMs.unitName,HistogramBinBoundaries.createExponential(1e-3,1e6,1e2));DEFAULT_BOUNDARIES_FOR_UNIT.set(tr.b.Unit.byName.timeStampInMs.unitName,HistogramBinBoundaries.createLinear(0,1e10,1e3));DEFAULT_BOUNDARIES_FOR_UNIT.set(tr.b.Unit.byName.normalizedPercentage.unitName,HistogramBinBoundaries.createLinear(0,1.0,20));DEFAULT_BOUNDARIES_FOR_UNIT.set(tr.b.Unit.byName.sizeInBytes.unitName,HistogramBinBoundaries.createExponential(1,1e12,1e2));DEFAULT_BOUNDARIES_FOR_UNIT.set(tr.b.Unit.byName.energyInJoules.unitName,HistogramBinBoundaries.createExponential(1e-3,1e3,50));DEFAULT_BOUNDARIES_FOR_UNIT.set(tr.b.Unit.byName.powerInWatts.unitName,HistogramBinBoundaries.createExponential(1e-3,1,50));DEFAULT_BOUNDARIES_FOR_UNIT.set(tr.b.Unit.byName.unitlessNumber.unitName,HistogramBinBoundaries.createExponential(1e-3,1e3,50));DEFAULT_BOUNDARIES_FOR_UNIT.set(tr.b.Unit.byName.count.unitName,HistogramBinBoundaries.createExponential(1,1e3,20));return{Histogram:Histogram,HistogramBinBoundaries:HistogramBinBoundaries};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/iteration_helpers.js":46,"../base/range.js":52,"../base/running_statistics.js":55,"../base/sorted_array_utils.js":57,"../base/statistics.js":58,"../base/unit.js":62,"./diagnostics/diagnostic_map.js":184,"./numeric.js":195}],195:[function(require,module,exports){
-(function(global){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../../base/base.js":34}],195:[function(require,module,exports){
+(function (global){
+"use strict";require("../base/iteration_helpers.js");require("../base/range.js");require("../base/running_statistics.js");require("../base/sorted_array_utils.js");require("../base/statistics.js");require("../base/unit.js");require("./diagnostics/diagnostic_map.js");require("./numeric.js");'use strict';global.tr.exportTo('tr.v',function(){var MAX_DIAGNOSTIC_MAPS=16;var DEFAULT_BOUNDARIES_FOR_UNIT=new Map();class HistogramBin{constructor(range){this.range=range;this.count=0;this.diagnosticMaps=[];}addSample(value){this.count+=1;}addDiagnosticMap(diagnostics){tr.b.Statistics.uniformlySampleStream(this.diagnosticMaps,this.count,diagnostics,MAX_DIAGNOSTIC_MAPS);}addBin(other){if(!this.range.equals(other.range))throw new Error('Merging incompatible Histogram bins.');tr.b.Statistics.mergeSampledStreams(this.diagnosticMaps,this.count,other.diagnosticMaps,other.count,MAX_DIAGNOSTIC_MAPS);this.count+=other.count;}fromDict(dict){this.count=dict[0];if(dict.length>1){for(var map of dict[1]){this.diagnosticMaps.push(tr.v.d.DiagnosticMap.fromDict(map));}}}asDict(){if(!this.diagnosticMaps.length){return[this.count];}return[this.count,this.diagnosticMaps.map(d=>d.asDict())];}}var DEFAULT_SUMMARY_OPTIONS=new Map([['avg',true],['geometricMean',false],['std',true],['count',true],['sum',true],['min',true],['max',true],['nans',false]]);class Histogram{constructor(name,unit,opt_binBoundaries){var binBoundaries=opt_binBoundaries;if(!binBoundaries){var baseUnit=unit.baseUnit?unit.baseUnit:unit;binBoundaries=DEFAULT_BOUNDARIES_FOR_UNIT.get(baseUnit.unitName);}this.guid_=undefined;this.binBoundariesDict_=binBoundaries.asDict();this.centralBins=[];this.description='';this.diagnostics=new tr.v.d.DiagnosticMap();this.maxCount_=0;this.name_=name;this.nanDiagnosticMaps=[];this.numNans=0;this.running=new tr.b.RunningStatistics();this.sampleValues_=[];this.shortName=undefined;this.summaryOptions=new Map(DEFAULT_SUMMARY_OPTIONS);this.summaryOptions.set('percentile',[]);this.unit=unit;this.underflowBin=new HistogramBin(tr.b.Range.fromExplicitRange(-Number.MAX_VALUE,binBoundaries.range.min));this.overflowBin=new HistogramBin(tr.b.Range.fromExplicitRange(binBoundaries.range.max,Number.MAX_VALUE));for(var range of binBoundaries.binRanges()){this.centralBins.push(new HistogramBin(range));}this.allBins=[this.underflowBin];for(var bin of this.centralBins)this.allBins.push(bin);this.allBins.push(this.overflowBin);this.maxNumSampleValues_=this.defaultMaxNumSampleValues_;}get maxNumSampleValues(){return this.maxNumSampleValues_;}set maxNumSampleValues(n){this.maxNumSampleValues_=n;tr.b.Statistics.uniformlySampleArray(this.sampleValues_,this.maxNumSampleValues_);}get name(){return this.name_;}get guid(){if(this.guid_===undefined)this.guid_=tr.b.GUID.allocateUUID4();return this.guid_;}set guid(guid){if(this.guid_!==undefined)throw new Error('Cannot reset guid');this.guid_=guid;}static fromDict(dict){var hist=new Histogram(dict.name,tr.b.Unit.fromJSON(dict.unit),HistogramBinBoundaries.fromDict(dict.binBoundaries));hist.guid=dict.guid;if(dict.shortName){hist.shortName=dict.shortName;}if(dict.description){hist.description=dict.description;}if(dict.diagnostics){hist.diagnostics.addDicts(dict.diagnostics);}if(dict.underflowBin){hist.underflowBin.fromDict(dict.underflowBin);}if(dict.overflowBin){hist.overflowBin.fromDict(dict.overflowBin);}if(dict.centralBins){if(dict.centralBins.length!==undefined){for(var i=0;i<dict.centralBins.length;++i){hist.centralBins[i].fromDict(dict.centralBins[i]);}}else{tr.b.iterItems(dict.centralBins,(i,binDict)=>{hist.centralBins[i].fromDict(binDict);});}}for(var bin of hist.allBins){hist.maxCount_=Math.max(hist.maxCount_,bin.count);}if(dict.running){hist.running=tr.b.RunningStatistics.fromDict(dict.running);}if(dict.summaryOptions){hist.customizeSummaryOptions(dict.summaryOptions);}if(dict.maxNumSampleValues!==undefined){hist.maxNumSampleValues=dict.maxNumSampleValues;}if(dict.sampleValues){hist.sampleValues_=dict.sampleValues;}if(dict.numNans){hist.numNans=dict.numNans;}if(dict.nanDiagnostics){for(var map of dict.nanDiagnostics){hist.nanDiagnosticMaps.push(tr.v.d.DiagnosticMap.fromDict(map));}}return hist;}static buildFromSamples(unit,samples){var boundaries=HistogramBinBoundaries.createFromSamples(samples);var result=new Histogram(unit,boundaries);result.maxNumSampleValues=1000;for(var sample of samples)result.addSample(sample);return result;}get numValues(){return tr.b.Statistics.sum(this.allBins,function(e){return e.count;});}get average(){return this.running.mean;}get standardDeviation(){return this.running.stddev;}get geometricMean(){return this.running.geometricMean;}get sum(){return this.running.sum;}get maxCount(){return this.maxCount_;}getDifferenceSignificance(other,opt_alpha){if(this.unit!==other.unit)throw new Error('Cannot compare Numerics with different units');if(this.unit.improvementDirection===tr.b.ImprovementDirection.DONT_CARE){return tr.b.Statistics.Significance.DONT_CARE;}if(!(other instanceof Histogram))throw new Error('Unable to compute a p-value');var testResult=tr.b.Statistics.mwu(this.sampleValues,other.sampleValues,opt_alpha);return testResult.significance;}getApproximatePercentile(percent){if(!(percent>=0&&percent<=1))throw new Error('percent must be [0,1]');if(this.numValues==0)return 0;var valuesToSkip=Math.floor((this.numValues-1)*percent);for(var i=0;i<this.allBins.length;i++){var bin=this.allBins[i];valuesToSkip-=bin.count;if(valuesToSkip<0){if(bin===this.underflowBin)return bin.range.max;else if(bin===this.overflowBin)return bin.range.min;else return bin.range.center;}}throw new Error('Unreachable');}getBinForValue(value){var binIndex=tr.b.findHighIndexInSortedArray(this.allBins,b=>value<b.range.max?-1:1);return this.allBins[binIndex]||this.overflowBin;}addSample(value,opt_diagnostics){if(opt_diagnostics&&!(opt_diagnostics instanceof tr.v.d.DiagnosticMap))opt_diagnostics=tr.v.d.DiagnosticMap.fromObject(opt_diagnostics);if(typeof value!=='number'||isNaN(value)){this.numNans++;if(opt_diagnostics){tr.b.Statistics.uniformlySampleStream(this.nanDiagnosticMaps,this.numNans,opt_diagnostics,MAX_DIAGNOSTIC_MAPS);}}else{this.running.add(value);var bin=this.getBinForValue(value);bin.addSample(value);if(opt_diagnostics)bin.addDiagnosticMap(opt_diagnostics);if(bin.count>this.maxCount_)this.maxCount_=bin.count;}tr.b.Statistics.uniformlySampleStream(this.sampleValues_,this.numValues+this.numNans,value,this.maxNumSampleValues);}sampleValuesInto(samples){for(var sampleValue of this.sampleValues)samples.push(sampleValue);}canAddHistogram(other){if(this.unit!==other.unit)return false;if(this.allBins.length!==other.allBins.length)return false;for(var i=0;i<this.allBins.length;++i)if(!this.allBins[i].range.equals(other.allBins[i].range))return false;return true;}addHistogram(other){if(!this.canAddHistogram(other)){throw new Error('Merging incompatible Histograms');}tr.b.Statistics.mergeSampledStreams(this.nanDiagnosticMaps,this.numNans,other.nanDiagnosticMaps,other.numNans,MAX_DIAGNOSTIC_MAPS);tr.b.Statistics.mergeSampledStreams(this.sampleValues,this.numValues,other.sampleValues,other.numValues,tr.b.Statistics.mean([this.maxNumSampleValues,other.maxNumSampleValues]));this.numNans+=other.numNans;this.running=this.running.merge(other.running);for(var i=0;i<this.allBins.length;++i){this.allBins[i].addBin(other.allBins[i]);}}customizeSummaryOptions(summaryOptions){tr.b.iterItems(summaryOptions,(key,value)=>this.summaryOptions.set(key,value));}get statisticsScalars(){function statNameToKey(stat){switch(stat){case'std':return'stddev';case'avg':return'mean';}return stat;}function percentToString(percent){if(percent<0||percent>1)throw new Error('Percent must be between 0.0 and 1.0');switch(percent){case 0:return'000';case 1:return'100';}var str=percent.toString();if(str[1]!=='.')throw new Error('Unexpected percent');str=str+'0'.repeat(Math.max(4-str.length,0));if(str.length>4)str=str.slice(0,4)+'_'+str.slice(4);return'0'+str.slice(2);}var results=new Map();for(var[stat,option]of this.summaryOptions){if(!option){continue;}if(stat==='percentile'){for(var percent of option){var percentile=this.getApproximatePercentile(percent);results.set('pct_'+percentToString(percent),new tr.v.ScalarNumeric(this.unit,percentile));}}else if(stat==='nans'){results.set('nans',new tr.v.ScalarNumeric(tr.b.Unit.byName.count_smallerIsBetter,this.numNans));}else{var statUnit=stat==='count'?tr.b.Unit.byName.count_smallerIsBetter:this.unit;var key=statNameToKey(stat);var statValue=this.running[key];if(typeof statValue==='number'){results.set(stat,new tr.v.ScalarNumeric(statUnit,statValue));}}}return results;}get sampleValues(){return this.sampleValues_;}clone(){return Histogram.fromDict(this.asDict());}cloneEmpty(){var binBoundaries=HistogramBinBoundaries.fromDict(this.binBoundariesDict_);return new Histogram(this.name,this.unit,binBoundaries);}asDict(){var dict={};dict.binBoundaries=this.binBoundariesDict_;dict.name=this.name;dict.unit=this.unit.asJSON();dict.guid=this.guid;if(this.shortName){dict.shortName=this.shortName;}if(this.description){dict.description=this.description;}if(this.diagnostics.size){dict.diagnostics=this.diagnostics.asDict();}if(this.maxNumSampleValues!==this.defaultMaxNumSampleValues_){dict.maxNumSampleValues=this.maxNumSampleValues;}if(this.numNans){dict.numNans=this.numNans;}if(this.nanDiagnosticMaps.length){dict.nanDiagnostics=this.nanDiagnosticMaps.map(dm=>dm.asDict());}if(this.underflowBin.count){dict.underflowBin=this.underflowBin.asDict();}if(this.overflowBin.count){dict.overflowBin=this.overflowBin.asDict();}if(this.numValues){dict.sampleValues=this.sampleValues.slice();dict.running=this.running.asDict();dict.centralBins=this.centralBinsAsDict_();}var summaryOptions={};var anyOverriddenSummaryOptions=false;for(var[name,option]of this.summaryOptions){if(name==='percentile'){if(option.length===0){continue;}option=option.slice();}else if(option===DEFAULT_SUMMARY_OPTIONS.get(name)){continue;}summaryOptions[name]=option;anyOverriddenSummaryOptions=true;}if(anyOverriddenSummaryOptions){dict.summaryOptions=summaryOptions;}return dict;}centralBinsAsDict_(){var numCentralBins=this.centralBins.length;var emptyBins=0;for(var i=0;i<numCentralBins;++i){if(this.centralBins[i].count===0){++emptyBins;}}if(emptyBins===numCentralBins){return undefined;}if(emptyBins>numCentralBins/2){var centralBinsDict={};for(var i=0;i<numCentralBins;++i){var bin=this.centralBins[i];if(bin.count>0){centralBinsDict[i]=bin.asDict();}}return centralBinsDict;}var centralBinsArray=[];for(var i=0;i<numCentralBins;++i){centralBinsArray.push(this.centralBins[i].asDict());}return centralBinsArray;}get defaultMaxNumSampleValues_(){return this.allBins.length*10;}}var HISTOGRAM_BIN_BOUNDARIES_CACHE=new Map();class HistogramBinBoundaries{static createLinear(min,max,numBins){return new HistogramBinBoundaries(min).addLinearBins(max,numBins);}static createExponential(min,max,numBins){return new HistogramBinBoundaries(min).addExponentialBins(max,numBins);}static createWithBoundaries(binBoundaries){var builder=new HistogramBinBoundaries(binBoundaries[0]);for(var boundary of binBoundaries.slice(1))builder.addBinBoundary(boundary);return builder;}static createFromSamples(samples){var range=new tr.b.Range();for(var sample of samples)if(!isNaN(Math.max(sample)))range.addValue(sample);if(range.isEmpty)range.addValue(1);if(range.min===range.max)range.addValue(range.min-1);var numBins=Math.ceil(Math.sqrt(samples.length));var builder=new HistogramBinBoundaries(range.min);builder.addLinearBins(range.max,numBins);return builder;}constructor(minBinBoundary){this.boundaries_=undefined;this.builder_=[minBinBoundary];this.range_=new tr.b.Range();this.range_.addValue(minBinBoundary);}get range(){return this.range_;}asDict(){return this.builder_.slice();}static fromDict(dict){var cacheKey=JSON.stringify(dict);if(HISTOGRAM_BIN_BOUNDARIES_CACHE.has(cacheKey)){return HISTOGRAM_BIN_BOUNDARIES_CACHE.get(cacheKey);}var binBoundaries=new HistogramBinBoundaries(dict[0]);for(var slice of dict.slice(1)){if(!(slice instanceof Array)){binBoundaries.addBinBoundary(slice);continue;}switch(slice[0]){case HistogramBinBoundaries.SLICE_TYPE.LINEAR:binBoundaries.addLinearBins(slice[1],slice[2]);break;case HistogramBinBoundaries.SLICE_TYPE.EXPONENTIAL:binBoundaries.addExponentialBins(slice[1],slice[2]);break;default:throw new Error('Unrecognized HistogramBinBoundaries slice type');}}HISTOGRAM_BIN_BOUNDARIES_CACHE.set(cacheKey,binBoundaries);return binBoundaries;}*binRanges(){if(this.boundaries_===undefined){this.build_();}for(var i=0;i<this.boundaries_.length-1;++i){yield tr.b.Range.fromExplicitRange(this.boundaries_[i],this.boundaries_[i+1]);}}build_(){if(typeof this.builder_[0]!=='number'){throw new Error('Invalid start of builder_');}this.boundaries_=[this.builder_[0]];for(var slice of this.builder_.slice(1)){if(!(slice instanceof Array)){this.boundaries_.push(slice);continue;}var nextMaxBinBoundary=slice[1];var binCount=slice[2];var curMaxBinBoundary=this.boundaries_[this.boundaries_.length-1];switch(slice[0]){case HistogramBinBoundaries.SLICE_TYPE.LINEAR:var binWidth=(nextMaxBinBoundary-curMaxBinBoundary)/binCount;for(var i=1;i<binCount;i++){var boundary=curMaxBinBoundary+i*binWidth;this.boundaries_.push(boundary);}break;case HistogramBinBoundaries.SLICE_TYPE.EXPONENTIAL:var binExponentWidth=Math.log(nextMaxBinBoundary/curMaxBinBoundary)/binCount;for(var i=1;i<binCount;i++){var boundary=curMaxBinBoundary*Math.exp(i*binExponentWidth);this.boundaries_.push(boundary);}break;default:throw new Error('Unrecognized HistogramBinBoundaries slice type');}this.boundaries_.push(nextMaxBinBoundary);}}addBinBoundary(nextMaxBinBoundary){if(nextMaxBinBoundary<=this.range.max){throw new Error('The added max bin boundary must be larger than '+'the current max boundary');}this.boundaries_=undefined;this.builder_.push(nextMaxBinBoundary);this.range.addValue(nextMaxBinBoundary);return this;}addLinearBins(nextMaxBinBoundary,binCount){if(binCount<=0)throw new Error('Bin count must be positive');if(nextMaxBinBoundary<=this.range.max){throw new Error('The new max bin boundary must be greater than '+'the previous max bin boundary');}this.boundaries_=undefined;this.builder_.push([HistogramBinBoundaries.SLICE_TYPE.LINEAR,nextMaxBinBoundary,binCount]);this.range.addValue(nextMaxBinBoundary);return this;}addExponentialBins(nextMaxBinBoundary,binCount){if(binCount<=0){throw new Error('Bin count must be positive');}if(this.range.max<=0){throw new Error('Current max bin boundary must be positive');}if(this.range.max>=nextMaxBinBoundary){throw new Error('The last added max boundary must be greater than '+'the current max boundary boundary');}this.boundaries_=undefined;this.builder_.push([HistogramBinBoundaries.SLICE_TYPE.EXPONENTIAL,nextMaxBinBoundary,binCount]);this.range.addValue(nextMaxBinBoundary);return this;}}HistogramBinBoundaries.SLICE_TYPE={LINEAR:0,EXPONENTIAL:1};DEFAULT_BOUNDARIES_FOR_UNIT.set(tr.b.Unit.byName.timeDurationInMs.unitName,HistogramBinBoundaries.createExponential(1e-3,1e6,1e2));DEFAULT_BOUNDARIES_FOR_UNIT.set(tr.b.Unit.byName.timeStampInMs.unitName,HistogramBinBoundaries.createLinear(0,1e10,1e3));DEFAULT_BOUNDARIES_FOR_UNIT.set(tr.b.Unit.byName.normalizedPercentage.unitName,HistogramBinBoundaries.createLinear(0,1.0,20));DEFAULT_BOUNDARIES_FOR_UNIT.set(tr.b.Unit.byName.sizeInBytes.unitName,HistogramBinBoundaries.createExponential(1,1e12,1e2));DEFAULT_BOUNDARIES_FOR_UNIT.set(tr.b.Unit.byName.energyInJoules.unitName,HistogramBinBoundaries.createExponential(1e-3,1e3,50));DEFAULT_BOUNDARIES_FOR_UNIT.set(tr.b.Unit.byName.powerInWatts.unitName,HistogramBinBoundaries.createExponential(1e-3,1,50));DEFAULT_BOUNDARIES_FOR_UNIT.set(tr.b.Unit.byName.unitlessNumber.unitName,HistogramBinBoundaries.createExponential(1e-3,1e3,50));DEFAULT_BOUNDARIES_FOR_UNIT.set(tr.b.Unit.byName.count.unitName,HistogramBinBoundaries.createExponential(1,1e3,20));return{Histogram:Histogram,HistogramBinBoundaries:HistogramBinBoundaries};});
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/iteration_helpers.js":47,"../base/range.js":53,"../base/running_statistics.js":56,"../base/sorted_array_utils.js":58,"../base/statistics.js":59,"../base/unit.js":63,"./diagnostics/diagnostic_map.js":185,"./numeric.js":196}],196:[function(require,module,exports){
+(function (global){
 "use strict";require("../base/iteration_helpers.js");require("../base/unit.js");'use strict';global.tr.exportTo('tr.v',function(){class NumericBase{constructor(unit){if(!(unit instanceof tr.b.Unit))throw new Error('Expected provided unit to be instance of Unit');this.unit=unit;}asDict(){var d={unit:this.unit.asJSON()};this.asDictInto_(d);return d;}static fromDict(d){if(d.type==='scalar')return ScalarNumeric.fromDict(d);throw new Error('Not implemented');}}class ScalarNumeric extends NumericBase{constructor(unit,value){if(!(unit instanceof tr.b.Unit))throw new Error('Expected Unit');if(!(typeof value=='number'))throw new Error('Expected value to be number');super(unit);this.value=value;}asDictInto_(d){d.type='scalar';if(this.value===Infinity)d.value='Infinity';else if(this.value===-Infinity)d.value='-Infinity';else if(isNaN(this.value))d.value='NaN';else d.value=this.value;}toString(){return this.unit.format(this.value);}static fromDict(d){if(typeof d.value==='string'){if(d.value==='-Infinity'){d.value=-Infinity;}else if(d.value==='Infinity'){d.value=Infinity;}else if(d.value==='NaN'){d.value=NaN;}}return new ScalarNumeric(tr.b.Unit.fromJSON(d.unit),d.value);}}return{NumericBase:NumericBase,ScalarNumeric:ScalarNumeric};});
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../base/iteration_helpers.js":46,"../base/unit.js":62}],196:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"../base/iteration_helpers.js":47,"../base/unit.js":63}],197:[function(require,module,exports){
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 'use strict';
 
-const ExtensionProtocol=require('../../../lighthouse-core/gather/connections/extension');
-const RawProtocol=require('../../../lighthouse-core/gather/connections/raw');
-const Runner=require('../../../lighthouse-core/runner');
-const Config=require('../../../lighthouse-core/config/config');
-const defaultConfig=require('../../../lighthouse-core/config/default.json');
-const log=require('../../../lighthouse-core/lib/log');
+const ExtensionProtocol = require('../../../lighthouse-core/gather/connections/extension');
+const RawProtocol = require('../../../lighthouse-core/gather/connections/raw');
+const Runner = require('../../../lighthouse-core/runner');
+const Config = require('../../../lighthouse-core/config/config');
+const defaultConfig = require('../../../lighthouse-core/config/default.js');
+const log = require('../../../lighthouse-core/lib/log');
 
-const ReportGenerator=require('../../../lighthouse-core/report/report-generator');
+const ReportGenerator = require('../../../lighthouse-core/report/report-generator');
 
-const STORAGE_KEY='lighthouse_audits';
-const SETTINGS_KEY='lighthouse_settings';
+const STORAGE_KEY = 'lighthouse_audits';
+const SETTINGS_KEY = 'lighthouse_settings';
 
+// let installedExtensions = [];
+let disableExtensionsDuringRun = false;
+let lighthouseIsRunning = false;
+let latestStatusLog = [];
 
-let disableExtensionsDuringRun=false;
-let lighthouseIsRunning=false;
-let latestStatusLog=[];
+// /**
+//  * Enables or disables all other installed chrome extensions. The initial list
+//  * of the user's extension is created when the background page is started.
+//  * @param {!boolean} enable If true, enables all other installed extensions.
+//  *     False disables them.
+//  * @param {!Promise}
+//  */
+// function enableOtherChromeExtensions(enable) {
+//   if (!disableExtensionsDuringRun) {
+//     return Promise.resolve();
+//   }
 
+//   const str = enable ? 'enabling' : 'disabling';
+//   log.log('Chrome', `${str} ${installedExtensions.length} extensions.`);
 
+//   return Promise.all(installedExtensions.map(info => {
+//     return new Promise((resolve, reject) => {
+//       chrome.management.setEnabled(info.id, enable, _ => {
+//         if (chrome.runtime.lastError) {
+//           reject(chrome.runtime.lastError);
+//         }
+//         resolve();
+//       });
+//     });
+//   }));
+// }
 
+/**
+ * Sets the extension badge text.
+ * @param {string=} optUrl If present, sets the badge text to "Testing <url>".
+ *     Otherwise, restore the default badge text.
+ */
+function updateBadgeUI(optUrl) {
+  if (window.chrome && chrome.runtime) {
+    const manifest = chrome.runtime.getManifest();
 
+    let title = manifest.browser_action.default_title;
+    let path = manifest.browser_action.default_icon['38'];
 
+    if (lighthouseIsRunning) {
+      title = `Testing ${optUrl}`;
+      path = 'images/lh_logo_icon_light.png';
+    }
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-function updateBadgeUI(optUrl){
-if(window.chrome&&chrome.runtime){
-const manifest=chrome.runtime.getManifest();
-
-let title=manifest.browser_action.default_title;
-let path=manifest.browser_action.default_icon['38'];
-
-if(lighthouseIsRunning){
-title=`Testing ${optUrl}`;
-path='images/lh_logo_icon_light.png';
+    chrome.browserAction.setTitle({title});
+    chrome.browserAction.setIcon({path});
+  }
 }
 
-chrome.browserAction.setTitle({title});
-chrome.browserAction.setIcon({path});
-}
+/**
+ * Removes artifacts from the result object for portability
+ * @param {!Object} result Lighthouse results object
+ */
+function filterOutArtifacts(result) {
+  // strip them out, as the networkRecords artifact has circular structures
+  result.artifacts = undefined;
 }
 
+/**
+ * @param {!Connection} connection
+ * @param {string} url
+ * @param {!Object} options Lighthouse options.
+ * @param {!Array<string>} categoryIDs Name values of categories to include.
+ * @return {!Promise}
+ */
+window.runLighthouseForConnection = function(connection, url, options, categoryIDs) {
+  const config = new Config({
+    extends: 'lighthouse:default',
+    settings: {onlyCategories: categoryIDs},
+  });
 
+  // Add url and config to fresh options object.
+  const runOptions = Object.assign({}, options, {url, config});
 
+  lighthouseIsRunning = true;
+  updateBadgeUI(url);
 
+  return Runner.run(connection, runOptions) // Run Lighthouse.
+    .then(result => {
+      lighthouseIsRunning = false;
+      updateBadgeUI();
+      filterOutArtifacts(result);
+      return result;
+    })
+    .catch(err => {
+      lighthouseIsRunning = false;
+      updateBadgeUI();
+      throw err;
+    });
+};
 
-function filterOutArtifacts(result){
+/**
+ * @param {!Object} options Lighthouse options.
+ * @param {!Array<string>} categoryIDs Name values of categories to include.
+ * @return {!Promise}
+ */
+window.runLighthouseInExtension = function(options, categoryIDs) {
+  // Default to 'info' logging level.
+  log.setLevel('info');
+  const connection = new ExtensionProtocol();
+  // return enableOtherChromeExtensions(false)
+    // .then(_ => connection.getCurrentTabURL())
+  return connection.getCurrentTabURL()
+    .then(url => window.runLighthouseForConnection(connection, url, options, categoryIDs))
+    .then(results => {
+      // return enableOtherChromeExtensions(true).then(_ => {
+      const blobURL = window.createReportPageAsBlob(results, 'extension');
+      chrome.tabs.create({url: blobURL});
+      // });
+    }).catch(err => {
+      // return enableOtherChromeExtensions(true).then(_ => {
+      throw err;
+      // });
+    });
+};
 
-result.artifacts=undefined;
+/**
+ * @param {!RawProtocol.Port} port
+ * @param {string} url
+ * @param {!Object} options Lighthouse options.
+ * @param {!Array<string>} categoryIDs Name values of categories to include.
+ * @return {!Promise}
+ */
+window.runLighthouseInWorker = function(port, url, options, categoryIDs) {
+  // Default to 'info' logging level.
+  log.setLevel('info');
+  const connection = new RawProtocol(port);
+  return window.runLighthouseForConnection(connection, url, options, categoryIDs);
+};
+
+/**
+ * @param {!Object} results Lighthouse results object
+ * @param {!string} reportContext Where the report is going
+ * @return {!string} Blob URL of the report (or error page) HTML
+ */
+window.createReportPageAsBlob = function(results, reportContext) {
+  performance.mark('report-start');
+
+  const reportGenerator = new ReportGenerator();
+  let html;
+  try {
+    html = reportGenerator.generateHTML(results, reportContext);
+  } catch (err) {
+    html = reportGenerator.renderException(err, results);
+  }
+  const blob = new Blob([html], {type: 'text/html'});
+  const blobURL = window.URL.createObjectURL(blob);
+
+  performance.mark('report-end');
+  performance.measure('generate report', 'report-start', 'report-end');
+  return blobURL;
+};
+
+/**
+ * Returns list of top-level categories from the default config.
+ * @return {!Array<{name: string, id: string}>}
+ */
+window.getDefaultCategories = function() {
+  return Config.getCategories(defaultConfig);
+};
+
+/**
+ * Save currently selected set of category categories to local storage.
+ * @param {{selectedCategories: !Array<string>, disableExtensions: boolean}} settings
+ */
+window.saveSettings = function(settings) {
+  const storage = {
+    [STORAGE_KEY]: {},
+    [SETTINGS_KEY]: {}
+  };
+
+  // Stash selected categories.
+  window.getDefaultCategories().forEach(category => {
+    storage[STORAGE_KEY][category.id] = settings.selectedCategories.includes(category.id);
+  });
+
+  // Stash disable extensions setting.
+  disableExtensionsDuringRun = settings.disableExtensions;
+  storage[SETTINGS_KEY].disableExtensions = disableExtensionsDuringRun;
+
+  // Save object to chrome local storage.
+  chrome.storage.local.set(storage);
+};
+
+/**
+ * Load selected category categories from local storage.
+ * @return {!Promise<{selectedCategories: !Object<boolean>, disableExtensions: boolean}>}
+ */
+window.loadSettings = function() {
+  return new Promise(resolve => {
+    // Protip: debug what's in storage with:
+    //   chrome.storage.local.get(['lighthouse_audits'], console.log)
+    chrome.storage.local.get([STORAGE_KEY, SETTINGS_KEY], result => {
+      // Start with list of all default categories set to true so list is
+      // always up to date.
+      const defaultCategories = {};
+      window.getDefaultCategories().forEach(category => {
+        defaultCategories[category.id] = true;
+      });
+
+      // Load saved categories and settings, overwriting defaults with any
+      // saved selections.
+      const savedCategories = Object.assign(defaultCategories, result[STORAGE_KEY]);
+
+      const defaultSettings = {
+        disableExtensions: disableExtensionsDuringRun
+      };
+      const savedSettings = Object.assign(defaultSettings, result[SETTINGS_KEY]);
+
+      resolve({
+        selectedCategories: savedCategories,
+        disableExtensions: savedSettings.disableExtensions
+      });
+    });
+  });
+};
+
+window.listenForStatus = function(callback) {
+  log.events.addListener('status', function(log) {
+    latestStatusLog = log;
+    callback(log);
+  });
+
+  // Show latest saved status log to give immediate feedback
+  // when reopening the popup message when lighthouse is running
+  if (lighthouseIsRunning && latestStatusLog) {
+    callback(latestStatusLog);
+  }
+};
+
+window.isRunning = function() {
+  return lighthouseIsRunning;
+};
+
+// Run when in extension context, but not in devtools.
+if (window.chrome && chrome.runtime) {
+  // Get list of installed extensions that are enabled and can be disabled.
+  // Extensions are not allowed to be disabled if they are under an admin policy.
+  // chrome.management.getAll(installs => {
+  //   chrome.management.getSelf(lighthouseCrxInfo => {
+  //     installedExtensions = installs.filter(info => {
+  //       return info.id !== lighthouseCrxInfo.id && info.type === 'extension' &&
+  //              info.enabled && info.mayDisable;
+  //     });
+  //   });
+  // });
+
+  chrome.runtime.onInstalled.addListener(details => {
+    if (details.previousVersion) {
+      // eslint-disable-next-line no-console
+      console.log('previousVersion', details.previousVersion);
+    }
+  });
 }
 
-
-
-
-
-
-
-
-window.runLighthouseForConnection=function(connection,url,options,aggregationIDs){
-const newConfig=Config.generateNewConfigOfAggregations(defaultConfig,aggregationIDs);
-const config=new Config(newConfig);
-
-
-const runOptions=Object.assign({},options,{url,config});
-
-lighthouseIsRunning=true;
-updateBadgeUI(url);
-
-return Runner.run(connection,runOptions).
-then(result=>{
-lighthouseIsRunning=false;
-updateBadgeUI();
-filterOutArtifacts(result);
-return result;
-}).
-catch(err=>{
-lighthouseIsRunning=false;
-updateBadgeUI();
-throw err;
-});
-};
-
-
-
-
-
-
-window.runLighthouseInExtension=function(options,aggregationIDs){
-
-log.setLevel('info');
-const connection=new ExtensionProtocol();
-
-
-return connection.getCurrentTabURL().
-then(url=>window.runLighthouseForConnection(connection,url,options,aggregationIDs)).
-then(results=>{
-
-const blobURL=window.createReportPageAsBlob(results,'extension');
-chrome.tabs.create({url:blobURL});
-
-}).catch(err=>{
-
-throw err;
-
-});
-};
-
-
-
-
-
-
-
-
-window.runLighthouseInWorker=function(port,url,options,aggregationIDs){
-
-log.setLevel('info');
-const connection=new RawProtocol(port);
-return window.runLighthouseForConnection(connection,url,options,aggregationIDs);
-};
-
-
-
-
-
-
-window.createReportPageAsBlob=function(results,reportContext){
-performance.mark('report-start');
-
-const reportGenerator=new ReportGenerator();
-let html;
-try{
-html=reportGenerator.generateHTML(results,reportContext);
-}catch(err){
-html=reportGenerator.renderException(err,results);
-}
-const blob=new Blob([html],{type:'text/html'});
-const blobURL=window.URL.createObjectURL(blob);
-
-performance.mark('report-end');
-performance.measure('generate report','report-start','report-end');
-return blobURL;
-};
-
-
-
-
-
-window.getDefaultAggregations=function(){
-return Config.getAggregations(defaultConfig);
-};
-
-
-
-
-
-window.saveSettings=function(settings){
-const storage={
-[STORAGE_KEY]:{},
-[SETTINGS_KEY]:{}};
-
-
-
-window.getDefaultAggregations().forEach(agg=>{
-storage[STORAGE_KEY][agg.id]=settings.selectedAggregations.includes(agg.id);
-});
-
-
-disableExtensionsDuringRun=settings.disableExtensions;
-storage[SETTINGS_KEY].disableExtensions=disableExtensionsDuringRun;
-
-
-chrome.storage.local.set(storage);
-};
-
-
-
-
-
-window.loadSettings=function(){
-return new Promise(resolve=>{
-
-
-chrome.storage.local.get([STORAGE_KEY,SETTINGS_KEY],result=>{
-
-
-const defaultAggregations={};
-window.getDefaultAggregations().forEach(agg=>{
-defaultAggregations[agg.id]=true;
-});
-
-
-
-const savedAggregations=Object.assign(defaultAggregations,result[STORAGE_KEY]);
-
-const defaultSettings={
-disableExtensions:disableExtensionsDuringRun};
-
-const savedSettings=Object.assign(defaultSettings,result[SETTINGS_KEY]);
-
-resolve({
-selectedAggregations:savedAggregations,
-disableExtensions:savedSettings.disableExtensions});
-
-});
-});
-};
-
-window.listenForStatus=function(callback){
-log.events.addListener('status',function(log){
-latestStatusLog=log;
-callback(log);
-});
-
-
-
-if(lighthouseIsRunning&&latestStatusLog){
-callback(latestStatusLog);
-}
-};
-
-window.isRunning=function(){
-return lighthouseIsRunning;
-};
-
-
-if(window.chrome&&chrome.runtime){
-
-
-
-
-
-
-
-
-
-
-
-chrome.runtime.onInstalled.addListener(details=>{
-if(details.previousVersion){
-
-console.log('previousVersion',details.previousVersion);
-}
-});
-}
-
-},{"../../../lighthouse-core/config/config":5,"../../../lighthouse-core/config/default.json":6,"../../../lighthouse-core/gather/connections/extension":8,"../../../lighthouse-core/gather/connections/raw":9,"../../../lighthouse-core/lib/log":19,"../../../lighthouse-core/report/report-generator":30,"../../../lighthouse-core/runner":32}],197:[function(require,module,exports){
+},{"../../../lighthouse-core/config/config":5,"../../../lighthouse-core/config/default.js":6,"../../../lighthouse-core/gather/connections/extension":8,"../../../lighthouse-core/gather/connections/raw":9,"../../../lighthouse-core/lib/log":19,"../../../lighthouse-core/report/report-generator":30,"../../../lighthouse-core/runner":33}],198:[function(require,module,exports){
+(function (global){
 'use strict';
 
-exports.byteLength=byteLength;
-exports.toByteArray=toByteArray;
-exports.fromByteArray=fromByteArray;
+// compare and isBuffer taken from https://github.com/feross/buffer/blob/680e9e5e488f22aac27599a57dc844a6315928dd/index.js
+// original notice:
 
-var lookup=[];
-var revLookup=[];
-var Arr=typeof Uint8Array!=='undefined'?Uint8Array:Array;
+/*!
+ * The buffer module from node.js, for the browser.
+ *
+ * @author   Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
+ * @license  MIT
+ */
+function compare(a, b) {
+  if (a === b) {
+    return 0;
+  }
 
-var code='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
-for(var i=0,len=code.length;i<len;++i){
-lookup[i]=code[i];
-revLookup[code.charCodeAt(i)]=i;
-}
+  var x = a.length;
+  var y = b.length;
 
-revLookup['-'.charCodeAt(0)]=62;
-revLookup['_'.charCodeAt(0)]=63;
+  for (var i = 0, len = Math.min(x, y); i < len; ++i) {
+    if (a[i] !== b[i]) {
+      x = a[i];
+      y = b[i];
+      break;
+    }
+  }
 
-function placeHoldersCount(b64){
-var len=b64.length;
-if(len%4>0){
-throw new Error('Invalid string. Length must be a multiple of 4');
+  if (x < y) {
+    return -1;
+  }
+  if (y < x) {
+    return 1;
+  }
+  return 0;
 }
-
-
-
-
-
-
-return b64[len-2]==='='?2:b64[len-1]==='='?1:0;
+function isBuffer(b) {
+  if (global.Buffer && typeof global.Buffer.isBuffer === 'function') {
+    return global.Buffer.isBuffer(b);
+  }
+  return !!(b != null && b._isBuffer);
 }
 
-function byteLength(b64){
+// based on node assert, original notice:
 
-return b64.length*3/4-placeHoldersCount(b64);
-}
+// http://wiki.commonjs.org/wiki/Unit_Testing/1.0
+//
+// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8!
+//
+// Originally from narwhal.js (http://narwhaljs.org)
+// Copyright (c) 2009 Thomas Robinson <280north.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the 'Software'), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-function toByteArray(b64){
-var i,j,l,tmp,placeHolders,arr;
-var len=b64.length;
-placeHolders=placeHoldersCount(b64);
-
-arr=new Arr(len*3/4-placeHolders);
-
-
-l=placeHolders>0?len-4:len;
-
-var L=0;
-
-for(i=0,j=0;i<l;i+=4,j+=3){
-tmp=revLookup[b64.charCodeAt(i)]<<18|revLookup[b64.charCodeAt(i+1)]<<12|revLookup[b64.charCodeAt(i+2)]<<6|revLookup[b64.charCodeAt(i+3)];
-arr[L++]=tmp>>16&0xFF;
-arr[L++]=tmp>>8&0xFF;
-arr[L++]=tmp&0xFF;
+var util = require('util/');
+var hasOwn = Object.prototype.hasOwnProperty;
+var pSlice = Array.prototype.slice;
+var functionsHaveNames = (function () {
+  return function foo() {}.name === 'foo';
+}());
+function pToString (obj) {
+  return Object.prototype.toString.call(obj);
 }
-
-if(placeHolders===2){
-tmp=revLookup[b64.charCodeAt(i)]<<2|revLookup[b64.charCodeAt(i+1)]>>4;
-arr[L++]=tmp&0xFF;
-}else if(placeHolders===1){
-tmp=revLookup[b64.charCodeAt(i)]<<10|revLookup[b64.charCodeAt(i+1)]<<4|revLookup[b64.charCodeAt(i+2)]>>2;
-arr[L++]=tmp>>8&0xFF;
-arr[L++]=tmp&0xFF;
+function isView(arrbuf) {
+  if (isBuffer(arrbuf)) {
+    return false;
+  }
+  if (typeof global.ArrayBuffer !== 'function') {
+    return false;
+  }
+  if (typeof ArrayBuffer.isView === 'function') {
+    return ArrayBuffer.isView(arrbuf);
+  }
+  if (!arrbuf) {
+    return false;
+  }
+  if (arrbuf instanceof DataView) {
+    return true;
+  }
+  if (arrbuf.buffer && arrbuf.buffer instanceof ArrayBuffer) {
+    return true;
+  }
+  return false;
 }
+// 1. The assert module provides functions that throw
+// AssertionError's when particular conditions are not met. The
+// assert module must conform to the following interface.
 
-return arr;
-}
+var assert = module.exports = ok;
 
-function tripletToBase64(num){
-return lookup[num>>18&0x3F]+lookup[num>>12&0x3F]+lookup[num>>6&0x3F]+lookup[num&0x3F];
-}
+// 2. The AssertionError is defined in assert.
+// new assert.AssertionError({ message: message,
+//                             actual: actual,
+//                             expected: expected })
 
-function encodeChunk(uint8,start,end){
-var tmp;
-var output=[];
-for(var i=start;i<end;i+=3){
-tmp=(uint8[i]<<16)+(uint8[i+1]<<8)+uint8[i+2];
-output.push(tripletToBase64(tmp));
-}
-return output.join('');
+var regex = /\s*function\s+([^\(\s]*)\s*/;
+// based on https://github.com/ljharb/function.prototype.name/blob/adeeeec8bfcc6068b187d7d9fb3d5bb1d3a30899/implementation.js
+function getName(func) {
+  if (!util.isFunction(func)) {
+    return;
+  }
+  if (functionsHaveNames) {
+    return func.name;
+  }
+  var str = func.toString();
+  var match = str.match(regex);
+  return match && match[1];
 }
+assert.AssertionError = function AssertionError(options) {
+  this.name = 'AssertionError';
+  this.actual = options.actual;
+  this.expected = options.expected;
+  this.operator = options.operator;
+  if (options.message) {
+    this.message = options.message;
+    this.generatedMessage = false;
+  } else {
+    this.message = getMessage(this);
+    this.generatedMessage = true;
+  }
+  var stackStartFunction = options.stackStartFunction || fail;
+  if (Error.captureStackTrace) {
+    Error.captureStackTrace(this, stackStartFunction);
+  } else {
+    // non v8 browsers so we can have a stacktrace
+    var err = new Error();
+    if (err.stack) {
+      var out = err.stack;
 
-function fromByteArray(uint8){
-var tmp;
-var len=uint8.length;
-var extraBytes=len%3;
-var output='';
-var parts=[];
-var maxChunkLength=16383;
+      // try to strip useless frames
+      var fn_name = getName(stackStartFunction);
+      var idx = out.indexOf('\n' + fn_name);
+      if (idx >= 0) {
+        // once we have located the function frame
+        // we need to strip out everything before it (and its line)
+        var next_line = out.indexOf('\n', idx + 1);
+        out = out.substring(next_line + 1);
+      }
 
-
-for(var i=0,len2=len-extraBytes;i<len2;i+=maxChunkLength){
-parts.push(encodeChunk(uint8,i,i+maxChunkLength>len2?len2:i+maxChunkLength));
-}
-
-
-if(extraBytes===1){
-tmp=uint8[len-1];
-output+=lookup[tmp>>2];
-output+=lookup[tmp<<4&0x3F];
-output+='==';
-}else if(extraBytes===2){
-tmp=(uint8[len-2]<<8)+uint8[len-1];
-output+=lookup[tmp>>10];
-output+=lookup[tmp>>4&0x3F];
-output+=lookup[tmp<<2&0x3F];
-output+='=';
-}
-
-parts.push(output);
-
-return parts.join('');
-}
-
-},{}],198:[function(require,module,exports){
-
-},{}],199:[function(require,module,exports){
-(function(global){
-
-
-
-
-
-
-
-
-'use strict';
-
-var base64=require('base64-js');
-var ieee754=require('ieee754');
-var isArray=require('isarray');
-
-exports.Buffer=Buffer;
-exports.SlowBuffer=SlowBuffer;
-exports.INSPECT_MAX_BYTES=50;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Buffer.TYPED_ARRAY_SUPPORT=global.TYPED_ARRAY_SUPPORT!==undefined?
-global.TYPED_ARRAY_SUPPORT:
-typedArraySupport();
-
-
-
-
-exports.kMaxLength=kMaxLength();
-
-function typedArraySupport(){
-try{
-var arr=new Uint8Array(1);
-arr.__proto__={__proto__:Uint8Array.prototype,foo:function(){return 42;}};
-return arr.foo()===42&&
-typeof arr.subarray==='function'&&
-arr.subarray(1,1).byteLength===0;
-}catch(e){
-return false;
-}
-}
-
-function kMaxLength(){
-return Buffer.TYPED_ARRAY_SUPPORT?
-0x7fffffff:
-0x3fffffff;
-}
-
-function createBuffer(that,length){
-if(kMaxLength()<length){
-throw new RangeError('Invalid typed array length');
-}
-if(Buffer.TYPED_ARRAY_SUPPORT){
-
-that=new Uint8Array(length);
-that.__proto__=Buffer.prototype;
-}else{
-
-if(that===null){
-that=new Buffer(length);
-}
-that.length=length;
-}
-
-return that;
-}
-
-
-
-
-
-
-
-
-
-
-
-function Buffer(arg,encodingOrOffset,length){
-if(!Buffer.TYPED_ARRAY_SUPPORT&&!(this instanceof Buffer)){
-return new Buffer(arg,encodingOrOffset,length);
-}
-
-
-if(typeof arg==='number'){
-if(typeof encodingOrOffset==='string'){
-throw new Error(
-'If encoding is specified then the first argument must be a string');
-
-}
-return allocUnsafe(this,arg);
-}
-return from(this,arg,encodingOrOffset,length);
-}
-
-Buffer.poolSize=8192;
-
-
-Buffer._augment=function(arr){
-arr.__proto__=Buffer.prototype;
-return arr;
+      this.stack = out;
+    }
+  }
 };
 
-function from(that,value,encodingOrOffset,length){
-if(typeof value==='number'){
-throw new TypeError('"value" argument must not be a number');
-}
+// assert.AssertionError instanceof Error
+util.inherits(assert.AssertionError, Error);
 
-if(typeof ArrayBuffer!=='undefined'&&value instanceof ArrayBuffer){
-return fromArrayBuffer(that,value,encodingOrOffset,length);
+function truncate(s, n) {
+  if (typeof s === 'string') {
+    return s.length < n ? s : s.slice(0, n);
+  } else {
+    return s;
+  }
 }
-
-if(typeof value==='string'){
-return fromString(that,value,encodingOrOffset);
+function inspect(something) {
+  if (functionsHaveNames || !util.isFunction(something)) {
+    return util.inspect(something);
+  }
+  var rawname = getName(something);
+  var name = rawname ? ': ' + rawname : '';
+  return '[Function' +  name + ']';
 }
-
-return fromObject(that,value);
+function getMessage(self) {
+  return truncate(inspect(self.actual), 128) + ' ' +
+         self.operator + ' ' +
+         truncate(inspect(self.expected), 128);
 }
 
-
-
-
-
-
-
-
-
-Buffer.from=function(value,encodingOrOffset,length){
-return from(null,value,encodingOrOffset,length);
-};
-
-if(Buffer.TYPED_ARRAY_SUPPORT){
-Buffer.prototype.__proto__=Uint8Array.prototype;
-Buffer.__proto__=Uint8Array;
-if(typeof Symbol!=='undefined'&&Symbol.species&&
-Buffer[Symbol.species]===Buffer){
+// At present only the three keys mentioned above are used and
+// understood by the spec. Implementations or sub modules can pass
+// other keys to the AssertionError's constructor - they will be
+// ignored.
 
-Object.defineProperty(Buffer,Symbol.species,{
-value:null,
-configurable:true});
+// 3. All of the following functions must throw an AssertionError
+// when a corresponding condition is not met, with a message that
+// may be undefined if not provided.  All assertion methods provide
+// both the actual and expected values to the assertion error for
+// display purposes.
 
-}
+function fail(actual, expected, message, operator, stackStartFunction) {
+  throw new assert.AssertionError({
+    message: message,
+    actual: actual,
+    expected: expected,
+    operator: operator,
+    stackStartFunction: stackStartFunction
+  });
 }
 
-function assertSize(size){
-if(typeof size!=='number'){
-throw new TypeError('"size" argument must be a number');
-}else if(size<0){
-throw new RangeError('"size" argument must not be negative');
-}
-}
-
-function alloc(that,size,fill,encoding){
-assertSize(size);
-if(size<=0){
-return createBuffer(that,size);
-}
-if(fill!==undefined){
-
+// EXTENSION! allows for well behaved errors defined elsewhere.
+assert.fail = fail;
 
+// 4. Pure assertion tests whether a value is truthy, as determined
+// by !!guard.
+// assert.ok(guard, message_opt);
+// This statement is equivalent to assert.equal(true, !!guard,
+// message_opt);. To test strictly for the value true, use
+// assert.strictEqual(true, guard, message_opt);.
 
-return typeof encoding==='string'?
-createBuffer(that,size).fill(fill,encoding):
-createBuffer(that,size).fill(fill);
+function ok(value, message) {
+  if (!value) fail(value, true, message, '==', assert.ok);
 }
-return createBuffer(that,size);
-}
-
-
-
+assert.ok = ok;
 
+// 5. The equality assertion tests shallow, coercive equality with
+// ==.
+// assert.equal(actual, expected, message_opt);
 
-Buffer.alloc=function(size,fill,encoding){
-return alloc(null,size,fill,encoding);
+assert.equal = function equal(actual, expected, message) {
+  if (actual != expected) fail(actual, expected, message, '==', assert.equal);
 };
 
-function allocUnsafe(that,size){
-assertSize(size);
-that=createBuffer(that,size<0?0:checked(size)|0);
-if(!Buffer.TYPED_ARRAY_SUPPORT){
-for(var i=0;i<size;++i){
-that[i]=0;
-}
-}
-return that;
-}
-
-
-
+// 6. The non-equality assertion tests for whether two objects are not equal
+// with != assert.notEqual(actual, expected, message_opt);
 
-Buffer.allocUnsafe=function(size){
-return allocUnsafe(null,size);
+assert.notEqual = function notEqual(actual, expected, message) {
+  if (actual == expected) {
+    fail(actual, expected, message, '!=', assert.notEqual);
+  }
 };
 
-
+// 7. The equivalence assertion tests a deep equality relation.
+// assert.deepEqual(actual, expected, message_opt);
 
-Buffer.allocUnsafeSlow=function(size){
-return allocUnsafe(null,size);
+assert.deepEqual = function deepEqual(actual, expected, message) {
+  if (!_deepEqual(actual, expected, false)) {
+    fail(actual, expected, message, 'deepEqual', assert.deepEqual);
+  }
 };
 
-function fromString(that,string,encoding){
-if(typeof encoding!=='string'||encoding===''){
-encoding='utf8';
-}
-
-if(!Buffer.isEncoding(encoding)){
-throw new TypeError('"encoding" must be a valid string encoding');
-}
-
-var length=byteLength(string,encoding)|0;
-that=createBuffer(that,length);
-
-var actual=that.write(string,encoding);
-
-if(actual!==length){
-
-
-
-that=that.slice(0,actual);
-}
-
-return that;
-}
-
-function fromArrayLike(that,array){
-var length=array.length<0?0:checked(array.length)|0;
-that=createBuffer(that,length);
-for(var i=0;i<length;i+=1){
-that[i]=array[i]&255;
-}
-return that;
-}
-
-function fromArrayBuffer(that,array,byteOffset,length){
-array.byteLength;
-
-if(byteOffset<0||array.byteLength<byteOffset){
-throw new RangeError('\'offset\' is out of bounds');
-}
-
-if(array.byteLength<byteOffset+(length||0)){
-throw new RangeError('\'length\' is out of bounds');
-}
-
-if(byteOffset===undefined&&length===undefined){
-array=new Uint8Array(array);
-}else if(length===undefined){
-array=new Uint8Array(array,byteOffset);
-}else{
-array=new Uint8Array(array,byteOffset,length);
-}
-
-if(Buffer.TYPED_ARRAY_SUPPORT){
-
-that=array;
-that.__proto__=Buffer.prototype;
-}else{
-
-that=fromArrayLike(that,array);
-}
-return that;
-}
-
-function fromObject(that,obj){
-if(Buffer.isBuffer(obj)){
-var len=checked(obj.length)|0;
-that=createBuffer(that,len);
-
-if(that.length===0){
-return that;
-}
-
-obj.copy(that,0,0,len);
-return that;
-}
-
-if(obj){
-if(typeof ArrayBuffer!=='undefined'&&
-obj.buffer instanceof ArrayBuffer||'length'in obj){
-if(typeof obj.length!=='number'||isnan(obj.length)){
-return createBuffer(that,0);
-}
-return fromArrayLike(that,obj);
-}
-
-if(obj.type==='Buffer'&&isArray(obj.data)){
-return fromArrayLike(that,obj.data);
-}
-}
-
-throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.');
-}
-
-function checked(length){
-
-
-if(length>=kMaxLength()){
-throw new RangeError('Attempt to allocate Buffer larger than maximum '+
-'size: 0x'+kMaxLength().toString(16)+' bytes');
-}
-return length|0;
-}
-
-function SlowBuffer(length){
-if(+length!=length){
-length=0;
-}
-return Buffer.alloc(+length);
-}
-
-Buffer.isBuffer=function isBuffer(b){
-return!!(b!=null&&b._isBuffer);
-};
-
-Buffer.compare=function compare(a,b){
-if(!Buffer.isBuffer(a)||!Buffer.isBuffer(b)){
-throw new TypeError('Arguments must be Buffers');
-}
-
-if(a===b)return 0;
-
-var x=a.length;
-var y=b.length;
-
-for(var i=0,len=Math.min(x,y);i<len;++i){
-if(a[i]!==b[i]){
-x=a[i];
-y=b[i];
-break;
-}
-}
-
-if(x<y)return-1;
-if(y<x)return 1;
-return 0;
-};
-
-Buffer.isEncoding=function isEncoding(encoding){
-switch(String(encoding).toLowerCase()){
-case'hex':
-case'utf8':
-case'utf-8':
-case'ascii':
-case'latin1':
-case'binary':
-case'base64':
-case'ucs2':
-case'ucs-2':
-case'utf16le':
-case'utf-16le':
-return true;
-default:
-return false;}
-
-};
-
-Buffer.concat=function concat(list,length){
-if(!isArray(list)){
-throw new TypeError('"list" argument must be an Array of Buffers');
-}
-
-if(list.length===0){
-return Buffer.alloc(0);
-}
-
-var i;
-if(length===undefined){
-length=0;
-for(i=0;i<list.length;++i){
-length+=list[i].length;
-}
-}
-
-var buffer=Buffer.allocUnsafe(length);
-var pos=0;
-for(i=0;i<list.length;++i){
-var buf=list[i];
-if(!Buffer.isBuffer(buf)){
-throw new TypeError('"list" argument must be an Array of Buffers');
-}
-buf.copy(buffer,pos);
-pos+=buf.length;
-}
-return buffer;
+assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) {
+  if (!_deepEqual(actual, expected, true)) {
+    fail(actual, expected, message, 'deepStrictEqual', assert.deepStrictEqual);
+  }
 };
 
-function byteLength(string,encoding){
-if(Buffer.isBuffer(string)){
-return string.length;
-}
-if(typeof ArrayBuffer!=='undefined'&&typeof ArrayBuffer.isView==='function'&&(
-ArrayBuffer.isView(string)||string instanceof ArrayBuffer)){
-return string.byteLength;
-}
-if(typeof string!=='string'){
-string=''+string;
-}
+function _deepEqual(actual, expected, strict, memos) {
+  // 7.1. All identical values are equivalent, as determined by ===.
+  if (actual === expected) {
+    return true;
+  } else if (isBuffer(actual) && isBuffer(expected)) {
+    return compare(actual, expected) === 0;
 
-var len=string.length;
-if(len===0)return 0;
+  // 7.2. If the expected value is a Date object, the actual value is
+  // equivalent if it is also a Date object that refers to the same time.
+  } else if (util.isDate(actual) && util.isDate(expected)) {
+    return actual.getTime() === expected.getTime();
 
+  // 7.3 If the expected value is a RegExp object, the actual value is
+  // equivalent if it is also a RegExp object with the same source and
+  // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
+  } else if (util.isRegExp(actual) && util.isRegExp(expected)) {
+    return actual.source === expected.source &&
+           actual.global === expected.global &&
+           actual.multiline === expected.multiline &&
+           actual.lastIndex === expected.lastIndex &&
+           actual.ignoreCase === expected.ignoreCase;
 
-var loweredCase=false;
-for(;;){
-switch(encoding){
-case'ascii':
-case'latin1':
-case'binary':
-return len;
-case'utf8':
-case'utf-8':
-case undefined:
-return utf8ToBytes(string).length;
-case'ucs2':
-case'ucs-2':
-case'utf16le':
-case'utf-16le':
-return len*2;
-case'hex':
-return len>>>1;
-case'base64':
-return base64ToBytes(string).length;
-default:
-if(loweredCase)return utf8ToBytes(string).length;
-encoding=(''+encoding).toLowerCase();
-loweredCase=true;}
-
-}
-}
-Buffer.byteLength=byteLength;
-
-function slowToString(encoding,start,end){
-var loweredCase=false;
-
-
-
+  // 7.4. Other pairs that do not both pass typeof value == 'object',
+  // equivalence is determined by ==.
+  } else if ((actual === null || typeof actual !== 'object') &&
+             (expected === null || typeof expected !== 'object')) {
+    return strict ? actual === expected : actual == expected;
 
+  // If both values are instances of typed arrays, wrap their underlying
+  // ArrayBuffers in a Buffer each to increase performance
+  // This optimization requires the arrays to have the same type as checked by
+  // Object.prototype.toString (aka pToString). Never perform binary
+  // comparisons for Float*Arrays, though, since e.g. +0 === -0 but their
+  // bit patterns are not identical.
+  } else if (isView(actual) && isView(expected) &&
+             pToString(actual) === pToString(expected) &&
+             !(actual instanceof Float32Array ||
+               actual instanceof Float64Array)) {
+    return compare(new Uint8Array(actual.buffer),
+                   new Uint8Array(expected.buffer)) === 0;
 
+  // 7.5 For all other Object pairs, including Array objects, equivalence is
+  // determined by having the same number of owned properties (as verified
+  // with Object.prototype.hasOwnProperty.call), the same set of keys
+  // (although not necessarily the same order), equivalent values for every
+  // corresponding key, and an identical 'prototype' property. Note: this
+  // accounts for both named and indexed properties on Arrays.
+  } else if (isBuffer(actual) !== isBuffer(expected)) {
+    return false;
+  } else {
+    memos = memos || {actual: [], expected: []};
 
+    var actualIndex = memos.actual.indexOf(actual);
+    if (actualIndex !== -1) {
+      if (actualIndex === memos.expected.indexOf(expected)) {
+        return true;
+      }
+    }
 
+    memos.actual.push(actual);
+    memos.expected.push(expected);
 
-if(start===undefined||start<0){
-start=0;
+    return objEquiv(actual, expected, strict, memos);
+  }
 }
 
-
-if(start>this.length){
-return'';
+function isArguments(object) {
+  return Object.prototype.toString.call(object) == '[object Arguments]';
 }
 
-if(end===undefined||end>this.length){
-end=this.length;
-}
-
-if(end<=0){
-return'';
-}
-
-
-end>>>=0;
-start>>>=0;
-
-if(end<=start){
-return'';
-}
-
-if(!encoding)encoding='utf8';
-
-while(true){
-switch(encoding){
-case'hex':
-return hexSlice(this,start,end);
-
-case'utf8':
-case'utf-8':
-return utf8Slice(this,start,end);
-
-case'ascii':
-return asciiSlice(this,start,end);
-
-case'latin1':
-case'binary':
-return latin1Slice(this,start,end);
-
-case'base64':
-return base64Slice(this,start,end);
-
-case'ucs2':
-case'ucs-2':
-case'utf16le':
-case'utf-16le':
-return utf16leSlice(this,start,end);
-
-default:
-if(loweredCase)throw new TypeError('Unknown encoding: '+encoding);
-encoding=(encoding+'').toLowerCase();
-loweredCase=true;}
-
-}
+function objEquiv(a, b, strict, actualVisitedObjects) {
+  if (a === null || a === undefined || b === null || b === undefined)
+    return false;
+  // if one is a primitive, the other must be same
+  if (util.isPrimitive(a) || util.isPrimitive(b))
+    return a === b;
+  if (strict && Object.getPrototypeOf(a) !== Object.getPrototypeOf(b))
+    return false;
+  var aIsArgs = isArguments(a);
+  var bIsArgs = isArguments(b);
+  if ((aIsArgs && !bIsArgs) || (!aIsArgs && bIsArgs))
+    return false;
+  if (aIsArgs) {
+    a = pSlice.call(a);
+    b = pSlice.call(b);
+    return _deepEqual(a, b, strict);
+  }
+  var ka = objectKeys(a);
+  var kb = objectKeys(b);
+  var key, i;
+  // having the same number of owned properties (keys incorporates
+  // hasOwnProperty)
+  if (ka.length !== kb.length)
+    return false;
+  //the same set of keys (although not necessarily the same order),
+  ka.sort();
+  kb.sort();
+  //~~~cheap key test
+  for (i = ka.length - 1; i >= 0; i--) {
+    if (ka[i] !== kb[i])
+      return false;
+  }
+  //equivalent values for every corresponding key, and
+  //~~~possibly expensive deep test
+  for (i = ka.length - 1; i >= 0; i--) {
+    key = ka[i];
+    if (!_deepEqual(a[key], b[key], strict, actualVisitedObjects))
+      return false;
+  }
+  return true;
 }
 
+// 8. The non-equivalence assertion tests for any deep inequality.
+// assert.notDeepEqual(actual, expected, message_opt);
 
-
-Buffer.prototype._isBuffer=true;
-
-function swap(b,n,m){
-var i=b[n];
-b[n]=b[m];
-b[m]=i;
-}
-
-Buffer.prototype.swap16=function swap16(){
-var len=this.length;
-if(len%2!==0){
-throw new RangeError('Buffer size must be a multiple of 16-bits');
-}
-for(var i=0;i<len;i+=2){
-swap(this,i,i+1);
-}
-return this;
+assert.notDeepEqual = function notDeepEqual(actual, expected, message) {
+  if (_deepEqual(actual, expected, false)) {
+    fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual);
+  }
 };
 
-Buffer.prototype.swap32=function swap32(){
-var len=this.length;
-if(len%4!==0){
-throw new RangeError('Buffer size must be a multiple of 32-bits');
-}
-for(var i=0;i<len;i+=4){
-swap(this,i,i+3);
-swap(this,i+1,i+2);
+assert.notDeepStrictEqual = notDeepStrictEqual;
+function notDeepStrictEqual(actual, expected, message) {
+  if (_deepEqual(actual, expected, true)) {
+    fail(actual, expected, message, 'notDeepStrictEqual', notDeepStrictEqual);
+  }
 }
-return this;
-};
 
-Buffer.prototype.swap64=function swap64(){
-var len=this.length;
-if(len%8!==0){
-throw new RangeError('Buffer size must be a multiple of 64-bits');
-}
-for(var i=0;i<len;i+=8){
-swap(this,i,i+7);
-swap(this,i+1,i+6);
-swap(this,i+2,i+5);
-swap(this,i+3,i+4);
-}
-return this;
-};
 
-Buffer.prototype.toString=function toString(){
-var length=this.length|0;
-if(length===0)return'';
-if(arguments.length===0)return utf8Slice(this,0,length);
-return slowToString.apply(this,arguments);
-};
-
-Buffer.prototype.equals=function equals(b){
-if(!Buffer.isBuffer(b))throw new TypeError('Argument must be a Buffer');
-if(this===b)return true;
-return Buffer.compare(this,b)===0;
-};
+// 9. The strict equality assertion tests strict equality, as determined by ===.
+// assert.strictEqual(actual, expected, message_opt);
 
-Buffer.prototype.inspect=function inspect(){
-var str='';
-var max=exports.INSPECT_MAX_BYTES;
-if(this.length>0){
-str=this.toString('hex',0,max).match(/.{2}/g).join(' ');
-if(this.length>max)str+=' ... ';
-}
-return'<Buffer '+str+'>';
+assert.strictEqual = function strictEqual(actual, expected, message) {
+  if (actual !== expected) {
+    fail(actual, expected, message, '===', assert.strictEqual);
+  }
 };
 
-Buffer.prototype.compare=function compare(target,start,end,thisStart,thisEnd){
-if(!Buffer.isBuffer(target)){
-throw new TypeError('Argument must be a Buffer');
-}
+// 10. The strict non-equality assertion tests for strict inequality, as
+// determined by !==.  assert.notStrictEqual(actual, expected, message_opt);
 
-if(start===undefined){
-start=0;
-}
-if(end===undefined){
-end=target?target.length:0;
-}
-if(thisStart===undefined){
-thisStart=0;
-}
-if(thisEnd===undefined){
-thisEnd=this.length;
-}
-
-if(start<0||end>target.length||thisStart<0||thisEnd>this.length){
-throw new RangeError('out of range index');
-}
-
-if(thisStart>=thisEnd&&start>=end){
-return 0;
-}
-if(thisStart>=thisEnd){
-return-1;
-}
-if(start>=end){
-return 1;
-}
-
-start>>>=0;
-end>>>=0;
-thisStart>>>=0;
-thisEnd>>>=0;
-
-if(this===target)return 0;
-
-var x=thisEnd-thisStart;
-var y=end-start;
-var len=Math.min(x,y);
-
-var thisCopy=this.slice(thisStart,thisEnd);
-var targetCopy=target.slice(start,end);
-
-for(var i=0;i<len;++i){
-if(thisCopy[i]!==targetCopy[i]){
-x=thisCopy[i];
-y=targetCopy[i];
-break;
-}
-}
-
-if(x<y)return-1;
-if(y<x)return 1;
-return 0;
+assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
+  if (actual === expected) {
+    fail(actual, expected, message, '!==', assert.notStrictEqual);
+  }
 };
 
+function expectedException(actual, expected) {
+  if (!actual || !expected) {
+    return false;
+  }
 
+  if (Object.prototype.toString.call(expected) == '[object RegExp]') {
+    return expected.test(actual);
+  }
 
+  try {
+    if (actual instanceof expected) {
+      return true;
+    }
+  } catch (e) {
+    // Ignore.  The instanceof check doesn't work for arrow functions.
+  }
 
+  if (Error.isPrototypeOf(expected)) {
+    return false;
+  }
 
-
-
-
-
-
-function bidirectionalIndexOf(buffer,val,byteOffset,encoding,dir){
-
-if(buffer.length===0)return-1;
-
-
-if(typeof byteOffset==='string'){
-encoding=byteOffset;
-byteOffset=0;
-}else if(byteOffset>0x7fffffff){
-byteOffset=0x7fffffff;
-}else if(byteOffset<-0x80000000){
-byteOffset=-0x80000000;
+  return expected.call({}, actual) === true;
 }
-byteOffset=+byteOffset;
-if(isNaN(byteOffset)){
 
-byteOffset=dir?0:buffer.length-1;
+function _tryBlock(block) {
+  var error;
+  try {
+    block();
+  } catch (e) {
+    error = e;
+  }
+  return error;
 }
 
+function _throws(shouldThrow, block, expected, message) {
+  var actual;
 
-if(byteOffset<0)byteOffset=buffer.length+byteOffset;
-if(byteOffset>=buffer.length){
-if(dir)return-1;else
-byteOffset=buffer.length-1;
-}else if(byteOffset<0){
-if(dir)byteOffset=0;else
-return-1;
-}
-
-
-if(typeof val==='string'){
-val=Buffer.from(val,encoding);
-}
-
+  if (typeof block !== 'function') {
+    throw new TypeError('"block" argument must be a function');
+  }
 
-if(Buffer.isBuffer(val)){
+  if (typeof expected === 'string') {
+    message = expected;
+    expected = null;
+  }
 
-if(val.length===0){
-return-1;
-}
-return arrayIndexOf(buffer,val,byteOffset,encoding,dir);
-}else if(typeof val==='number'){
-val=val&0xFF;
-if(Buffer.TYPED_ARRAY_SUPPORT&&
-typeof Uint8Array.prototype.indexOf==='function'){
-if(dir){
-return Uint8Array.prototype.indexOf.call(buffer,val,byteOffset);
-}else{
-return Uint8Array.prototype.lastIndexOf.call(buffer,val,byteOffset);
-}
-}
-return arrayIndexOf(buffer,[val],byteOffset,encoding,dir);
-}
+  actual = _tryBlock(block);
 
-throw new TypeError('val must be string, number or Buffer');
-}
+  message = (expected && expected.name ? ' (' + expected.name + ').' : '.') +
+            (message ? ' ' + message : '.');
 
-function arrayIndexOf(arr,val,byteOffset,encoding,dir){
-var indexSize=1;
-var arrLength=arr.length;
-var valLength=val.length;
+  if (shouldThrow && !actual) {
+    fail(actual, expected, 'Missing expected exception' + message);
+  }
 
-if(encoding!==undefined){
-encoding=String(encoding).toLowerCase();
-if(encoding==='ucs2'||encoding==='ucs-2'||
-encoding==='utf16le'||encoding==='utf-16le'){
-if(arr.length<2||val.length<2){
-return-1;
-}
-indexSize=2;
-arrLength/=2;
-valLength/=2;
-byteOffset/=2;
-}
-}
+  var userProvidedMessage = typeof message === 'string';
+  var isUnwantedException = !shouldThrow && util.isError(actual);
+  var isUnexpectedException = !shouldThrow && actual && !expected;
 
-function read(buf,i){
-if(indexSize===1){
-return buf[i];
-}else{
-return buf.readUInt16BE(i*indexSize);
-}
-}
+  if ((isUnwantedException &&
+      userProvidedMessage &&
+      expectedException(actual, expected)) ||
+      isUnexpectedException) {
+    fail(actual, expected, 'Got unwanted exception' + message);
+  }
 
-var i;
-if(dir){
-var foundIndex=-1;
-for(i=byteOffset;i<arrLength;i++){
-if(read(arr,i)===read(val,foundIndex===-1?0:i-foundIndex)){
-if(foundIndex===-1)foundIndex=i;
-if(i-foundIndex+1===valLength)return foundIndex*indexSize;
-}else{
-if(foundIndex!==-1)i-=i-foundIndex;
-foundIndex=-1;
-}
-}
-}else{
-if(byteOffset+valLength>arrLength)byteOffset=arrLength-valLength;
-for(i=byteOffset;i>=0;i--){
-var found=true;
-for(var j=0;j<valLength;j++){
-if(read(arr,i+j)!==read(val,j)){
-found=false;
-break;
-}
-}
-if(found)return i;
-}
+  if ((shouldThrow && actual && expected &&
+      !expectedException(actual, expected)) || (!shouldThrow && actual)) {
+    throw actual;
+  }
 }
 
-return-1;
-}
+// 11. Expected to throw an error:
+// assert.throws(block, Error_opt, message_opt);
 
-Buffer.prototype.includes=function includes(val,byteOffset,encoding){
-return this.indexOf(val,byteOffset,encoding)!==-1;
+assert.throws = function(block, /*optional*/error, /*optional*/message) {
+  _throws(true, block, error, message);
 };
 
-Buffer.prototype.indexOf=function indexOf(val,byteOffset,encoding){
-return bidirectionalIndexOf(this,val,byteOffset,encoding,true);
+// EXTENSION! This is annoying to write outside this module.
+assert.doesNotThrow = function(block, /*optional*/error, /*optional*/message) {
+  _throws(false, block, error, message);
 };
 
-Buffer.prototype.lastIndexOf=function lastIndexOf(val,byteOffset,encoding){
-return bidirectionalIndexOf(this,val,byteOffset,encoding,false);
-};
+assert.ifError = function(err) { if (err) throw err; };
 
-function hexWrite(buf,string,offset,length){
-offset=Number(offset)||0;
-var remaining=buf.length-offset;
-if(!length){
-length=remaining;
-}else{
-length=Number(length);
-if(length>remaining){
-length=remaining;
-}
-}
-
-
-var strLen=string.length;
-if(strLen%2!==0)throw new TypeError('Invalid hex string');
-
-if(length>strLen/2){
-length=strLen/2;
-}
-for(var i=0;i<length;++i){
-var parsed=parseInt(string.substr(i*2,2),16);
-if(isNaN(parsed))return i;
-buf[offset+i]=parsed;
-}
-return i;
-}
-
-function utf8Write(buf,string,offset,length){
-return blitBuffer(utf8ToBytes(string,buf.length-offset),buf,offset,length);
-}
-
-function asciiWrite(buf,string,offset,length){
-return blitBuffer(asciiToBytes(string),buf,offset,length);
-}
-
-function latin1Write(buf,string,offset,length){
-return asciiWrite(buf,string,offset,length);
-}
-
-function base64Write(buf,string,offset,length){
-return blitBuffer(base64ToBytes(string),buf,offset,length);
-}
-
-function ucs2Write(buf,string,offset,length){
-return blitBuffer(utf16leToBytes(string,buf.length-offset),buf,offset,length);
-}
-
-Buffer.prototype.write=function write(string,offset,length,encoding){
-
-if(offset===undefined){
-encoding='utf8';
-length=this.length;
-offset=0;
-
-}else if(length===undefined&&typeof offset==='string'){
-encoding=offset;
-length=this.length;
-offset=0;
-
-}else if(isFinite(offset)){
-offset=offset|0;
-if(isFinite(length)){
-length=length|0;
-if(encoding===undefined)encoding='utf8';
-}else{
-encoding=length;
-length=undefined;
-}
-
-}else{
-throw new Error(
-'Buffer.write(string, encoding, offset[, length]) is no longer supported');
-
-}
-
-var remaining=this.length-offset;
-if(length===undefined||length>remaining)length=remaining;
-
-if(string.length>0&&(length<0||offset<0)||offset>this.length){
-throw new RangeError('Attempt to write outside buffer bounds');
-}
-
-if(!encoding)encoding='utf8';
-
-var loweredCase=false;
-for(;;){
-switch(encoding){
-case'hex':
-return hexWrite(this,string,offset,length);
-
-case'utf8':
-case'utf-8':
-return utf8Write(this,string,offset,length);
-
-case'ascii':
-return asciiWrite(this,string,offset,length);
-
-case'latin1':
-case'binary':
-return latin1Write(this,string,offset,length);
-
-case'base64':
-
-return base64Write(this,string,offset,length);
-
-case'ucs2':
-case'ucs-2':
-case'utf16le':
-case'utf-16le':
-return ucs2Write(this,string,offset,length);
-
-default:
-if(loweredCase)throw new TypeError('Unknown encoding: '+encoding);
-encoding=(''+encoding).toLowerCase();
-loweredCase=true;}
-
-}
-};
-
-Buffer.prototype.toJSON=function toJSON(){
-return{
-type:'Buffer',
-data:Array.prototype.slice.call(this._arr||this,0)};
-
-};
-
-function base64Slice(buf,start,end){
-if(start===0&&end===buf.length){
-return base64.fromByteArray(buf);
-}else{
-return base64.fromByteArray(buf.slice(start,end));
-}
-}
-
-function utf8Slice(buf,start,end){
-end=Math.min(buf.length,end);
-var res=[];
-
-var i=start;
-while(i<end){
-var firstByte=buf[i];
-var codePoint=null;
-var bytesPerSequence=firstByte>0xEF?4:
-firstByte>0xDF?3:
-firstByte>0xBF?2:
-1;
-
-if(i+bytesPerSequence<=end){
-var secondByte,thirdByte,fourthByte,tempCodePoint;
-
-switch(bytesPerSequence){
-case 1:
-if(firstByte<0x80){
-codePoint=firstByte;
-}
-break;
-case 2:
-secondByte=buf[i+1];
-if((secondByte&0xC0)===0x80){
-tempCodePoint=(firstByte&0x1F)<<0x6|secondByte&0x3F;
-if(tempCodePoint>0x7F){
-codePoint=tempCodePoint;
-}
-}
-break;
-case 3:
-secondByte=buf[i+1];
-thirdByte=buf[i+2];
-if((secondByte&0xC0)===0x80&&(thirdByte&0xC0)===0x80){
-tempCodePoint=(firstByte&0xF)<<0xC|(secondByte&0x3F)<<0x6|thirdByte&0x3F;
-if(tempCodePoint>0x7FF&&(tempCodePoint<0xD800||tempCodePoint>0xDFFF)){
-codePoint=tempCodePoint;
-}
-}
-break;
-case 4:
-secondByte=buf[i+1];
-thirdByte=buf[i+2];
-fourthByte=buf[i+3];
-if((secondByte&0xC0)===0x80&&(thirdByte&0xC0)===0x80&&(fourthByte&0xC0)===0x80){
-tempCodePoint=(firstByte&0xF)<<0x12|(secondByte&0x3F)<<0xC|(thirdByte&0x3F)<<0x6|fourthByte&0x3F;
-if(tempCodePoint>0xFFFF&&tempCodePoint<0x110000){
-codePoint=tempCodePoint;
-}
-}}
-
-}
-
-if(codePoint===null){
-
-
-codePoint=0xFFFD;
-bytesPerSequence=1;
-}else if(codePoint>0xFFFF){
-
-codePoint-=0x10000;
-res.push(codePoint>>>10&0x3FF|0xD800);
-codePoint=0xDC00|codePoint&0x3FF;
-}
-
-res.push(codePoint);
-i+=bytesPerSequence;
-}
-
-return decodeCodePointsArray(res);
-}
-
-
-
-
-var MAX_ARGUMENTS_LENGTH=0x1000;
-
-function decodeCodePointsArray(codePoints){
-var len=codePoints.length;
-if(len<=MAX_ARGUMENTS_LENGTH){
-return String.fromCharCode.apply(String,codePoints);
-}
-
-
-var res='';
-var i=0;
-while(i<len){
-res+=String.fromCharCode.apply(
-String,
-codePoints.slice(i,i+=MAX_ARGUMENTS_LENGTH));
-
-}
-return res;
-}
-
-function asciiSlice(buf,start,end){
-var ret='';
-end=Math.min(buf.length,end);
-
-for(var i=start;i<end;++i){
-ret+=String.fromCharCode(buf[i]&0x7F);
-}
-return ret;
-}
-
-function latin1Slice(buf,start,end){
-var ret='';
-end=Math.min(buf.length,end);
-
-for(var i=start;i<end;++i){
-ret+=String.fromCharCode(buf[i]);
-}
-return ret;
-}
-
-function hexSlice(buf,start,end){
-var len=buf.length;
-
-if(!start||start<0)start=0;
-if(!end||end<0||end>len)end=len;
-
-var out='';
-for(var i=start;i<end;++i){
-out+=toHex(buf[i]);
-}
-return out;
-}
-
-function utf16leSlice(buf,start,end){
-var bytes=buf.slice(start,end);
-var res='';
-for(var i=0;i<bytes.length;i+=2){
-res+=String.fromCharCode(bytes[i]+bytes[i+1]*256);
-}
-return res;
-}
-
-Buffer.prototype.slice=function slice(start,end){
-var len=this.length;
-start=~~start;
-end=end===undefined?len:~~end;
-
-if(start<0){
-start+=len;
-if(start<0)start=0;
-}else if(start>len){
-start=len;
-}
-
-if(end<0){
-end+=len;
-if(end<0)end=0;
-}else if(end>len){
-end=len;
-}
-
-if(end<start)end=start;
-
-var newBuf;
-if(Buffer.TYPED_ARRAY_SUPPORT){
-newBuf=this.subarray(start,end);
-newBuf.__proto__=Buffer.prototype;
-}else{
-var sliceLen=end-start;
-newBuf=new Buffer(sliceLen,undefined);
-for(var i=0;i<sliceLen;++i){
-newBuf[i]=this[i+start];
-}
-}
-
-return newBuf;
-};
-
-
-
-
-function checkOffset(offset,ext,length){
-if(offset%1!==0||offset<0)throw new RangeError('offset is not uint');
-if(offset+ext>length)throw new RangeError('Trying to access beyond buffer length');
-}
-
-Buffer.prototype.readUIntLE=function readUIntLE(offset,byteLength,noAssert){
-offset=offset|0;
-byteLength=byteLength|0;
-if(!noAssert)checkOffset(offset,byteLength,this.length);
-
-var val=this[offset];
-var mul=1;
-var i=0;
-while(++i<byteLength&&(mul*=0x100)){
-val+=this[offset+i]*mul;
-}
-
-return val;
-};
-
-Buffer.prototype.readUIntBE=function readUIntBE(offset,byteLength,noAssert){
-offset=offset|0;
-byteLength=byteLength|0;
-if(!noAssert){
-checkOffset(offset,byteLength,this.length);
-}
-
-var val=this[offset+--byteLength];
-var mul=1;
-while(byteLength>0&&(mul*=0x100)){
-val+=this[offset+--byteLength]*mul;
-}
-
-return val;
-};
-
-Buffer.prototype.readUInt8=function readUInt8(offset,noAssert){
-if(!noAssert)checkOffset(offset,1,this.length);
-return this[offset];
-};
-
-Buffer.prototype.readUInt16LE=function readUInt16LE(offset,noAssert){
-if(!noAssert)checkOffset(offset,2,this.length);
-return this[offset]|this[offset+1]<<8;
-};
-
-Buffer.prototype.readUInt16BE=function readUInt16BE(offset,noAssert){
-if(!noAssert)checkOffset(offset,2,this.length);
-return this[offset]<<8|this[offset+1];
-};
-
-Buffer.prototype.readUInt32LE=function readUInt32LE(offset,noAssert){
-if(!noAssert)checkOffset(offset,4,this.length);
-
-return(this[offset]|
-this[offset+1]<<8|
-this[offset+2]<<16)+
-this[offset+3]*0x1000000;
-};
-
-Buffer.prototype.readUInt32BE=function readUInt32BE(offset,noAssert){
-if(!noAssert)checkOffset(offset,4,this.length);
-
-return this[offset]*0x1000000+(
-this[offset+1]<<16|
-this[offset+2]<<8|
-this[offset+3]);
-};
-
-Buffer.prototype.readIntLE=function readIntLE(offset,byteLength,noAssert){
-offset=offset|0;
-byteLength=byteLength|0;
-if(!noAssert)checkOffset(offset,byteLength,this.length);
-
-var val=this[offset];
-var mul=1;
-var i=0;
-while(++i<byteLength&&(mul*=0x100)){
-val+=this[offset+i]*mul;
-}
-mul*=0x80;
-
-if(val>=mul)val-=Math.pow(2,8*byteLength);
-
-return val;
-};
-
-Buffer.prototype.readIntBE=function readIntBE(offset,byteLength,noAssert){
-offset=offset|0;
-byteLength=byteLength|0;
-if(!noAssert)checkOffset(offset,byteLength,this.length);
-
-var i=byteLength;
-var mul=1;
-var val=this[offset+--i];
-while(i>0&&(mul*=0x100)){
-val+=this[offset+--i]*mul;
-}
-mul*=0x80;
-
-if(val>=mul)val-=Math.pow(2,8*byteLength);
-
-return val;
-};
-
-Buffer.prototype.readInt8=function readInt8(offset,noAssert){
-if(!noAssert)checkOffset(offset,1,this.length);
-if(!(this[offset]&0x80))return this[offset];
-return(0xff-this[offset]+1)*-1;
-};
-
-Buffer.prototype.readInt16LE=function readInt16LE(offset,noAssert){
-if(!noAssert)checkOffset(offset,2,this.length);
-var val=this[offset]|this[offset+1]<<8;
-return val&0x8000?val|0xFFFF0000:val;
-};
-
-Buffer.prototype.readInt16BE=function readInt16BE(offset,noAssert){
-if(!noAssert)checkOffset(offset,2,this.length);
-var val=this[offset+1]|this[offset]<<8;
-return val&0x8000?val|0xFFFF0000:val;
-};
-
-Buffer.prototype.readInt32LE=function readInt32LE(offset,noAssert){
-if(!noAssert)checkOffset(offset,4,this.length);
-
-return this[offset]|
-this[offset+1]<<8|
-this[offset+2]<<16|
-this[offset+3]<<24;
-};
-
-Buffer.prototype.readInt32BE=function readInt32BE(offset,noAssert){
-if(!noAssert)checkOffset(offset,4,this.length);
-
-return this[offset]<<24|
-this[offset+1]<<16|
-this[offset+2]<<8|
-this[offset+3];
-};
-
-Buffer.prototype.readFloatLE=function readFloatLE(offset,noAssert){
-if(!noAssert)checkOffset(offset,4,this.length);
-return ieee754.read(this,offset,true,23,4);
-};
-
-Buffer.prototype.readFloatBE=function readFloatBE(offset,noAssert){
-if(!noAssert)checkOffset(offset,4,this.length);
-return ieee754.read(this,offset,false,23,4);
-};
-
-Buffer.prototype.readDoubleLE=function readDoubleLE(offset,noAssert){
-if(!noAssert)checkOffset(offset,8,this.length);
-return ieee754.read(this,offset,true,52,8);
-};
-
-Buffer.prototype.readDoubleBE=function readDoubleBE(offset,noAssert){
-if(!noAssert)checkOffset(offset,8,this.length);
-return ieee754.read(this,offset,false,52,8);
-};
-
-function checkInt(buf,value,offset,ext,max,min){
-if(!Buffer.isBuffer(buf))throw new TypeError('"buffer" argument must be a Buffer instance');
-if(value>max||value<min)throw new RangeError('"value" argument is out of bounds');
-if(offset+ext>buf.length)throw new RangeError('Index out of range');
-}
-
-Buffer.prototype.writeUIntLE=function writeUIntLE(value,offset,byteLength,noAssert){
-value=+value;
-offset=offset|0;
-byteLength=byteLength|0;
-if(!noAssert){
-var maxBytes=Math.pow(2,8*byteLength)-1;
-checkInt(this,value,offset,byteLength,maxBytes,0);
-}
-
-var mul=1;
-var i=0;
-this[offset]=value&0xFF;
-while(++i<byteLength&&(mul*=0x100)){
-this[offset+i]=value/mul&0xFF;
-}
-
-return offset+byteLength;
-};
-
-Buffer.prototype.writeUIntBE=function writeUIntBE(value,offset,byteLength,noAssert){
-value=+value;
-offset=offset|0;
-byteLength=byteLength|0;
-if(!noAssert){
-var maxBytes=Math.pow(2,8*byteLength)-1;
-checkInt(this,value,offset,byteLength,maxBytes,0);
-}
-
-var i=byteLength-1;
-var mul=1;
-this[offset+i]=value&0xFF;
-while(--i>=0&&(mul*=0x100)){
-this[offset+i]=value/mul&0xFF;
-}
-
-return offset+byteLength;
-};
-
-Buffer.prototype.writeUInt8=function writeUInt8(value,offset,noAssert){
-value=+value;
-offset=offset|0;
-if(!noAssert)checkInt(this,value,offset,1,0xff,0);
-if(!Buffer.TYPED_ARRAY_SUPPORT)value=Math.floor(value);
-this[offset]=value&0xff;
-return offset+1;
-};
-
-function objectWriteUInt16(buf,value,offset,littleEndian){
-if(value<0)value=0xffff+value+1;
-for(var i=0,j=Math.min(buf.length-offset,2);i<j;++i){
-buf[offset+i]=(value&0xff<<8*(littleEndian?i:1-i))>>>
-(littleEndian?i:1-i)*8;
-}
-}
-
-Buffer.prototype.writeUInt16LE=function writeUInt16LE(value,offset,noAssert){
-value=+value;
-offset=offset|0;
-if(!noAssert)checkInt(this,value,offset,2,0xffff,0);
-if(Buffer.TYPED_ARRAY_SUPPORT){
-this[offset]=value&0xff;
-this[offset+1]=value>>>8;
-}else{
-objectWriteUInt16(this,value,offset,true);
-}
-return offset+2;
-};
-
-Buffer.prototype.writeUInt16BE=function writeUInt16BE(value,offset,noAssert){
-value=+value;
-offset=offset|0;
-if(!noAssert)checkInt(this,value,offset,2,0xffff,0);
-if(Buffer.TYPED_ARRAY_SUPPORT){
-this[offset]=value>>>8;
-this[offset+1]=value&0xff;
-}else{
-objectWriteUInt16(this,value,offset,false);
-}
-return offset+2;
-};
-
-function objectWriteUInt32(buf,value,offset,littleEndian){
-if(value<0)value=0xffffffff+value+1;
-for(var i=0,j=Math.min(buf.length-offset,4);i<j;++i){
-buf[offset+i]=value>>>(littleEndian?i:3-i)*8&0xff;
-}
-}
-
-Buffer.prototype.writeUInt32LE=function writeUInt32LE(value,offset,noAssert){
-value=+value;
-offset=offset|0;
-if(!noAssert)checkInt(this,value,offset,4,0xffffffff,0);
-if(Buffer.TYPED_ARRAY_SUPPORT){
-this[offset+3]=value>>>24;
-this[offset+2]=value>>>16;
-this[offset+1]=value>>>8;
-this[offset]=value&0xff;
-}else{
-objectWriteUInt32(this,value,offset,true);
-}
-return offset+4;
-};
-
-Buffer.prototype.writeUInt32BE=function writeUInt32BE(value,offset,noAssert){
-value=+value;
-offset=offset|0;
-if(!noAssert)checkInt(this,value,offset,4,0xffffffff,0);
-if(Buffer.TYPED_ARRAY_SUPPORT){
-this[offset]=value>>>24;
-this[offset+1]=value>>>16;
-this[offset+2]=value>>>8;
-this[offset+3]=value&0xff;
-}else{
-objectWriteUInt32(this,value,offset,false);
-}
-return offset+4;
-};
-
-Buffer.prototype.writeIntLE=function writeIntLE(value,offset,byteLength,noAssert){
-value=+value;
-offset=offset|0;
-if(!noAssert){
-var limit=Math.pow(2,8*byteLength-1);
-
-checkInt(this,value,offset,byteLength,limit-1,-limit);
-}
-
-var i=0;
-var mul=1;
-var sub=0;
-this[offset]=value&0xFF;
-while(++i<byteLength&&(mul*=0x100)){
-if(value<0&&sub===0&&this[offset+i-1]!==0){
-sub=1;
-}
-this[offset+i]=(value/mul>>0)-sub&0xFF;
-}
-
-return offset+byteLength;
-};
-
-Buffer.prototype.writeIntBE=function writeIntBE(value,offset,byteLength,noAssert){
-value=+value;
-offset=offset|0;
-if(!noAssert){
-var limit=Math.pow(2,8*byteLength-1);
-
-checkInt(this,value,offset,byteLength,limit-1,-limit);
-}
-
-var i=byteLength-1;
-var mul=1;
-var sub=0;
-this[offset+i]=value&0xFF;
-while(--i>=0&&(mul*=0x100)){
-if(value<0&&sub===0&&this[offset+i+1]!==0){
-sub=1;
-}
-this[offset+i]=(value/mul>>0)-sub&0xFF;
-}
-
-return offset+byteLength;
-};
-
-Buffer.prototype.writeInt8=function writeInt8(value,offset,noAssert){
-value=+value;
-offset=offset|0;
-if(!noAssert)checkInt(this,value,offset,1,0x7f,-0x80);
-if(!Buffer.TYPED_ARRAY_SUPPORT)value=Math.floor(value);
-if(value<0)value=0xff+value+1;
-this[offset]=value&0xff;
-return offset+1;
-};
-
-Buffer.prototype.writeInt16LE=function writeInt16LE(value,offset,noAssert){
-value=+value;
-offset=offset|0;
-if(!noAssert)checkInt(this,value,offset,2,0x7fff,-0x8000);
-if(Buffer.TYPED_ARRAY_SUPPORT){
-this[offset]=value&0xff;
-this[offset+1]=value>>>8;
-}else{
-objectWriteUInt16(this,value,offset,true);
-}
-return offset+2;
-};
-
-Buffer.prototype.writeInt16BE=function writeInt16BE(value,offset,noAssert){
-value=+value;
-offset=offset|0;
-if(!noAssert)checkInt(this,value,offset,2,0x7fff,-0x8000);
-if(Buffer.TYPED_ARRAY_SUPPORT){
-this[offset]=value>>>8;
-this[offset+1]=value&0xff;
-}else{
-objectWriteUInt16(this,value,offset,false);
-}
-return offset+2;
-};
-
-Buffer.prototype.writeInt32LE=function writeInt32LE(value,offset,noAssert){
-value=+value;
-offset=offset|0;
-if(!noAssert)checkInt(this,value,offset,4,0x7fffffff,-0x80000000);
-if(Buffer.TYPED_ARRAY_SUPPORT){
-this[offset]=value&0xff;
-this[offset+1]=value>>>8;
-this[offset+2]=value>>>16;
-this[offset+3]=value>>>24;
-}else{
-objectWriteUInt32(this,value,offset,true);
-}
-return offset+4;
-};
-
-Buffer.prototype.writeInt32BE=function writeInt32BE(value,offset,noAssert){
-value=+value;
-offset=offset|0;
-if(!noAssert)checkInt(this,value,offset,4,0x7fffffff,-0x80000000);
-if(value<0)value=0xffffffff+value+1;
-if(Buffer.TYPED_ARRAY_SUPPORT){
-this[offset]=value>>>24;
-this[offset+1]=value>>>16;
-this[offset+2]=value>>>8;
-this[offset+3]=value&0xff;
-}else{
-objectWriteUInt32(this,value,offset,false);
-}
-return offset+4;
-};
-
-function checkIEEE754(buf,value,offset,ext,max,min){
-if(offset+ext>buf.length)throw new RangeError('Index out of range');
-if(offset<0)throw new RangeError('Index out of range');
-}
-
-function writeFloat(buf,value,offset,littleEndian,noAssert){
-if(!noAssert){
-checkIEEE754(buf,value,offset,4,3.4028234663852886e+38,-3.4028234663852886e+38);
-}
-ieee754.write(buf,value,offset,littleEndian,23,4);
-return offset+4;
-}
-
-Buffer.prototype.writeFloatLE=function writeFloatLE(value,offset,noAssert){
-return writeFloat(this,value,offset,true,noAssert);
-};
-
-Buffer.prototype.writeFloatBE=function writeFloatBE(value,offset,noAssert){
-return writeFloat(this,value,offset,false,noAssert);
-};
-
-function writeDouble(buf,value,offset,littleEndian,noAssert){
-if(!noAssert){
-checkIEEE754(buf,value,offset,8,1.7976931348623157E+308,-1.7976931348623157E+308);
-}
-ieee754.write(buf,value,offset,littleEndian,52,8);
-return offset+8;
-}
-
-Buffer.prototype.writeDoubleLE=function writeDoubleLE(value,offset,noAssert){
-return writeDouble(this,value,offset,true,noAssert);
-};
-
-Buffer.prototype.writeDoubleBE=function writeDoubleBE(value,offset,noAssert){
-return writeDouble(this,value,offset,false,noAssert);
-};
-
-
-Buffer.prototype.copy=function copy(target,targetStart,start,end){
-if(!start)start=0;
-if(!end&&end!==0)end=this.length;
-if(targetStart>=target.length)targetStart=target.length;
-if(!targetStart)targetStart=0;
-if(end>0&&end<start)end=start;
-
-
-if(end===start)return 0;
-if(target.length===0||this.length===0)return 0;
-
-
-if(targetStart<0){
-throw new RangeError('targetStart out of bounds');
-}
-if(start<0||start>=this.length)throw new RangeError('sourceStart out of bounds');
-if(end<0)throw new RangeError('sourceEnd out of bounds');
-
-
-if(end>this.length)end=this.length;
-if(target.length-targetStart<end-start){
-end=target.length-targetStart+start;
-}
-
-var len=end-start;
-var i;
-
-if(this===target&&start<targetStart&&targetStart<end){
-
-for(i=len-1;i>=0;--i){
-target[i+targetStart]=this[i+start];
-}
-}else if(len<1000||!Buffer.TYPED_ARRAY_SUPPORT){
-
-for(i=0;i<len;++i){
-target[i+targetStart]=this[i+start];
-}
-}else{
-Uint8Array.prototype.set.call(
-target,
-this.subarray(start,start+len),
-targetStart);
-
-}
-
-return len;
-};
-
-
-
-
-
-Buffer.prototype.fill=function fill(val,start,end,encoding){
-
-if(typeof val==='string'){
-if(typeof start==='string'){
-encoding=start;
-start=0;
-end=this.length;
-}else if(typeof end==='string'){
-encoding=end;
-end=this.length;
-}
-if(val.length===1){
-var code=val.charCodeAt(0);
-if(code<256){
-val=code;
-}
-}
-if(encoding!==undefined&&typeof encoding!=='string'){
-throw new TypeError('encoding must be a string');
-}
-if(typeof encoding==='string'&&!Buffer.isEncoding(encoding)){
-throw new TypeError('Unknown encoding: '+encoding);
-}
-}else if(typeof val==='number'){
-val=val&255;
-}
-
-
-if(start<0||this.length<start||this.length<end){
-throw new RangeError('Out of range index');
-}
-
-if(end<=start){
-return this;
-}
-
-start=start>>>0;
-end=end===undefined?this.length:end>>>0;
-
-if(!val)val=0;
-
-var i;
-if(typeof val==='number'){
-for(i=start;i<end;++i){
-this[i]=val;
-}
-}else{
-var bytes=Buffer.isBuffer(val)?
-val:
-utf8ToBytes(new Buffer(val,encoding).toString());
-var len=bytes.length;
-for(i=0;i<end-start;++i){
-this[i+start]=bytes[i%len];
-}
-}
-
-return this;
-};
-
-
-
-
-var INVALID_BASE64_RE=/[^+\/0-9A-Za-z-_]/g;
-
-function base64clean(str){
-
-str=stringtrim(str).replace(INVALID_BASE64_RE,'');
-
-if(str.length<2)return'';
-
-while(str.length%4!==0){
-str=str+'=';
-}
-return str;
-}
-
-function stringtrim(str){
-if(str.trim)return str.trim();
-return str.replace(/^\s+|\s+$/g,'');
-}
-
-function toHex(n){
-if(n<16)return'0'+n.toString(16);
-return n.toString(16);
-}
-
-function utf8ToBytes(string,units){
-units=units||Infinity;
-var codePoint;
-var length=string.length;
-var leadSurrogate=null;
-var bytes=[];
-
-for(var i=0;i<length;++i){
-codePoint=string.charCodeAt(i);
-
-
-if(codePoint>0xD7FF&&codePoint<0xE000){
-
-if(!leadSurrogate){
-
-if(codePoint>0xDBFF){
-
-if((units-=3)>-1)bytes.push(0xEF,0xBF,0xBD);
-continue;
-}else if(i+1===length){
-
-if((units-=3)>-1)bytes.push(0xEF,0xBF,0xBD);
-continue;
-}
-
-
-leadSurrogate=codePoint;
-
-continue;
-}
-
-
-if(codePoint<0xDC00){
-if((units-=3)>-1)bytes.push(0xEF,0xBF,0xBD);
-leadSurrogate=codePoint;
-continue;
-}
-
-
-codePoint=(leadSurrogate-0xD800<<10|codePoint-0xDC00)+0x10000;
-}else if(leadSurrogate){
-
-if((units-=3)>-1)bytes.push(0xEF,0xBF,0xBD);
-}
-
-leadSurrogate=null;
-
-
-if(codePoint<0x80){
-if((units-=1)<0)break;
-bytes.push(codePoint);
-}else if(codePoint<0x800){
-if((units-=2)<0)break;
-bytes.push(
-codePoint>>0x6|0xC0,
-codePoint&0x3F|0x80);
-
-}else if(codePoint<0x10000){
-if((units-=3)<0)break;
-bytes.push(
-codePoint>>0xC|0xE0,
-codePoint>>0x6&0x3F|0x80,
-codePoint&0x3F|0x80);
-
-}else if(codePoint<0x110000){
-if((units-=4)<0)break;
-bytes.push(
-codePoint>>0x12|0xF0,
-codePoint>>0xC&0x3F|0x80,
-codePoint>>0x6&0x3F|0x80,
-codePoint&0x3F|0x80);
-
-}else{
-throw new Error('Invalid code point');
-}
-}
-
-return bytes;
-}
-
-function asciiToBytes(str){
-var byteArray=[];
-for(var i=0;i<str.length;++i){
-
-byteArray.push(str.charCodeAt(i)&0xFF);
-}
-return byteArray;
-}
-
-function utf16leToBytes(str,units){
-var c,hi,lo;
-var byteArray=[];
-for(var i=0;i<str.length;++i){
-if((units-=2)<0)break;
-
-c=str.charCodeAt(i);
-hi=c>>8;
-lo=c%256;
-byteArray.push(lo);
-byteArray.push(hi);
-}
-
-return byteArray;
-}
-
-function base64ToBytes(str){
-return base64.toByteArray(base64clean(str));
-}
-
-function blitBuffer(src,dst,offset,length){
-for(var i=0;i<length;++i){
-if(i+offset>=dst.length||i>=src.length)break;
-dst[i+offset]=src[i];
-}
-return i;
-}
-
-function isnan(val){
-return val!==val;
-}
-
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"base64-js":197,"ieee754":201,"isarray":202}],200:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-function EventEmitter(){
-this._events=this._events||{};
-this._maxListeners=this._maxListeners||undefined;
-}
-module.exports=EventEmitter;
-
-
-EventEmitter.EventEmitter=EventEmitter;
-
-EventEmitter.prototype._events=undefined;
-EventEmitter.prototype._maxListeners=undefined;
-
-
-
-EventEmitter.defaultMaxListeners=10;
-
-
-
-EventEmitter.prototype.setMaxListeners=function(n){
-if(!isNumber(n)||n<0||isNaN(n))
-throw TypeError('n must be a positive number');
-this._maxListeners=n;
-return this;
-};
-
-EventEmitter.prototype.emit=function(type){
-var er,handler,len,args,i,listeners;
-
-if(!this._events)
-this._events={};
-
-
-if(type==='error'){
-if(!this._events.error||
-isObject(this._events.error)&&!this._events.error.length){
-er=arguments[1];
-if(er instanceof Error){
-throw er;
-}else{
-
-var err=new Error('Uncaught, unspecified "error" event. ('+er+')');
-err.context=er;
-throw err;
-}
-}
-}
-
-handler=this._events[type];
-
-if(isUndefined(handler))
-return false;
-
-if(isFunction(handler)){
-switch(arguments.length){
-
-case 1:
-handler.call(this);
-break;
-case 2:
-handler.call(this,arguments[1]);
-break;
-case 3:
-handler.call(this,arguments[1],arguments[2]);
-break;
-
-default:
-args=Array.prototype.slice.call(arguments,1);
-handler.apply(this,args);}
-
-}else if(isObject(handler)){
-args=Array.prototype.slice.call(arguments,1);
-listeners=handler.slice();
-len=listeners.length;
-for(i=0;i<len;i++)
-listeners[i].apply(this,args);
-}
-
-return true;
+var objectKeys = Object.keys || function (obj) {
+  var keys = [];
+  for (var key in obj) {
+    if (hasOwn.call(obj, key)) keys.push(key);
+  }
+  return keys;
 };
 
-EventEmitter.prototype.addListener=function(type,listener){
-var m;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"util/":233}],199:[function(require,module,exports){
+'use strict'
 
-if(!isFunction(listener))
-throw TypeError('listener must be a function');
+exports.byteLength = byteLength
+exports.toByteArray = toByteArray
+exports.fromByteArray = fromByteArray
 
-if(!this._events)
-this._events={};
+var lookup = []
+var revLookup = []
+var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array
 
-
-
-if(this._events.newListener)
-this.emit('newListener',type,
-isFunction(listener.listener)?
-listener.listener:listener);
-
-if(!this._events[type])
-
-this._events[type]=listener;else
-if(isObject(this._events[type]))
-
-this._events[type].push(listener);else
-
-
-this._events[type]=[this._events[type],listener];
-
-
-if(isObject(this._events[type])&&!this._events[type].warned){
-if(!isUndefined(this._maxListeners)){
-m=this._maxListeners;
-}else{
-m=EventEmitter.defaultMaxListeners;
+var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
+for (var i = 0, len = code.length; i < len; ++i) {
+  lookup[i] = code[i]
+  revLookup[code.charCodeAt(i)] = i
 }
 
-if(m&&m>0&&this._events[type].length>m){
-this._events[type].warned=true;
-console.error('(node) warning: possible EventEmitter memory '+
-'leak detected. %d listeners added. '+
-'Use emitter.setMaxListeners() to increase limit.',
-this._events[type].length);
-if(typeof console.trace==='function'){
+revLookup['-'.charCodeAt(0)] = 62
+revLookup['_'.charCodeAt(0)] = 63
 
-console.trace();
-}
-}
-}
+function placeHoldersCount (b64) {
+  var len = b64.length
+  if (len % 4 > 0) {
+    throw new Error('Invalid string. Length must be a multiple of 4')
+  }
 
-return this;
-};
-
-EventEmitter.prototype.on=EventEmitter.prototype.addListener;
-
-EventEmitter.prototype.once=function(type,listener){
-if(!isFunction(listener))
-throw TypeError('listener must be a function');
-
-var fired=false;
-
-function g(){
-this.removeListener(type,g);
-
-if(!fired){
-fired=true;
-listener.apply(this,arguments);
-}
+  // the number of equal signs (place holders)
+  // if there are two placeholders, than the two characters before it
+  // represent one byte
+  // if there is only one, then the three characters before it represent 2 bytes
+  // this is just a cheap hack to not do indexOf twice
+  return b64[len - 2] === '=' ? 2 : b64[len - 1] === '=' ? 1 : 0
 }
 
-g.listener=listener;
-this.on(type,g);
-
-return this;
-};
-
-
-EventEmitter.prototype.removeListener=function(type,listener){
-var list,position,length,i;
-
-if(!isFunction(listener))
-throw TypeError('listener must be a function');
-
-if(!this._events||!this._events[type])
-return this;
-
-list=this._events[type];
-length=list.length;
-position=-1;
-
-if(list===listener||
-isFunction(list.listener)&&list.listener===listener){
-delete this._events[type];
-if(this._events.removeListener)
-this.emit('removeListener',type,listener);
-
-}else if(isObject(list)){
-for(i=length;i-->0;){
-if(list[i]===listener||
-list[i].listener&&list[i].listener===listener){
-position=i;
-break;
-}
+function byteLength (b64) {
+  // base64 is 4/3 + up to two characters of the original data
+  return b64.length * 3 / 4 - placeHoldersCount(b64)
 }
 
-if(position<0)
-return this;
+function toByteArray (b64) {
+  var i, j, l, tmp, placeHolders, arr
+  var len = b64.length
+  placeHolders = placeHoldersCount(b64)
 
-if(list.length===1){
-list.length=0;
-delete this._events[type];
-}else{
-list.splice(position,1);
-}
-
-if(this._events.removeListener)
-this.emit('removeListener',type,listener);
-}
+  arr = new Arr(len * 3 / 4 - placeHolders)
 
-return this;
-};
+  // if there are placeholders, only get up to the last complete 4 chars
+  l = placeHolders > 0 ? len - 4 : len
 
-EventEmitter.prototype.removeAllListeners=function(type){
-var key,listeners;
+  var L = 0
 
-if(!this._events)
-return this;
+  for (i = 0, j = 0; i < l; i += 4, j += 3) {
+    tmp = (revLookup[b64.charCodeAt(i)] << 18) | (revLookup[b64.charCodeAt(i + 1)] << 12) | (revLookup[b64.charCodeAt(i + 2)] << 6) | revLookup[b64.charCodeAt(i + 3)]
+    arr[L++] = (tmp >> 16) & 0xFF
+    arr[L++] = (tmp >> 8) & 0xFF
+    arr[L++] = tmp & 0xFF
+  }
 
+  if (placeHolders === 2) {
+    tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4)
+    arr[L++] = tmp & 0xFF
+  } else if (placeHolders === 1) {
+    tmp = (revLookup[b64.charCodeAt(i)] << 10) | (revLookup[b64.charCodeAt(i + 1)] << 4) | (revLookup[b64.charCodeAt(i + 2)] >> 2)
+    arr[L++] = (tmp >> 8) & 0xFF
+    arr[L++] = tmp & 0xFF
+  }
 
-if(!this._events.removeListener){
-if(arguments.length===0)
-this._events={};else
-if(this._events[type])
-delete this._events[type];
-return this;
+  return arr
 }
 
-
-if(arguments.length===0){
-for(key in this._events){
-if(key==='removeListener')continue;
-this.removeAllListeners(key);
-}
-this.removeAllListeners('removeListener');
-this._events={};
-return this;
+function tripletToBase64 (num) {
+  return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F]
 }
 
-listeners=this._events[type];
-
-if(isFunction(listeners)){
-this.removeListener(type,listeners);
-}else if(listeners){
-
-while(listeners.length)
-this.removeListener(type,listeners[listeners.length-1]);
+function encodeChunk (uint8, start, end) {
+  var tmp
+  var output = []
+  for (var i = start; i < end; i += 3) {
+    tmp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2])
+    output.push(tripletToBase64(tmp))
+  }
+  return output.join('')
 }
-delete this._events[type];
 
-return this;
-};
-
-EventEmitter.prototype.listeners=function(type){
-var ret;
-if(!this._events||!this._events[type])
-ret=[];else
-if(isFunction(this._events[type]))
-ret=[this._events[type]];else
-
-ret=this._events[type].slice();
-return ret;
-};
-
-EventEmitter.prototype.listenerCount=function(type){
-if(this._events){
-var evlistener=this._events[type];
-
-if(isFunction(evlistener))
-return 1;else
-if(evlistener)
-return evlistener.length;
-}
-return 0;
-};
+function fromByteArray (uint8) {
+  var tmp
+  var len = uint8.length
+  var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes
+  var output = ''
+  var parts = []
+  var maxChunkLength = 16383 // must be multiple of 3
 
-EventEmitter.listenerCount=function(emitter,type){
-return emitter.listenerCount(type);
-};
+  // go through the array every three bytes, we'll deal with trailing stuff later
+  for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
+    parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)))
+  }
 
-function isFunction(arg){
-return typeof arg==='function';
-}
+  // pad the end with zeros, but make sure to not forget the extra bytes
+  if (extraBytes === 1) {
+    tmp = uint8[len - 1]
+    output += lookup[tmp >> 2]
+    output += lookup[(tmp << 4) & 0x3F]
+    output += '=='
+  } else if (extraBytes === 2) {
+    tmp = (uint8[len - 2] << 8) + (uint8[len - 1])
+    output += lookup[tmp >> 10]
+    output += lookup[(tmp >> 4) & 0x3F]
+    output += lookup[(tmp << 2) & 0x3F]
+    output += '='
+  }
 
-function isNumber(arg){
-return typeof arg==='number';
-}
+  parts.push(output)
 
-function isObject(arg){
-return typeof arg==='object'&&arg!==null;
+  return parts.join('')
 }
 
-function isUndefined(arg){
-return arg===void 0;
-}
+},{}],200:[function(require,module,exports){
 
 },{}],201:[function(require,module,exports){
-exports.read=function(buffer,offset,isLE,mLen,nBytes){
-var e,m;
-var eLen=nBytes*8-mLen-1;
-var eMax=(1<<eLen)-1;
-var eBias=eMax>>1;
-var nBits=-7;
-var i=isLE?nBytes-1:0;
-var d=isLE?-1:1;
-var s=buffer[offset+i];
+(function (process,Buffer){
+var msg = require('pako/lib/zlib/messages');
+var zstream = require('pako/lib/zlib/zstream');
+var zlib_deflate = require('pako/lib/zlib/deflate.js');
+var zlib_inflate = require('pako/lib/zlib/inflate.js');
+var constants = require('pako/lib/zlib/constants');
 
-i+=d;
-
-e=s&(1<<-nBits)-1;
-s>>=-nBits;
-nBits+=eLen;
-for(;nBits>0;e=e*256+buffer[offset+i],i+=d,nBits-=8){}
-
-m=e&(1<<-nBits)-1;
-e>>=-nBits;
-nBits+=mLen;
-for(;nBits>0;m=m*256+buffer[offset+i],i+=d,nBits-=8){}
-
-if(e===0){
-e=1-eBias;
-}else if(e===eMax){
-return m?NaN:(s?-1:1)*Infinity;
-}else{
-m=m+Math.pow(2,mLen);
-e=e-eBias;
+for (var key in constants) {
+  exports[key] = constants[key];
 }
-return(s?-1:1)*m*Math.pow(2,e-mLen);
+
+// zlib modes
+exports.NONE = 0;
+exports.DEFLATE = 1;
+exports.INFLATE = 2;
+exports.GZIP = 3;
+exports.GUNZIP = 4;
+exports.DEFLATERAW = 5;
+exports.INFLATERAW = 6;
+exports.UNZIP = 7;
+
+/**
+ * Emulate Node's zlib C++ layer for use by the JS layer in index.js
+ */
+function Zlib(mode) {
+  if (mode < exports.DEFLATE || mode > exports.UNZIP)
+    throw new TypeError("Bad argument");
+    
+  this.mode = mode;
+  this.init_done = false;
+  this.write_in_progress = false;
+  this.pending_close = false;
+  this.windowBits = 0;
+  this.level = 0;
+  this.memLevel = 0;
+  this.strategy = 0;
+  this.dictionary = null;
+}
+
+Zlib.prototype.init = function(windowBits, level, memLevel, strategy, dictionary) {
+  this.windowBits = windowBits;
+  this.level = level;
+  this.memLevel = memLevel;
+  this.strategy = strategy;
+  // dictionary not supported.
+  
+  if (this.mode === exports.GZIP || this.mode === exports.GUNZIP)
+    this.windowBits += 16;
+    
+  if (this.mode === exports.UNZIP)
+    this.windowBits += 32;
+    
+  if (this.mode === exports.DEFLATERAW || this.mode === exports.INFLATERAW)
+    this.windowBits = -this.windowBits;
+    
+  this.strm = new zstream();
+  
+  switch (this.mode) {
+    case exports.DEFLATE:
+    case exports.GZIP:
+    case exports.DEFLATERAW:
+      var status = zlib_deflate.deflateInit2(
+        this.strm,
+        this.level,
+        exports.Z_DEFLATED,
+        this.windowBits,
+        this.memLevel,
+        this.strategy
+      );
+      break;
+    case exports.INFLATE:
+    case exports.GUNZIP:
+    case exports.INFLATERAW:
+    case exports.UNZIP:
+      var status  = zlib_inflate.inflateInit2(
+        this.strm,
+        this.windowBits
+      );
+      break;
+    default:
+      throw new Error("Unknown mode " + this.mode);
+  }
+  
+  if (status !== exports.Z_OK) {
+    this._error(status);
+    return;
+  }
+  
+  this.write_in_progress = false;
+  this.init_done = true;
 };
 
-exports.write=function(buffer,value,offset,isLE,mLen,nBytes){
-var e,m,c;
-var eLen=nBytes*8-mLen-1;
-var eMax=(1<<eLen)-1;
-var eBias=eMax>>1;
-var rt=mLen===23?Math.pow(2,-24)-Math.pow(2,-77):0;
-var i=isLE?0:nBytes-1;
-var d=isLE?1:-1;
-var s=value<0||value===0&&1/value<0?1:0;
-
-value=Math.abs(value);
-
-if(isNaN(value)||value===Infinity){
-m=isNaN(value)?1:0;
-e=eMax;
-}else{
-e=Math.floor(Math.log(value)/Math.LN2);
-if(value*(c=Math.pow(2,-e))<1){
-e--;
-c*=2;
-}
-if(e+eBias>=1){
-value+=rt/c;
-}else{
-value+=rt*Math.pow(2,1-eBias);
-}
-if(value*c>=2){
-e++;
-c/=2;
-}
-
-if(e+eBias>=eMax){
-m=0;
-e=eMax;
-}else if(e+eBias>=1){
-m=(value*c-1)*Math.pow(2,mLen);
-e=e+eBias;
-}else{
-m=value*Math.pow(2,eBias-1)*Math.pow(2,mLen);
-e=0;
-}
-}
-
-for(;mLen>=8;buffer[offset+i]=m&0xff,i+=d,m/=256,mLen-=8){}
-
-e=e<<mLen|m;
-eLen+=mLen;
-for(;eLen>0;buffer[offset+i]=e&0xff,i+=d,e/=256,eLen-=8){}
-
-buffer[offset+i-d]|=s*128;
+Zlib.prototype.params = function() {
+  throw new Error("deflateParams Not supported");
 };
 
-},{}],202:[function(require,module,exports){
-var toString={}.toString;
-
-module.exports=Array.isArray||function(arr){
-return toString.call(arr)=='[object Array]';
+Zlib.prototype._writeCheck = function() {
+  if (!this.init_done)
+    throw new Error("write before init");
+    
+  if (this.mode === exports.NONE)
+    throw new Error("already finalized");
+    
+  if (this.write_in_progress)
+    throw new Error("write already in progress");
+    
+  if (this.pending_close)
+    throw new Error("close is pending");
 };
 
-},{}],203:[function(require,module,exports){
-(function(process){
+Zlib.prototype.write = function(flush, input, in_off, in_len, out, out_off, out_len) {    
+  this._writeCheck();
+  this.write_in_progress = true;
+  
+  var self = this;
+  process.nextTick(function() {
+    self.write_in_progress = false;
+    var res = self._write(flush, input, in_off, in_len, out, out_off, out_len);
+    self.callback(res[0], res[1]);
+    
+    if (self.pending_close)
+      self.close();
+  });
+  
+  return this;
+};
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-function normalizeArray(parts,allowAboveRoot){
-
-var up=0;
-for(var i=parts.length-1;i>=0;i--){
-var last=parts[i];
-if(last==='.'){
-parts.splice(i,1);
-}else if(last==='..'){
-parts.splice(i,1);
-up++;
-}else if(up){
-parts.splice(i,1);
-up--;
-}
+// set method for Node buffers, used by pako
+function bufferSet(data, offset) {
+  for (var i = 0; i < data.length; i++) {
+    this[offset + i] = data[i];
+  }
 }
 
+Zlib.prototype.writeSync = function(flush, input, in_off, in_len, out, out_off, out_len) {
+  this._writeCheck();
+  return this._write(flush, input, in_off, in_len, out, out_off, out_len);
+};
 
-if(allowAboveRoot){
-for(;up--;up){
-parts.unshift('..');
-}
-}
+Zlib.prototype._write = function(flush, input, in_off, in_len, out, out_off, out_len) {
+  this.write_in_progress = true;
+  
+  if (flush !== exports.Z_NO_FLUSH &&
+      flush !== exports.Z_PARTIAL_FLUSH &&
+      flush !== exports.Z_SYNC_FLUSH &&
+      flush !== exports.Z_FULL_FLUSH &&
+      flush !== exports.Z_FINISH &&
+      flush !== exports.Z_BLOCK) {
+    throw new Error("Invalid flush value");
+  }
+  
+  if (input == null) {
+    input = new Buffer(0);
+    in_len = 0;
+    in_off = 0;
+  }
+  
+  if (out._set)
+    out.set = out._set;
+  else
+    out.set = bufferSet;
+  
+  var strm = this.strm;
+  strm.avail_in = in_len;
+  strm.input = input;
+  strm.next_in = in_off;
+  strm.avail_out = out_len;
+  strm.output = out;
+  strm.next_out = out_off;
+  
+  switch (this.mode) {
+    case exports.DEFLATE:
+    case exports.GZIP:
+    case exports.DEFLATERAW:
+      var status = zlib_deflate.deflate(strm, flush);
+      break;
+    case exports.UNZIP:
+    case exports.INFLATE:
+    case exports.GUNZIP:
+    case exports.INFLATERAW:
+      var status = zlib_inflate.inflate(strm, flush);
+      break;
+    default:
+      throw new Error("Unknown mode " + this.mode);
+  }
+  
+  if (status !== exports.Z_STREAM_END && status !== exports.Z_OK) {
+    this._error(status);
+  }
+  
+  this.write_in_progress = false;
+  return [strm.avail_in, strm.avail_out];
+};
 
-return parts;
-}
+Zlib.prototype.close = function() {
+  if (this.write_in_progress) {
+    this.pending_close = true;
+    return;
+  }
+  
+  this.pending_close = false;
+  
+  if (this.mode === exports.DEFLATE || this.mode === exports.GZIP || this.mode === exports.DEFLATERAW) {
+    zlib_deflate.deflateEnd(this.strm);
+  } else {
+    zlib_inflate.inflateEnd(this.strm);
+  }
+  
+  this.mode = exports.NONE;
+};
 
+Zlib.prototype.reset = function() {
+  switch (this.mode) {
+    case exports.DEFLATE:
+    case exports.DEFLATERAW:
+      var status = zlib_deflate.deflateReset(this.strm);
+      break;
+    case exports.INFLATE:
+    case exports.INFLATERAW:
+      var status = zlib_inflate.inflateReset(this.strm);
+      break;
+  }
+  
+  if (status !== exports.Z_OK) {
+    this._error(status);
+  }
+};
 
+Zlib.prototype._error = function(status) {
+  this.onerror(msg[status] + ': ' + this.strm.msg, status);
+  
+  this.write_in_progress = false;
+  if (this.pending_close)
+    this.close();
+};
 
-var splitPathRe=
-/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
-var splitPath=function(filename){
-return splitPathRe.exec(filename).slice(1);
+exports.Zlib = Zlib;
+
+}).call(this,require('_process'),require("buffer").Buffer)
+},{"_process":222,"buffer":205,"pako/lib/zlib/constants":214,"pako/lib/zlib/deflate.js":216,"pako/lib/zlib/inflate.js":203,"pako/lib/zlib/messages":217,"pako/lib/zlib/zstream":219}],202:[function(require,module,exports){
+(function (process,Buffer){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+var Transform = require('_stream_transform');
+
+var binding = require('./binding');
+var util = require('util');
+var assert = require('assert').ok;
+
+// zlib doesn't provide these, so kludge them in following the same
+// const naming scheme zlib uses.
+binding.Z_MIN_WINDOWBITS = 8;
+binding.Z_MAX_WINDOWBITS = 15;
+binding.Z_DEFAULT_WINDOWBITS = 15;
+
+// fewer than 64 bytes per chunk is stupid.
+// technically it could work with as few as 8, but even 64 bytes
+// is absurdly low.  Usually a MB or more is best.
+binding.Z_MIN_CHUNK = 64;
+binding.Z_MAX_CHUNK = Infinity;
+binding.Z_DEFAULT_CHUNK = (16 * 1024);
+
+binding.Z_MIN_MEMLEVEL = 1;
+binding.Z_MAX_MEMLEVEL = 9;
+binding.Z_DEFAULT_MEMLEVEL = 8;
+
+binding.Z_MIN_LEVEL = -1;
+binding.Z_MAX_LEVEL = 9;
+binding.Z_DEFAULT_LEVEL = binding.Z_DEFAULT_COMPRESSION;
+
+// expose all the zlib constants
+Object.keys(binding).forEach(function(k) {
+  if (k.match(/^Z/)) exports[k] = binding[k];
+});
+
+// translation table for return codes.
+exports.codes = {
+  Z_OK: binding.Z_OK,
+  Z_STREAM_END: binding.Z_STREAM_END,
+  Z_NEED_DICT: binding.Z_NEED_DICT,
+  Z_ERRNO: binding.Z_ERRNO,
+  Z_STREAM_ERROR: binding.Z_STREAM_ERROR,
+  Z_DATA_ERROR: binding.Z_DATA_ERROR,
+  Z_MEM_ERROR: binding.Z_MEM_ERROR,
+  Z_BUF_ERROR: binding.Z_BUF_ERROR,
+  Z_VERSION_ERROR: binding.Z_VERSION_ERROR
+};
+
+Object.keys(exports.codes).forEach(function(k) {
+  exports.codes[exports.codes[k]] = k;
+});
+
+exports.Deflate = Deflate;
+exports.Inflate = Inflate;
+exports.Gzip = Gzip;
+exports.Gunzip = Gunzip;
+exports.DeflateRaw = DeflateRaw;
+exports.InflateRaw = InflateRaw;
+exports.Unzip = Unzip;
+
+exports.createDeflate = function(o) {
+  return new Deflate(o);
+};
+
+exports.createInflate = function(o) {
+  return new Inflate(o);
+};
+
+exports.createDeflateRaw = function(o) {
+  return new DeflateRaw(o);
+};
+
+exports.createInflateRaw = function(o) {
+  return new InflateRaw(o);
+};
+
+exports.createGzip = function(o) {
+  return new Gzip(o);
+};
+
+exports.createGunzip = function(o) {
+  return new Gunzip(o);
+};
+
+exports.createUnzip = function(o) {
+  return new Unzip(o);
 };
 
 
+// Convenience methods.
+// compress/decompress a string or buffer in one step.
+exports.deflate = function(buffer, opts, callback) {
+  if (typeof opts === 'function') {
+    callback = opts;
+    opts = {};
+  }
+  return zlibBuffer(new Deflate(opts), buffer, callback);
+};
 
-exports.resolve=function(){
-var resolvedPath='',
-resolvedAbsolute=false;
+exports.deflateSync = function(buffer, opts) {
+  return zlibBufferSync(new Deflate(opts), buffer);
+};
 
-for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){
-var path=i>=0?arguments[i]:process.cwd();
+exports.gzip = function(buffer, opts, callback) {
+  if (typeof opts === 'function') {
+    callback = opts;
+    opts = {};
+  }
+  return zlibBuffer(new Gzip(opts), buffer, callback);
+};
 
+exports.gzipSync = function(buffer, opts) {
+  return zlibBufferSync(new Gzip(opts), buffer);
+};
 
-if(typeof path!=='string'){
-throw new TypeError('Arguments to path.resolve must be strings');
-}else if(!path){
-continue;
+exports.deflateRaw = function(buffer, opts, callback) {
+  if (typeof opts === 'function') {
+    callback = opts;
+    opts = {};
+  }
+  return zlibBuffer(new DeflateRaw(opts), buffer, callback);
+};
+
+exports.deflateRawSync = function(buffer, opts) {
+  return zlibBufferSync(new DeflateRaw(opts), buffer);
+};
+
+exports.unzip = function(buffer, opts, callback) {
+  if (typeof opts === 'function') {
+    callback = opts;
+    opts = {};
+  }
+  return zlibBuffer(new Unzip(opts), buffer, callback);
+};
+
+exports.unzipSync = function(buffer, opts) {
+  return zlibBufferSync(new Unzip(opts), buffer);
+};
+
+exports.inflate = function(buffer, opts, callback) {
+  if (typeof opts === 'function') {
+    callback = opts;
+    opts = {};
+  }
+  return zlibBuffer(new Inflate(opts), buffer, callback);
+};
+
+exports.inflateSync = function(buffer, opts) {
+  return zlibBufferSync(new Inflate(opts), buffer);
+};
+
+exports.gunzip = function(buffer, opts, callback) {
+  if (typeof opts === 'function') {
+    callback = opts;
+    opts = {};
+  }
+  return zlibBuffer(new Gunzip(opts), buffer, callback);
+};
+
+exports.gunzipSync = function(buffer, opts) {
+  return zlibBufferSync(new Gunzip(opts), buffer);
+};
+
+exports.inflateRaw = function(buffer, opts, callback) {
+  if (typeof opts === 'function') {
+    callback = opts;
+    opts = {};
+  }
+  return zlibBuffer(new InflateRaw(opts), buffer, callback);
+};
+
+exports.inflateRawSync = function(buffer, opts) {
+  return zlibBufferSync(new InflateRaw(opts), buffer);
+};
+
+function zlibBuffer(engine, buffer, callback) {
+  var buffers = [];
+  var nread = 0;
+
+  engine.on('error', onError);
+  engine.on('end', onEnd);
+
+  engine.end(buffer);
+  flow();
+
+  function flow() {
+    var chunk;
+    while (null !== (chunk = engine.read())) {
+      buffers.push(chunk);
+      nread += chunk.length;
+    }
+    engine.once('readable', flow);
+  }
+
+  function onError(err) {
+    engine.removeListener('end', onEnd);
+    engine.removeListener('readable', flow);
+    callback(err);
+  }
+
+  function onEnd() {
+    var buf = Buffer.concat(buffers, nread);
+    buffers = [];
+    callback(null, buf);
+    engine.close();
+  }
 }
 
-resolvedPath=path+'/'+resolvedPath;
-resolvedAbsolute=path.charAt(0)==='/';
+function zlibBufferSync(engine, buffer) {
+  if (typeof buffer === 'string')
+    buffer = new Buffer(buffer);
+  if (!Buffer.isBuffer(buffer))
+    throw new TypeError('Not a string or buffer');
+
+  var flushFlag = binding.Z_FINISH;
+
+  return engine._processChunk(buffer, flushFlag);
+}
+
+// generic zlib
+// minimal 2-byte header
+function Deflate(opts) {
+  if (!(this instanceof Deflate)) return new Deflate(opts);
+  Zlib.call(this, opts, binding.DEFLATE);
+}
+
+function Inflate(opts) {
+  if (!(this instanceof Inflate)) return new Inflate(opts);
+  Zlib.call(this, opts, binding.INFLATE);
 }
 
 
 
+// gzip - bigger header, same deflate compression
+function Gzip(opts) {
+  if (!(this instanceof Gzip)) return new Gzip(opts);
+  Zlib.call(this, opts, binding.GZIP);
+}
+
+function Gunzip(opts) {
+  if (!(this instanceof Gunzip)) return new Gunzip(opts);
+  Zlib.call(this, opts, binding.GUNZIP);
+}
 
 
-resolvedPath=normalizeArray(filter(resolvedPath.split('/'),function(p){
-return!!p;
-}),!resolvedAbsolute).join('/');
 
-return(resolvedAbsolute?'/':'')+resolvedPath||'.';
+// raw - no header
+function DeflateRaw(opts) {
+  if (!(this instanceof DeflateRaw)) return new DeflateRaw(opts);
+  Zlib.call(this, opts, binding.DEFLATERAW);
+}
+
+function InflateRaw(opts) {
+  if (!(this instanceof InflateRaw)) return new InflateRaw(opts);
+  Zlib.call(this, opts, binding.INFLATERAW);
+}
+
+
+// auto-detect header.
+function Unzip(opts) {
+  if (!(this instanceof Unzip)) return new Unzip(opts);
+  Zlib.call(this, opts, binding.UNZIP);
+}
+
+
+// the Zlib class they all inherit from
+// This thing manages the queue of requests, and returns
+// true or false if there is anything in the queue when
+// you call the .write() method.
+
+function Zlib(opts, mode) {
+  this._opts = opts = opts || {};
+  this._chunkSize = opts.chunkSize || exports.Z_DEFAULT_CHUNK;
+
+  Transform.call(this, opts);
+
+  if (opts.flush) {
+    if (opts.flush !== binding.Z_NO_FLUSH &&
+        opts.flush !== binding.Z_PARTIAL_FLUSH &&
+        opts.flush !== binding.Z_SYNC_FLUSH &&
+        opts.flush !== binding.Z_FULL_FLUSH &&
+        opts.flush !== binding.Z_FINISH &&
+        opts.flush !== binding.Z_BLOCK) {
+      throw new Error('Invalid flush flag: ' + opts.flush);
+    }
+  }
+  this._flushFlag = opts.flush || binding.Z_NO_FLUSH;
+
+  if (opts.chunkSize) {
+    if (opts.chunkSize < exports.Z_MIN_CHUNK ||
+        opts.chunkSize > exports.Z_MAX_CHUNK) {
+      throw new Error('Invalid chunk size: ' + opts.chunkSize);
+    }
+  }
+
+  if (opts.windowBits) {
+    if (opts.windowBits < exports.Z_MIN_WINDOWBITS ||
+        opts.windowBits > exports.Z_MAX_WINDOWBITS) {
+      throw new Error('Invalid windowBits: ' + opts.windowBits);
+    }
+  }
+
+  if (opts.level) {
+    if (opts.level < exports.Z_MIN_LEVEL ||
+        opts.level > exports.Z_MAX_LEVEL) {
+      throw new Error('Invalid compression level: ' + opts.level);
+    }
+  }
+
+  if (opts.memLevel) {
+    if (opts.memLevel < exports.Z_MIN_MEMLEVEL ||
+        opts.memLevel > exports.Z_MAX_MEMLEVEL) {
+      throw new Error('Invalid memLevel: ' + opts.memLevel);
+    }
+  }
+
+  if (opts.strategy) {
+    if (opts.strategy != exports.Z_FILTERED &&
+        opts.strategy != exports.Z_HUFFMAN_ONLY &&
+        opts.strategy != exports.Z_RLE &&
+        opts.strategy != exports.Z_FIXED &&
+        opts.strategy != exports.Z_DEFAULT_STRATEGY) {
+      throw new Error('Invalid strategy: ' + opts.strategy);
+    }
+  }
+
+  if (opts.dictionary) {
+    if (!Buffer.isBuffer(opts.dictionary)) {
+      throw new Error('Invalid dictionary: it should be a Buffer instance');
+    }
+  }
+
+  this._binding = new binding.Zlib(mode);
+
+  var self = this;
+  this._hadError = false;
+  this._binding.onerror = function(message, errno) {
+    // there is no way to cleanly recover.
+    // continuing only obscures problems.
+    self._binding = null;
+    self._hadError = true;
+
+    var error = new Error(message);
+    error.errno = errno;
+    error.code = exports.codes[errno];
+    self.emit('error', error);
+  };
+
+  var level = exports.Z_DEFAULT_COMPRESSION;
+  if (typeof opts.level === 'number') level = opts.level;
+
+  var strategy = exports.Z_DEFAULT_STRATEGY;
+  if (typeof opts.strategy === 'number') strategy = opts.strategy;
+
+  this._binding.init(opts.windowBits || exports.Z_DEFAULT_WINDOWBITS,
+                     level,
+                     opts.memLevel || exports.Z_DEFAULT_MEMLEVEL,
+                     strategy,
+                     opts.dictionary);
+
+  this._buffer = new Buffer(this._chunkSize);
+  this._offset = 0;
+  this._closed = false;
+  this._level = level;
+  this._strategy = strategy;
+
+  this.once('end', this.close);
+}
+
+util.inherits(Zlib, Transform);
+
+Zlib.prototype.params = function(level, strategy, callback) {
+  if (level < exports.Z_MIN_LEVEL ||
+      level > exports.Z_MAX_LEVEL) {
+    throw new RangeError('Invalid compression level: ' + level);
+  }
+  if (strategy != exports.Z_FILTERED &&
+      strategy != exports.Z_HUFFMAN_ONLY &&
+      strategy != exports.Z_RLE &&
+      strategy != exports.Z_FIXED &&
+      strategy != exports.Z_DEFAULT_STRATEGY) {
+    throw new TypeError('Invalid strategy: ' + strategy);
+  }
+
+  if (this._level !== level || this._strategy !== strategy) {
+    var self = this;
+    this.flush(binding.Z_SYNC_FLUSH, function() {
+      self._binding.params(level, strategy);
+      if (!self._hadError) {
+        self._level = level;
+        self._strategy = strategy;
+        if (callback) callback();
+      }
+    });
+  } else {
+    process.nextTick(callback);
+  }
+};
+
+Zlib.prototype.reset = function() {
+  return this._binding.reset();
+};
+
+// This is the _flush function called by the transform class,
+// internally, when the last chunk has been written.
+Zlib.prototype._flush = function(callback) {
+  this._transform(new Buffer(0), '', callback);
+};
+
+Zlib.prototype.flush = function(kind, callback) {
+  var ws = this._writableState;
+
+  if (typeof kind === 'function' || (kind === void 0 && !callback)) {
+    callback = kind;
+    kind = binding.Z_FULL_FLUSH;
+  }
+
+  if (ws.ended) {
+    if (callback)
+      process.nextTick(callback);
+  } else if (ws.ending) {
+    if (callback)
+      this.once('end', callback);
+  } else if (ws.needDrain) {
+    var self = this;
+    this.once('drain', function() {
+      self.flush(callback);
+    });
+  } else {
+    this._flushFlag = kind;
+    this.write(new Buffer(0), '', callback);
+  }
+};
+
+Zlib.prototype.close = function(callback) {
+  if (callback)
+    process.nextTick(callback);
+
+  if (this._closed)
+    return;
+
+  this._closed = true;
+
+  this._binding.close();
+
+  var self = this;
+  process.nextTick(function() {
+    self.emit('close');
+  });
+};
+
+Zlib.prototype._transform = function(chunk, encoding, cb) {
+  var flushFlag;
+  var ws = this._writableState;
+  var ending = ws.ending || ws.ended;
+  var last = ending && (!chunk || ws.length === chunk.length);
+
+  if (!chunk === null && !Buffer.isBuffer(chunk))
+    return cb(new Error('invalid input'));
+
+  // If it's the last chunk, or a final flush, we use the Z_FINISH flush flag.
+  // If it's explicitly flushing at some other time, then we use
+  // Z_FULL_FLUSH. Otherwise, use Z_NO_FLUSH for maximum compression
+  // goodness.
+  if (last)
+    flushFlag = binding.Z_FINISH;
+  else {
+    flushFlag = this._flushFlag;
+    // once we've flushed the last of the queue, stop flushing and
+    // go back to the normal behavior.
+    if (chunk.length >= ws.length) {
+      this._flushFlag = this._opts.flush || binding.Z_NO_FLUSH;
+    }
+  }
+
+  var self = this;
+  this._processChunk(chunk, flushFlag, cb);
+};
+
+Zlib.prototype._processChunk = function(chunk, flushFlag, cb) {
+  var availInBefore = chunk && chunk.length;
+  var availOutBefore = this._chunkSize - this._offset;
+  var inOff = 0;
+
+  var self = this;
+
+  var async = typeof cb === 'function';
+
+  if (!async) {
+    var buffers = [];
+    var nread = 0;
+
+    var error;
+    this.on('error', function(er) {
+      error = er;
+    });
+
+    do {
+      var res = this._binding.writeSync(flushFlag,
+                                        chunk, // in
+                                        inOff, // in_off
+                                        availInBefore, // in_len
+                                        this._buffer, // out
+                                        this._offset, //out_off
+                                        availOutBefore); // out_len
+    } while (!this._hadError && callback(res[0], res[1]));
+
+    if (this._hadError) {
+      throw error;
+    }
+
+    var buf = Buffer.concat(buffers, nread);
+    this.close();
+
+    return buf;
+  }
+
+  var req = this._binding.write(flushFlag,
+                                chunk, // in
+                                inOff, // in_off
+                                availInBefore, // in_len
+                                this._buffer, // out
+                                this._offset, //out_off
+                                availOutBefore); // out_len
+
+  req.buffer = chunk;
+  req.callback = callback;
+
+  function callback(availInAfter, availOutAfter) {
+    if (self._hadError)
+      return;
+
+    var have = availOutBefore - availOutAfter;
+    assert(have >= 0, 'have should not go down');
+
+    if (have > 0) {
+      var out = self._buffer.slice(self._offset, self._offset + have);
+      self._offset += have;
+      // serve some output to the consumer.
+      if (async) {
+        self.push(out);
+      } else {
+        buffers.push(out);
+        nread += out.length;
+      }
+    }
+
+    // exhausted the output buffer, or used all the input create a new one.
+    if (availOutAfter === 0 || self._offset >= self._chunkSize) {
+      availOutBefore = self._chunkSize;
+      self._offset = 0;
+      self._buffer = new Buffer(self._chunkSize);
+    }
+
+    if (availOutAfter === 0) {
+      // Not actually done.  Need to reprocess.
+      // Also, update the availInBefore to the availInAfter value,
+      // so that if we have to hit it a third (fourth, etc.) time,
+      // it'll have the correct byte counts.
+      inOff += (availInBefore - availInAfter);
+      availInBefore = availInAfter;
+
+      if (!async)
+        return true;
+
+      var newReq = self._binding.write(flushFlag,
+                                       chunk,
+                                       inOff,
+                                       availInBefore,
+                                       self._buffer,
+                                       self._offset,
+                                       self._chunkSize);
+      newReq.callback = callback; // this same function
+      newReq.buffer = chunk;
+      return;
+    }
+
+    if (!async)
+      return false;
+
+    // finished with the chunk.
+    cb();
+  }
+};
+
+util.inherits(Deflate, Zlib);
+util.inherits(Inflate, Zlib);
+util.inherits(Gzip, Zlib);
+util.inherits(Gunzip, Zlib);
+util.inherits(DeflateRaw, Zlib);
+util.inherits(InflateRaw, Zlib);
+util.inherits(Unzip, Zlib);
+
+}).call(this,require('_process'),require("buffer").Buffer)
+},{"./binding":201,"_process":222,"_stream_transform":228,"assert":198,"buffer":205,"util":233}],203:[function(require,module,exports){
+arguments[4][200][0].apply(exports,arguments)
+},{"dup":200}],204:[function(require,module,exports){
+(function (global){
+'use strict';
+
+var buffer = require('buffer');
+var Buffer = buffer.Buffer;
+var SlowBuffer = buffer.SlowBuffer;
+var MAX_LEN = buffer.kMaxLength || 2147483647;
+exports.alloc = function alloc(size, fill, encoding) {
+  if (typeof Buffer.alloc === 'function') {
+    return Buffer.alloc(size, fill, encoding);
+  }
+  if (typeof encoding === 'number') {
+    throw new TypeError('encoding must not be number');
+  }
+  if (typeof size !== 'number') {
+    throw new TypeError('size must be a number');
+  }
+  if (size > MAX_LEN) {
+    throw new RangeError('size is too large');
+  }
+  var enc = encoding;
+  var _fill = fill;
+  if (_fill === undefined) {
+    enc = undefined;
+    _fill = 0;
+  }
+  var buf = new Buffer(size);
+  if (typeof _fill === 'string') {
+    var fillBuf = new Buffer(_fill, enc);
+    var flen = fillBuf.length;
+    var i = -1;
+    while (++i < size) {
+      buf[i] = fillBuf[i % flen];
+    }
+  } else {
+    buf.fill(_fill);
+  }
+  return buf;
+}
+exports.allocUnsafe = function allocUnsafe(size) {
+  if (typeof Buffer.allocUnsafe === 'function') {
+    return Buffer.allocUnsafe(size);
+  }
+  if (typeof size !== 'number') {
+    throw new TypeError('size must be a number');
+  }
+  if (size > MAX_LEN) {
+    throw new RangeError('size is too large');
+  }
+  return new Buffer(size);
+}
+exports.from = function from(value, encodingOrOffset, length) {
+  if (typeof Buffer.from === 'function' && (!global.Uint8Array || Uint8Array.from !== Buffer.from)) {
+    return Buffer.from(value, encodingOrOffset, length);
+  }
+  if (typeof value === 'number') {
+    throw new TypeError('"value" argument must not be a number');
+  }
+  if (typeof value === 'string') {
+    return new Buffer(value, encodingOrOffset);
+  }
+  if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) {
+    var offset = encodingOrOffset;
+    if (arguments.length === 1) {
+      return new Buffer(value);
+    }
+    if (typeof offset === 'undefined') {
+      offset = 0;
+    }
+    var len = length;
+    if (typeof len === 'undefined') {
+      len = value.byteLength - offset;
+    }
+    if (offset >= value.byteLength) {
+      throw new RangeError('\'offset\' is out of bounds');
+    }
+    if (len > value.byteLength - offset) {
+      throw new RangeError('\'length\' is out of bounds');
+    }
+    return new Buffer(value.slice(offset, offset + len));
+  }
+  if (Buffer.isBuffer(value)) {
+    var out = new Buffer(value.length);
+    value.copy(out, 0, 0, value.length);
+    return out;
+  }
+  if (value) {
+    if (Array.isArray(value) || (typeof ArrayBuffer !== 'undefined' && value.buffer instanceof ArrayBuffer) || 'length' in value) {
+      return new Buffer(value);
+    }
+    if (value.type === 'Buffer' && Array.isArray(value.data)) {
+      return new Buffer(value.data);
+    }
+  }
+
+  throw new TypeError('First argument must be a string, Buffer, ' + 'ArrayBuffer, Array, or array-like object.');
+}
+exports.allocUnsafeSlow = function allocUnsafeSlow(size) {
+  if (typeof Buffer.allocUnsafeSlow === 'function') {
+    return Buffer.allocUnsafeSlow(size);
+  }
+  if (typeof size !== 'number') {
+    throw new TypeError('size must be a number');
+  }
+  if (size >= MAX_LEN) {
+    throw new RangeError('size is too large');
+  }
+  return new SlowBuffer(size);
+}
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"buffer":205}],205:[function(require,module,exports){
+(function (global){
+/*!
+ * The buffer module from node.js, for the browser.
+ *
+ * @author   Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
+ * @license  MIT
+ */
+/* eslint-disable no-proto */
+
+'use strict'
+
+var base64 = require('base64-js')
+var ieee754 = require('ieee754')
+var isArray = require('isarray')
+
+exports.Buffer = Buffer
+exports.SlowBuffer = SlowBuffer
+exports.INSPECT_MAX_BYTES = 50
+
+/**
+ * If `Buffer.TYPED_ARRAY_SUPPORT`:
+ *   === true    Use Uint8Array implementation (fastest)
+ *   === false   Use Object implementation (most compatible, even IE6)
+ *
+ * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+,
+ * Opera 11.6+, iOS 4.2+.
+ *
+ * Due to various browser bugs, sometimes the Object implementation will be used even
+ * when the browser supports typed arrays.
+ *
+ * Note:
+ *
+ *   - Firefox 4-29 lacks support for adding new properties to `Uint8Array` instances,
+ *     See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438.
+ *
+ *   - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function.
+ *
+ *   - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of
+ *     incorrect length in some situations.
+
+ * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they
+ * get the Object implementation, which is slower but behaves correctly.
+ */
+Buffer.TYPED_ARRAY_SUPPORT = global.TYPED_ARRAY_SUPPORT !== undefined
+  ? global.TYPED_ARRAY_SUPPORT
+  : typedArraySupport()
+
+/*
+ * Export kMaxLength after typed array support is determined.
+ */
+exports.kMaxLength = kMaxLength()
+
+function typedArraySupport () {
+  try {
+    var arr = new Uint8Array(1)
+    arr.__proto__ = {__proto__: Uint8Array.prototype, foo: function () { return 42 }}
+    return arr.foo() === 42 && // typed array instances can be augmented
+        typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray`
+        arr.subarray(1, 1).byteLength === 0 // ie10 has broken `subarray`
+  } catch (e) {
+    return false
+  }
+}
+
+function kMaxLength () {
+  return Buffer.TYPED_ARRAY_SUPPORT
+    ? 0x7fffffff
+    : 0x3fffffff
+}
+
+function createBuffer (that, length) {
+  if (kMaxLength() < length) {
+    throw new RangeError('Invalid typed array length')
+  }
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    // Return an augmented `Uint8Array` instance, for best performance
+    that = new Uint8Array(length)
+    that.__proto__ = Buffer.prototype
+  } else {
+    // Fallback: Return an object instance of the Buffer class
+    if (that === null) {
+      that = new Buffer(length)
+    }
+    that.length = length
+  }
+
+  return that
+}
+
+/**
+ * The Buffer constructor returns instances of `Uint8Array` that have their
+ * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of
+ * `Uint8Array`, so the returned instances will have all the node `Buffer` methods
+ * and the `Uint8Array` methods. Square bracket notation works as expected -- it
+ * returns a single octet.
+ *
+ * The `Uint8Array` prototype remains unmodified.
+ */
+
+function Buffer (arg, encodingOrOffset, length) {
+  if (!Buffer.TYPED_ARRAY_SUPPORT && !(this instanceof Buffer)) {
+    return new Buffer(arg, encodingOrOffset, length)
+  }
+
+  // Common case.
+  if (typeof arg === 'number') {
+    if (typeof encodingOrOffset === 'string') {
+      throw new Error(
+        'If encoding is specified then the first argument must be a string'
+      )
+    }
+    return allocUnsafe(this, arg)
+  }
+  return from(this, arg, encodingOrOffset, length)
+}
+
+Buffer.poolSize = 8192 // not used by this implementation
+
+// TODO: Legacy, not needed anymore. Remove in next major version.
+Buffer._augment = function (arr) {
+  arr.__proto__ = Buffer.prototype
+  return arr
+}
+
+function from (that, value, encodingOrOffset, length) {
+  if (typeof value === 'number') {
+    throw new TypeError('"value" argument must not be a number')
+  }
+
+  if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) {
+    return fromArrayBuffer(that, value, encodingOrOffset, length)
+  }
+
+  if (typeof value === 'string') {
+    return fromString(that, value, encodingOrOffset)
+  }
+
+  return fromObject(that, value)
+}
+
+/**
+ * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError
+ * if value is a number.
+ * Buffer.from(str[, encoding])
+ * Buffer.from(array)
+ * Buffer.from(buffer)
+ * Buffer.from(arrayBuffer[, byteOffset[, length]])
+ **/
+Buffer.from = function (value, encodingOrOffset, length) {
+  return from(null, value, encodingOrOffset, length)
+}
+
+if (Buffer.TYPED_ARRAY_SUPPORT) {
+  Buffer.prototype.__proto__ = Uint8Array.prototype
+  Buffer.__proto__ = Uint8Array
+  if (typeof Symbol !== 'undefined' && Symbol.species &&
+      Buffer[Symbol.species] === Buffer) {
+    // Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97
+    Object.defineProperty(Buffer, Symbol.species, {
+      value: null,
+      configurable: true
+    })
+  }
+}
+
+function assertSize (size) {
+  if (typeof size !== 'number') {
+    throw new TypeError('"size" argument must be a number')
+  } else if (size < 0) {
+    throw new RangeError('"size" argument must not be negative')
+  }
+}
+
+function alloc (that, size, fill, encoding) {
+  assertSize(size)
+  if (size <= 0) {
+    return createBuffer(that, size)
+  }
+  if (fill !== undefined) {
+    // Only pay attention to encoding if it's a string. This
+    // prevents accidentally sending in a number that would
+    // be interpretted as a start offset.
+    return typeof encoding === 'string'
+      ? createBuffer(that, size).fill(fill, encoding)
+      : createBuffer(that, size).fill(fill)
+  }
+  return createBuffer(that, size)
+}
+
+/**
+ * Creates a new filled Buffer instance.
+ * alloc(size[, fill[, encoding]])
+ **/
+Buffer.alloc = function (size, fill, encoding) {
+  return alloc(null, size, fill, encoding)
+}
+
+function allocUnsafe (that, size) {
+  assertSize(size)
+  that = createBuffer(that, size < 0 ? 0 : checked(size) | 0)
+  if (!Buffer.TYPED_ARRAY_SUPPORT) {
+    for (var i = 0; i < size; ++i) {
+      that[i] = 0
+    }
+  }
+  return that
+}
+
+/**
+ * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance.
+ * */
+Buffer.allocUnsafe = function (size) {
+  return allocUnsafe(null, size)
+}
+/**
+ * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance.
+ */
+Buffer.allocUnsafeSlow = function (size) {
+  return allocUnsafe(null, size)
+}
+
+function fromString (that, string, encoding) {
+  if (typeof encoding !== 'string' || encoding === '') {
+    encoding = 'utf8'
+  }
+
+  if (!Buffer.isEncoding(encoding)) {
+    throw new TypeError('"encoding" must be a valid string encoding')
+  }
+
+  var length = byteLength(string, encoding) | 0
+  that = createBuffer(that, length)
+
+  var actual = that.write(string, encoding)
+
+  if (actual !== length) {
+    // Writing a hex string, for example, that contains invalid characters will
+    // cause everything after the first invalid character to be ignored. (e.g.
+    // 'abxxcd' will be treated as 'ab')
+    that = that.slice(0, actual)
+  }
+
+  return that
+}
+
+function fromArrayLike (that, array) {
+  var length = array.length < 0 ? 0 : checked(array.length) | 0
+  that = createBuffer(that, length)
+  for (var i = 0; i < length; i += 1) {
+    that[i] = array[i] & 255
+  }
+  return that
+}
+
+function fromArrayBuffer (that, array, byteOffset, length) {
+  array.byteLength // this throws if `array` is not a valid ArrayBuffer
+
+  if (byteOffset < 0 || array.byteLength < byteOffset) {
+    throw new RangeError('\'offset\' is out of bounds')
+  }
+
+  if (array.byteLength < byteOffset + (length || 0)) {
+    throw new RangeError('\'length\' is out of bounds')
+  }
+
+  if (byteOffset === undefined && length === undefined) {
+    array = new Uint8Array(array)
+  } else if (length === undefined) {
+    array = new Uint8Array(array, byteOffset)
+  } else {
+    array = new Uint8Array(array, byteOffset, length)
+  }
+
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    // Return an augmented `Uint8Array` instance, for best performance
+    that = array
+    that.__proto__ = Buffer.prototype
+  } else {
+    // Fallback: Return an object instance of the Buffer class
+    that = fromArrayLike(that, array)
+  }
+  return that
+}
+
+function fromObject (that, obj) {
+  if (Buffer.isBuffer(obj)) {
+    var len = checked(obj.length) | 0
+    that = createBuffer(that, len)
+
+    if (that.length === 0) {
+      return that
+    }
+
+    obj.copy(that, 0, 0, len)
+    return that
+  }
+
+  if (obj) {
+    if ((typeof ArrayBuffer !== 'undefined' &&
+        obj.buffer instanceof ArrayBuffer) || 'length' in obj) {
+      if (typeof obj.length !== 'number' || isnan(obj.length)) {
+        return createBuffer(that, 0)
+      }
+      return fromArrayLike(that, obj)
+    }
+
+    if (obj.type === 'Buffer' && isArray(obj.data)) {
+      return fromArrayLike(that, obj.data)
+    }
+  }
+
+  throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.')
+}
+
+function checked (length) {
+  // Note: cannot use `length < kMaxLength()` here because that fails when
+  // length is NaN (which is otherwise coerced to zero.)
+  if (length >= kMaxLength()) {
+    throw new RangeError('Attempt to allocate Buffer larger than maximum ' +
+                         'size: 0x' + kMaxLength().toString(16) + ' bytes')
+  }
+  return length | 0
+}
+
+function SlowBuffer (length) {
+  if (+length != length) { // eslint-disable-line eqeqeq
+    length = 0
+  }
+  return Buffer.alloc(+length)
+}
+
+Buffer.isBuffer = function isBuffer (b) {
+  return !!(b != null && b._isBuffer)
+}
+
+Buffer.compare = function compare (a, b) {
+  if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {
+    throw new TypeError('Arguments must be Buffers')
+  }
+
+  if (a === b) return 0
+
+  var x = a.length
+  var y = b.length
+
+  for (var i = 0, len = Math.min(x, y); i < len; ++i) {
+    if (a[i] !== b[i]) {
+      x = a[i]
+      y = b[i]
+      break
+    }
+  }
+
+  if (x < y) return -1
+  if (y < x) return 1
+  return 0
+}
+
+Buffer.isEncoding = function isEncoding (encoding) {
+  switch (String(encoding).toLowerCase()) {
+    case 'hex':
+    case 'utf8':
+    case 'utf-8':
+    case 'ascii':
+    case 'latin1':
+    case 'binary':
+    case 'base64':
+    case 'ucs2':
+    case 'ucs-2':
+    case 'utf16le':
+    case 'utf-16le':
+      return true
+    default:
+      return false
+  }
+}
+
+Buffer.concat = function concat (list, length) {
+  if (!isArray(list)) {
+    throw new TypeError('"list" argument must be an Array of Buffers')
+  }
+
+  if (list.length === 0) {
+    return Buffer.alloc(0)
+  }
+
+  var i
+  if (length === undefined) {
+    length = 0
+    for (i = 0; i < list.length; ++i) {
+      length += list[i].length
+    }
+  }
+
+  var buffer = Buffer.allocUnsafe(length)
+  var pos = 0
+  for (i = 0; i < list.length; ++i) {
+    var buf = list[i]
+    if (!Buffer.isBuffer(buf)) {
+      throw new TypeError('"list" argument must be an Array of Buffers')
+    }
+    buf.copy(buffer, pos)
+    pos += buf.length
+  }
+  return buffer
+}
+
+function byteLength (string, encoding) {
+  if (Buffer.isBuffer(string)) {
+    return string.length
+  }
+  if (typeof ArrayBuffer !== 'undefined' && typeof ArrayBuffer.isView === 'function' &&
+      (ArrayBuffer.isView(string) || string instanceof ArrayBuffer)) {
+    return string.byteLength
+  }
+  if (typeof string !== 'string') {
+    string = '' + string
+  }
+
+  var len = string.length
+  if (len === 0) return 0
+
+  // Use a for loop to avoid recursion
+  var loweredCase = false
+  for (;;) {
+    switch (encoding) {
+      case 'ascii':
+      case 'latin1':
+      case 'binary':
+        return len
+      case 'utf8':
+      case 'utf-8':
+      case undefined:
+        return utf8ToBytes(string).length
+      case 'ucs2':
+      case 'ucs-2':
+      case 'utf16le':
+      case 'utf-16le':
+        return len * 2
+      case 'hex':
+        return len >>> 1
+      case 'base64':
+        return base64ToBytes(string).length
+      default:
+        if (loweredCase) return utf8ToBytes(string).length // assume utf8
+        encoding = ('' + encoding).toLowerCase()
+        loweredCase = true
+    }
+  }
+}
+Buffer.byteLength = byteLength
+
+function slowToString (encoding, start, end) {
+  var loweredCase = false
+
+  // No need to verify that "this.length <= MAX_UINT32" since it's a read-only
+  // property of a typed array.
+
+  // This behaves neither like String nor Uint8Array in that we set start/end
+  // to their upper/lower bounds if the value passed is out of range.
+  // undefined is handled specially as per ECMA-262 6th Edition,
+  // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization.
+  if (start === undefined || start < 0) {
+    start = 0
+  }
+  // Return early if start > this.length. Done here to prevent potential uint32
+  // coercion fail below.
+  if (start > this.length) {
+    return ''
+  }
+
+  if (end === undefined || end > this.length) {
+    end = this.length
+  }
+
+  if (end <= 0) {
+    return ''
+  }
+
+  // Force coersion to uint32. This will also coerce falsey/NaN values to 0.
+  end >>>= 0
+  start >>>= 0
+
+  if (end <= start) {
+    return ''
+  }
+
+  if (!encoding) encoding = 'utf8'
+
+  while (true) {
+    switch (encoding) {
+      case 'hex':
+        return hexSlice(this, start, end)
+
+      case 'utf8':
+      case 'utf-8':
+        return utf8Slice(this, start, end)
+
+      case 'ascii':
+        return asciiSlice(this, start, end)
+
+      case 'latin1':
+      case 'binary':
+        return latin1Slice(this, start, end)
+
+      case 'base64':
+        return base64Slice(this, start, end)
+
+      case 'ucs2':
+      case 'ucs-2':
+      case 'utf16le':
+      case 'utf-16le':
+        return utf16leSlice(this, start, end)
+
+      default:
+        if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
+        encoding = (encoding + '').toLowerCase()
+        loweredCase = true
+    }
+  }
+}
+
+// The property is used by `Buffer.isBuffer` and `is-buffer` (in Safari 5-7) to detect
+// Buffer instances.
+Buffer.prototype._isBuffer = true
+
+function swap (b, n, m) {
+  var i = b[n]
+  b[n] = b[m]
+  b[m] = i
+}
+
+Buffer.prototype.swap16 = function swap16 () {
+  var len = this.length
+  if (len % 2 !== 0) {
+    throw new RangeError('Buffer size must be a multiple of 16-bits')
+  }
+  for (var i = 0; i < len; i += 2) {
+    swap(this, i, i + 1)
+  }
+  return this
+}
+
+Buffer.prototype.swap32 = function swap32 () {
+  var len = this.length
+  if (len % 4 !== 0) {
+    throw new RangeError('Buffer size must be a multiple of 32-bits')
+  }
+  for (var i = 0; i < len; i += 4) {
+    swap(this, i, i + 3)
+    swap(this, i + 1, i + 2)
+  }
+  return this
+}
+
+Buffer.prototype.swap64 = function swap64 () {
+  var len = this.length
+  if (len % 8 !== 0) {
+    throw new RangeError('Buffer size must be a multiple of 64-bits')
+  }
+  for (var i = 0; i < len; i += 8) {
+    swap(this, i, i + 7)
+    swap(this, i + 1, i + 6)
+    swap(this, i + 2, i + 5)
+    swap(this, i + 3, i + 4)
+  }
+  return this
+}
+
+Buffer.prototype.toString = function toString () {
+  var length = this.length | 0
+  if (length === 0) return ''
+  if (arguments.length === 0) return utf8Slice(this, 0, length)
+  return slowToString.apply(this, arguments)
+}
+
+Buffer.prototype.equals = function equals (b) {
+  if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')
+  if (this === b) return true
+  return Buffer.compare(this, b) === 0
+}
+
+Buffer.prototype.inspect = function inspect () {
+  var str = ''
+  var max = exports.INSPECT_MAX_BYTES
+  if (this.length > 0) {
+    str = this.toString('hex', 0, max).match(/.{2}/g).join(' ')
+    if (this.length > max) str += ' ... '
+  }
+  return '<Buffer ' + str + '>'
+}
+
+Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) {
+  if (!Buffer.isBuffer(target)) {
+    throw new TypeError('Argument must be a Buffer')
+  }
+
+  if (start === undefined) {
+    start = 0
+  }
+  if (end === undefined) {
+    end = target ? target.length : 0
+  }
+  if (thisStart === undefined) {
+    thisStart = 0
+  }
+  if (thisEnd === undefined) {
+    thisEnd = this.length
+  }
+
+  if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) {
+    throw new RangeError('out of range index')
+  }
+
+  if (thisStart >= thisEnd && start >= end) {
+    return 0
+  }
+  if (thisStart >= thisEnd) {
+    return -1
+  }
+  if (start >= end) {
+    return 1
+  }
+
+  start >>>= 0
+  end >>>= 0
+  thisStart >>>= 0
+  thisEnd >>>= 0
+
+  if (this === target) return 0
+
+  var x = thisEnd - thisStart
+  var y = end - start
+  var len = Math.min(x, y)
+
+  var thisCopy = this.slice(thisStart, thisEnd)
+  var targetCopy = target.slice(start, end)
+
+  for (var i = 0; i < len; ++i) {
+    if (thisCopy[i] !== targetCopy[i]) {
+      x = thisCopy[i]
+      y = targetCopy[i]
+      break
+    }
+  }
+
+  if (x < y) return -1
+  if (y < x) return 1
+  return 0
+}
+
+// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`,
+// OR the last index of `val` in `buffer` at offset <= `byteOffset`.
+//
+// Arguments:
+// - buffer - a Buffer to search
+// - val - a string, Buffer, or number
+// - byteOffset - an index into `buffer`; will be clamped to an int32
+// - encoding - an optional encoding, relevant is val is a string
+// - dir - true for indexOf, false for lastIndexOf
+function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) {
+  // Empty buffer means no match
+  if (buffer.length === 0) return -1
+
+  // Normalize byteOffset
+  if (typeof byteOffset === 'string') {
+    encoding = byteOffset
+    byteOffset = 0
+  } else if (byteOffset > 0x7fffffff) {
+    byteOffset = 0x7fffffff
+  } else if (byteOffset < -0x80000000) {
+    byteOffset = -0x80000000
+  }
+  byteOffset = +byteOffset  // Coerce to Number.
+  if (isNaN(byteOffset)) {
+    // byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer
+    byteOffset = dir ? 0 : (buffer.length - 1)
+  }
+
+  // Normalize byteOffset: negative offsets start from the end of the buffer
+  if (byteOffset < 0) byteOffset = buffer.length + byteOffset
+  if (byteOffset >= buffer.length) {
+    if (dir) return -1
+    else byteOffset = buffer.length - 1
+  } else if (byteOffset < 0) {
+    if (dir) byteOffset = 0
+    else return -1
+  }
+
+  // Normalize val
+  if (typeof val === 'string') {
+    val = Buffer.from(val, encoding)
+  }
+
+  // Finally, search either indexOf (if dir is true) or lastIndexOf
+  if (Buffer.isBuffer(val)) {
+    // Special case: looking for empty string/buffer always fails
+    if (val.length === 0) {
+      return -1
+    }
+    return arrayIndexOf(buffer, val, byteOffset, encoding, dir)
+  } else if (typeof val === 'number') {
+    val = val & 0xFF // Search for a byte value [0-255]
+    if (Buffer.TYPED_ARRAY_SUPPORT &&
+        typeof Uint8Array.prototype.indexOf === 'function') {
+      if (dir) {
+        return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset)
+      } else {
+        return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset)
+      }
+    }
+    return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir)
+  }
+
+  throw new TypeError('val must be string, number or Buffer')
+}
+
+function arrayIndexOf (arr, val, byteOffset, encoding, dir) {
+  var indexSize = 1
+  var arrLength = arr.length
+  var valLength = val.length
+
+  if (encoding !== undefined) {
+    encoding = String(encoding).toLowerCase()
+    if (encoding === 'ucs2' || encoding === 'ucs-2' ||
+        encoding === 'utf16le' || encoding === 'utf-16le') {
+      if (arr.length < 2 || val.length < 2) {
+        return -1
+      }
+      indexSize = 2
+      arrLength /= 2
+      valLength /= 2
+      byteOffset /= 2
+    }
+  }
+
+  function read (buf, i) {
+    if (indexSize === 1) {
+      return buf[i]
+    } else {
+      return buf.readUInt16BE(i * indexSize)
+    }
+  }
+
+  var i
+  if (dir) {
+    var foundIndex = -1
+    for (i = byteOffset; i < arrLength; i++) {
+      if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) {
+        if (foundIndex === -1) foundIndex = i
+        if (i - foundIndex + 1 === valLength) return foundIndex * indexSize
+      } else {
+        if (foundIndex !== -1) i -= i - foundIndex
+        foundIndex = -1
+      }
+    }
+  } else {
+    if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength
+    for (i = byteOffset; i >= 0; i--) {
+      var found = true
+      for (var j = 0; j < valLength; j++) {
+        if (read(arr, i + j) !== read(val, j)) {
+          found = false
+          break
+        }
+      }
+      if (found) return i
+    }
+  }
+
+  return -1
+}
+
+Buffer.prototype.includes = function includes (val, byteOffset, encoding) {
+  return this.indexOf(val, byteOffset, encoding) !== -1
+}
+
+Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) {
+  return bidirectionalIndexOf(this, val, byteOffset, encoding, true)
+}
+
+Buffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) {
+  return bidirectionalIndexOf(this, val, byteOffset, encoding, false)
+}
+
+function hexWrite (buf, string, offset, length) {
+  offset = Number(offset) || 0
+  var remaining = buf.length - offset
+  if (!length) {
+    length = remaining
+  } else {
+    length = Number(length)
+    if (length > remaining) {
+      length = remaining
+    }
+  }
+
+  // must be an even number of digits
+  var strLen = string.length
+  if (strLen % 2 !== 0) throw new TypeError('Invalid hex string')
+
+  if (length > strLen / 2) {
+    length = strLen / 2
+  }
+  for (var i = 0; i < length; ++i) {
+    var parsed = parseInt(string.substr(i * 2, 2), 16)
+    if (isNaN(parsed)) return i
+    buf[offset + i] = parsed
+  }
+  return i
+}
+
+function utf8Write (buf, string, offset, length) {
+  return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length)
+}
+
+function asciiWrite (buf, string, offset, length) {
+  return blitBuffer(asciiToBytes(string), buf, offset, length)
+}
+
+function latin1Write (buf, string, offset, length) {
+  return asciiWrite(buf, string, offset, length)
+}
+
+function base64Write (buf, string, offset, length) {
+  return blitBuffer(base64ToBytes(string), buf, offset, length)
+}
+
+function ucs2Write (buf, string, offset, length) {
+  return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length)
+}
+
+Buffer.prototype.write = function write (string, offset, length, encoding) {
+  // Buffer#write(string)
+  if (offset === undefined) {
+    encoding = 'utf8'
+    length = this.length
+    offset = 0
+  // Buffer#write(string, encoding)
+  } else if (length === undefined && typeof offset === 'string') {
+    encoding = offset
+    length = this.length
+    offset = 0
+  // Buffer#write(string, offset[, length][, encoding])
+  } else if (isFinite(offset)) {
+    offset = offset | 0
+    if (isFinite(length)) {
+      length = length | 0
+      if (encoding === undefined) encoding = 'utf8'
+    } else {
+      encoding = length
+      length = undefined
+    }
+  // legacy write(string, encoding, offset, length) - remove in v0.13
+  } else {
+    throw new Error(
+      'Buffer.write(string, encoding, offset[, length]) is no longer supported'
+    )
+  }
+
+  var remaining = this.length - offset
+  if (length === undefined || length > remaining) length = remaining
+
+  if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) {
+    throw new RangeError('Attempt to write outside buffer bounds')
+  }
+
+  if (!encoding) encoding = 'utf8'
+
+  var loweredCase = false
+  for (;;) {
+    switch (encoding) {
+      case 'hex':
+        return hexWrite(this, string, offset, length)
+
+      case 'utf8':
+      case 'utf-8':
+        return utf8Write(this, string, offset, length)
+
+      case 'ascii':
+        return asciiWrite(this, string, offset, length)
+
+      case 'latin1':
+      case 'binary':
+        return latin1Write(this, string, offset, length)
+
+      case 'base64':
+        // Warning: maxLength not taken into account in base64Write
+        return base64Write(this, string, offset, length)
+
+      case 'ucs2':
+      case 'ucs-2':
+      case 'utf16le':
+      case 'utf-16le':
+        return ucs2Write(this, string, offset, length)
+
+      default:
+        if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
+        encoding = ('' + encoding).toLowerCase()
+        loweredCase = true
+    }
+  }
+}
+
+Buffer.prototype.toJSON = function toJSON () {
+  return {
+    type: 'Buffer',
+    data: Array.prototype.slice.call(this._arr || this, 0)
+  }
+}
+
+function base64Slice (buf, start, end) {
+  if (start === 0 && end === buf.length) {
+    return base64.fromByteArray(buf)
+  } else {
+    return base64.fromByteArray(buf.slice(start, end))
+  }
+}
+
+function utf8Slice (buf, start, end) {
+  end = Math.min(buf.length, end)
+  var res = []
+
+  var i = start
+  while (i < end) {
+    var firstByte = buf[i]
+    var codePoint = null
+    var bytesPerSequence = (firstByte > 0xEF) ? 4
+      : (firstByte > 0xDF) ? 3
+      : (firstByte > 0xBF) ? 2
+      : 1
+
+    if (i + bytesPerSequence <= end) {
+      var secondByte, thirdByte, fourthByte, tempCodePoint
+
+      switch (bytesPerSequence) {
+        case 1:
+          if (firstByte < 0x80) {
+            codePoint = firstByte
+          }
+          break
+        case 2:
+          secondByte = buf[i + 1]
+          if ((secondByte & 0xC0) === 0x80) {
+            tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F)
+            if (tempCodePoint > 0x7F) {
+              codePoint = tempCodePoint
+            }
+          }
+          break
+        case 3:
+          secondByte = buf[i + 1]
+          thirdByte = buf[i + 2]
+          if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) {
+            tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F)
+            if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) {
+              codePoint = tempCodePoint
+            }
+          }
+          break
+        case 4:
+          secondByte = buf[i + 1]
+          thirdByte = buf[i + 2]
+          fourthByte = buf[i + 3]
+          if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) {
+            tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F)
+            if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) {
+              codePoint = tempCodePoint
+            }
+          }
+      }
+    }
+
+    if (codePoint === null) {
+      // we did not generate a valid codePoint so insert a
+      // replacement char (U+FFFD) and advance only 1 byte
+      codePoint = 0xFFFD
+      bytesPerSequence = 1
+    } else if (codePoint > 0xFFFF) {
+      // encode to utf16 (surrogate pair dance)
+      codePoint -= 0x10000
+      res.push(codePoint >>> 10 & 0x3FF | 0xD800)
+      codePoint = 0xDC00 | codePoint & 0x3FF
+    }
+
+    res.push(codePoint)
+    i += bytesPerSequence
+  }
+
+  return decodeCodePointsArray(res)
+}
+
+// Based on http://stackoverflow.com/a/22747272/680742, the browser with
+// the lowest limit is Chrome, with 0x10000 args.
+// We go 1 magnitude less, for safety
+var MAX_ARGUMENTS_LENGTH = 0x1000
+
+function decodeCodePointsArray (codePoints) {
+  var len = codePoints.length
+  if (len <= MAX_ARGUMENTS_LENGTH) {
+    return String.fromCharCode.apply(String, codePoints) // avoid extra slice()
+  }
+
+  // Decode in chunks to avoid "call stack size exceeded".
+  var res = ''
+  var i = 0
+  while (i < len) {
+    res += String.fromCharCode.apply(
+      String,
+      codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH)
+    )
+  }
+  return res
+}
+
+function asciiSlice (buf, start, end) {
+  var ret = ''
+  end = Math.min(buf.length, end)
+
+  for (var i = start; i < end; ++i) {
+    ret += String.fromCharCode(buf[i] & 0x7F)
+  }
+  return ret
+}
+
+function latin1Slice (buf, start, end) {
+  var ret = ''
+  end = Math.min(buf.length, end)
+
+  for (var i = start; i < end; ++i) {
+    ret += String.fromCharCode(buf[i])
+  }
+  return ret
+}
+
+function hexSlice (buf, start, end) {
+  var len = buf.length
+
+  if (!start || start < 0) start = 0
+  if (!end || end < 0 || end > len) end = len
+
+  var out = ''
+  for (var i = start; i < end; ++i) {
+    out += toHex(buf[i])
+  }
+  return out
+}
+
+function utf16leSlice (buf, start, end) {
+  var bytes = buf.slice(start, end)
+  var res = ''
+  for (var i = 0; i < bytes.length; i += 2) {
+    res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256)
+  }
+  return res
+}
+
+Buffer.prototype.slice = function slice (start, end) {
+  var len = this.length
+  start = ~~start
+  end = end === undefined ? len : ~~end
+
+  if (start < 0) {
+    start += len
+    if (start < 0) start = 0
+  } else if (start > len) {
+    start = len
+  }
+
+  if (end < 0) {
+    end += len
+    if (end < 0) end = 0
+  } else if (end > len) {
+    end = len
+  }
+
+  if (end < start) end = start
+
+  var newBuf
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    newBuf = this.subarray(start, end)
+    newBuf.__proto__ = Buffer.prototype
+  } else {
+    var sliceLen = end - start
+    newBuf = new Buffer(sliceLen, undefined)
+    for (var i = 0; i < sliceLen; ++i) {
+      newBuf[i] = this[i + start]
+    }
+  }
+
+  return newBuf
+}
+
+/*
+ * Need to make sure that buffer isn't trying to write out of bounds.
+ */
+function checkOffset (offset, ext, length) {
+  if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint')
+  if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length')
+}
+
+Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) {
+  offset = offset | 0
+  byteLength = byteLength | 0
+  if (!noAssert) checkOffset(offset, byteLength, this.length)
+
+  var val = this[offset]
+  var mul = 1
+  var i = 0
+  while (++i < byteLength && (mul *= 0x100)) {
+    val += this[offset + i] * mul
+  }
+
+  return val
+}
+
+Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) {
+  offset = offset | 0
+  byteLength = byteLength | 0
+  if (!noAssert) {
+    checkOffset(offset, byteLength, this.length)
+  }
+
+  var val = this[offset + --byteLength]
+  var mul = 1
+  while (byteLength > 0 && (mul *= 0x100)) {
+    val += this[offset + --byteLength] * mul
+  }
+
+  return val
+}
+
+Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 1, this.length)
+  return this[offset]
+}
+
+Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 2, this.length)
+  return this[offset] | (this[offset + 1] << 8)
+}
+
+Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 2, this.length)
+  return (this[offset] << 8) | this[offset + 1]
+}
+
+Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 4, this.length)
+
+  return ((this[offset]) |
+      (this[offset + 1] << 8) |
+      (this[offset + 2] << 16)) +
+      (this[offset + 3] * 0x1000000)
+}
+
+Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 4, this.length)
+
+  return (this[offset] * 0x1000000) +
+    ((this[offset + 1] << 16) |
+    (this[offset + 2] << 8) |
+    this[offset + 3])
+}
+
+Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) {
+  offset = offset | 0
+  byteLength = byteLength | 0
+  if (!noAssert) checkOffset(offset, byteLength, this.length)
+
+  var val = this[offset]
+  var mul = 1
+  var i = 0
+  while (++i < byteLength && (mul *= 0x100)) {
+    val += this[offset + i] * mul
+  }
+  mul *= 0x80
+
+  if (val >= mul) val -= Math.pow(2, 8 * byteLength)
+
+  return val
+}
+
+Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) {
+  offset = offset | 0
+  byteLength = byteLength | 0
+  if (!noAssert) checkOffset(offset, byteLength, this.length)
+
+  var i = byteLength
+  var mul = 1
+  var val = this[offset + --i]
+  while (i > 0 && (mul *= 0x100)) {
+    val += this[offset + --i] * mul
+  }
+  mul *= 0x80
+
+  if (val >= mul) val -= Math.pow(2, 8 * byteLength)
+
+  return val
+}
+
+Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 1, this.length)
+  if (!(this[offset] & 0x80)) return (this[offset])
+  return ((0xff - this[offset] + 1) * -1)
+}
+
+Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 2, this.length)
+  var val = this[offset] | (this[offset + 1] << 8)
+  return (val & 0x8000) ? val | 0xFFFF0000 : val
+}
+
+Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 2, this.length)
+  var val = this[offset + 1] | (this[offset] << 8)
+  return (val & 0x8000) ? val | 0xFFFF0000 : val
+}
+
+Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 4, this.length)
+
+  return (this[offset]) |
+    (this[offset + 1] << 8) |
+    (this[offset + 2] << 16) |
+    (this[offset + 3] << 24)
+}
+
+Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 4, this.length)
+
+  return (this[offset] << 24) |
+    (this[offset + 1] << 16) |
+    (this[offset + 2] << 8) |
+    (this[offset + 3])
+}
+
+Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 4, this.length)
+  return ieee754.read(this, offset, true, 23, 4)
+}
+
+Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 4, this.length)
+  return ieee754.read(this, offset, false, 23, 4)
+}
+
+Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 8, this.length)
+  return ieee754.read(this, offset, true, 52, 8)
+}
+
+Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 8, this.length)
+  return ieee754.read(this, offset, false, 52, 8)
+}
+
+function checkInt (buf, value, offset, ext, max, min) {
+  if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance')
+  if (value > max || value < min) throw new RangeError('"value" argument is out of bounds')
+  if (offset + ext > buf.length) throw new RangeError('Index out of range')
+}
+
+Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) {
+  value = +value
+  offset = offset | 0
+  byteLength = byteLength | 0
+  if (!noAssert) {
+    var maxBytes = Math.pow(2, 8 * byteLength) - 1
+    checkInt(this, value, offset, byteLength, maxBytes, 0)
+  }
+
+  var mul = 1
+  var i = 0
+  this[offset] = value & 0xFF
+  while (++i < byteLength && (mul *= 0x100)) {
+    this[offset + i] = (value / mul) & 0xFF
+  }
+
+  return offset + byteLength
+}
+
+Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) {
+  value = +value
+  offset = offset | 0
+  byteLength = byteLength | 0
+  if (!noAssert) {
+    var maxBytes = Math.pow(2, 8 * byteLength) - 1
+    checkInt(this, value, offset, byteLength, maxBytes, 0)
+  }
+
+  var i = byteLength - 1
+  var mul = 1
+  this[offset + i] = value & 0xFF
+  while (--i >= 0 && (mul *= 0x100)) {
+    this[offset + i] = (value / mul) & 0xFF
+  }
+
+  return offset + byteLength
+}
+
+Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) {
+  value = +value
+  offset = offset | 0
+  if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0)
+  if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)
+  this[offset] = (value & 0xff)
+  return offset + 1
+}
+
+function objectWriteUInt16 (buf, value, offset, littleEndian) {
+  if (value < 0) value = 0xffff + value + 1
+  for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; ++i) {
+    buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>>
+      (littleEndian ? i : 1 - i) * 8
+  }
+}
+
+Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) {
+  value = +value
+  offset = offset | 0
+  if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    this[offset] = (value & 0xff)
+    this[offset + 1] = (value >>> 8)
+  } else {
+    objectWriteUInt16(this, value, offset, true)
+  }
+  return offset + 2
+}
+
+Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) {
+  value = +value
+  offset = offset | 0
+  if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    this[offset] = (value >>> 8)
+    this[offset + 1] = (value & 0xff)
+  } else {
+    objectWriteUInt16(this, value, offset, false)
+  }
+  return offset + 2
+}
+
+function objectWriteUInt32 (buf, value, offset, littleEndian) {
+  if (value < 0) value = 0xffffffff + value + 1
+  for (var i = 0, j = Math.min(buf.length - offset, 4); i < j; ++i) {
+    buf[offset + i] = (value >>> (littleEndian ? i : 3 - i) * 8) & 0xff
+  }
+}
+
+Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) {
+  value = +value
+  offset = offset | 0
+  if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    this[offset + 3] = (value >>> 24)
+    this[offset + 2] = (value >>> 16)
+    this[offset + 1] = (value >>> 8)
+    this[offset] = (value & 0xff)
+  } else {
+    objectWriteUInt32(this, value, offset, true)
+  }
+  return offset + 4
+}
+
+Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) {
+  value = +value
+  offset = offset | 0
+  if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    this[offset] = (value >>> 24)
+    this[offset + 1] = (value >>> 16)
+    this[offset + 2] = (value >>> 8)
+    this[offset + 3] = (value & 0xff)
+  } else {
+    objectWriteUInt32(this, value, offset, false)
+  }
+  return offset + 4
+}
+
+Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) {
+  value = +value
+  offset = offset | 0
+  if (!noAssert) {
+    var limit = Math.pow(2, 8 * byteLength - 1)
+
+    checkInt(this, value, offset, byteLength, limit - 1, -limit)
+  }
+
+  var i = 0
+  var mul = 1
+  var sub = 0
+  this[offset] = value & 0xFF
+  while (++i < byteLength && (mul *= 0x100)) {
+    if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) {
+      sub = 1
+    }
+    this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
+  }
+
+  return offset + byteLength
+}
+
+Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) {
+  value = +value
+  offset = offset | 0
+  if (!noAssert) {
+    var limit = Math.pow(2, 8 * byteLength - 1)
+
+    checkInt(this, value, offset, byteLength, limit - 1, -limit)
+  }
+
+  var i = byteLength - 1
+  var mul = 1
+  var sub = 0
+  this[offset + i] = value & 0xFF
+  while (--i >= 0 && (mul *= 0x100)) {
+    if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) {
+      sub = 1
+    }
+    this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
+  }
+
+  return offset + byteLength
+}
+
+Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) {
+  value = +value
+  offset = offset | 0
+  if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80)
+  if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)
+  if (value < 0) value = 0xff + value + 1
+  this[offset] = (value & 0xff)
+  return offset + 1
+}
+
+Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) {
+  value = +value
+  offset = offset | 0
+  if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    this[offset] = (value & 0xff)
+    this[offset + 1] = (value >>> 8)
+  } else {
+    objectWriteUInt16(this, value, offset, true)
+  }
+  return offset + 2
+}
+
+Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) {
+  value = +value
+  offset = offset | 0
+  if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    this[offset] = (value >>> 8)
+    this[offset + 1] = (value & 0xff)
+  } else {
+    objectWriteUInt16(this, value, offset, false)
+  }
+  return offset + 2
+}
+
+Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) {
+  value = +value
+  offset = offset | 0
+  if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    this[offset] = (value & 0xff)
+    this[offset + 1] = (value >>> 8)
+    this[offset + 2] = (value >>> 16)
+    this[offset + 3] = (value >>> 24)
+  } else {
+    objectWriteUInt32(this, value, offset, true)
+  }
+  return offset + 4
+}
+
+Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) {
+  value = +value
+  offset = offset | 0
+  if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
+  if (value < 0) value = 0xffffffff + value + 1
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    this[offset] = (value >>> 24)
+    this[offset + 1] = (value >>> 16)
+    this[offset + 2] = (value >>> 8)
+    this[offset + 3] = (value & 0xff)
+  } else {
+    objectWriteUInt32(this, value, offset, false)
+  }
+  return offset + 4
+}
+
+function checkIEEE754 (buf, value, offset, ext, max, min) {
+  if (offset + ext > buf.length) throw new RangeError('Index out of range')
+  if (offset < 0) throw new RangeError('Index out of range')
+}
+
+function writeFloat (buf, value, offset, littleEndian, noAssert) {
+  if (!noAssert) {
+    checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38)
+  }
+  ieee754.write(buf, value, offset, littleEndian, 23, 4)
+  return offset + 4
+}
+
+Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) {
+  return writeFloat(this, value, offset, true, noAssert)
+}
+
+Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) {
+  return writeFloat(this, value, offset, false, noAssert)
+}
+
+function writeDouble (buf, value, offset, littleEndian, noAssert) {
+  if (!noAssert) {
+    checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308)
+  }
+  ieee754.write(buf, value, offset, littleEndian, 52, 8)
+  return offset + 8
+}
+
+Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) {
+  return writeDouble(this, value, offset, true, noAssert)
+}
+
+Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) {
+  return writeDouble(this, value, offset, false, noAssert)
+}
+
+// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)
+Buffer.prototype.copy = function copy (target, targetStart, start, end) {
+  if (!start) start = 0
+  if (!end && end !== 0) end = this.length
+  if (targetStart >= target.length) targetStart = target.length
+  if (!targetStart) targetStart = 0
+  if (end > 0 && end < start) end = start
+
+  // Copy 0 bytes; we're done
+  if (end === start) return 0
+  if (target.length === 0 || this.length === 0) return 0
+
+  // Fatal error conditions
+  if (targetStart < 0) {
+    throw new RangeError('targetStart out of bounds')
+  }
+  if (start < 0 || start >= this.length) throw new RangeError('sourceStart out of bounds')
+  if (end < 0) throw new RangeError('sourceEnd out of bounds')
+
+  // Are we oob?
+  if (end > this.length) end = this.length
+  if (target.length - targetStart < end - start) {
+    end = target.length - targetStart + start
+  }
+
+  var len = end - start
+  var i
+
+  if (this === target && start < targetStart && targetStart < end) {
+    // descending copy from end
+    for (i = len - 1; i >= 0; --i) {
+      target[i + targetStart] = this[i + start]
+    }
+  } else if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) {
+    // ascending copy from start
+    for (i = 0; i < len; ++i) {
+      target[i + targetStart] = this[i + start]
+    }
+  } else {
+    Uint8Array.prototype.set.call(
+      target,
+      this.subarray(start, start + len),
+      targetStart
+    )
+  }
+
+  return len
+}
+
+// Usage:
+//    buffer.fill(number[, offset[, end]])
+//    buffer.fill(buffer[, offset[, end]])
+//    buffer.fill(string[, offset[, end]][, encoding])
+Buffer.prototype.fill = function fill (val, start, end, encoding) {
+  // Handle string cases:
+  if (typeof val === 'string') {
+    if (typeof start === 'string') {
+      encoding = start
+      start = 0
+      end = this.length
+    } else if (typeof end === 'string') {
+      encoding = end
+      end = this.length
+    }
+    if (val.length === 1) {
+      var code = val.charCodeAt(0)
+      if (code < 256) {
+        val = code
+      }
+    }
+    if (encoding !== undefined && typeof encoding !== 'string') {
+      throw new TypeError('encoding must be a string')
+    }
+    if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) {
+      throw new TypeError('Unknown encoding: ' + encoding)
+    }
+  } else if (typeof val === 'number') {
+    val = val & 255
+  }
+
+  // Invalid ranges are not set to a default, so can range check early.
+  if (start < 0 || this.length < start || this.length < end) {
+    throw new RangeError('Out of range index')
+  }
+
+  if (end <= start) {
+    return this
+  }
+
+  start = start >>> 0
+  end = end === undefined ? this.length : end >>> 0
+
+  if (!val) val = 0
+
+  var i
+  if (typeof val === 'number') {
+    for (i = start; i < end; ++i) {
+      this[i] = val
+    }
+  } else {
+    var bytes = Buffer.isBuffer(val)
+      ? val
+      : utf8ToBytes(new Buffer(val, encoding).toString())
+    var len = bytes.length
+    for (i = 0; i < end - start; ++i) {
+      this[i + start] = bytes[i % len]
+    }
+  }
+
+  return this
+}
+
+// HELPER FUNCTIONS
+// ================
+
+var INVALID_BASE64_RE = /[^+\/0-9A-Za-z-_]/g
+
+function base64clean (str) {
+  // Node strips out invalid characters like \n and \t from the string, base64-js does not
+  str = stringtrim(str).replace(INVALID_BASE64_RE, '')
+  // Node converts strings with length < 2 to ''
+  if (str.length < 2) return ''
+  // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not
+  while (str.length % 4 !== 0) {
+    str = str + '='
+  }
+  return str
+}
+
+function stringtrim (str) {
+  if (str.trim) return str.trim()
+  return str.replace(/^\s+|\s+$/g, '')
+}
+
+function toHex (n) {
+  if (n < 16) return '0' + n.toString(16)
+  return n.toString(16)
+}
+
+function utf8ToBytes (string, units) {
+  units = units || Infinity
+  var codePoint
+  var length = string.length
+  var leadSurrogate = null
+  var bytes = []
+
+  for (var i = 0; i < length; ++i) {
+    codePoint = string.charCodeAt(i)
+
+    // is surrogate component
+    if (codePoint > 0xD7FF && codePoint < 0xE000) {
+      // last char was a lead
+      if (!leadSurrogate) {
+        // no lead yet
+        if (codePoint > 0xDBFF) {
+          // unexpected trail
+          if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+          continue
+        } else if (i + 1 === length) {
+          // unpaired lead
+          if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+          continue
+        }
+
+        // valid lead
+        leadSurrogate = codePoint
+
+        continue
+      }
+
+      // 2 leads in a row
+      if (codePoint < 0xDC00) {
+        if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+        leadSurrogate = codePoint
+        continue
+      }
+
+      // valid surrogate pair
+      codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000
+    } else if (leadSurrogate) {
+      // valid bmp char, but last char was a lead
+      if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+    }
+
+    leadSurrogate = null
+
+    // encode utf8
+    if (codePoint < 0x80) {
+      if ((units -= 1) < 0) break
+      bytes.push(codePoint)
+    } else if (codePoint < 0x800) {
+      if ((units -= 2) < 0) break
+      bytes.push(
+        codePoint >> 0x6 | 0xC0,
+        codePoint & 0x3F | 0x80
+      )
+    } else if (codePoint < 0x10000) {
+      if ((units -= 3) < 0) break
+      bytes.push(
+        codePoint >> 0xC | 0xE0,
+        codePoint >> 0x6 & 0x3F | 0x80,
+        codePoint & 0x3F | 0x80
+      )
+    } else if (codePoint < 0x110000) {
+      if ((units -= 4) < 0) break
+      bytes.push(
+        codePoint >> 0x12 | 0xF0,
+        codePoint >> 0xC & 0x3F | 0x80,
+        codePoint >> 0x6 & 0x3F | 0x80,
+        codePoint & 0x3F | 0x80
+      )
+    } else {
+      throw new Error('Invalid code point')
+    }
+  }
+
+  return bytes
+}
+
+function asciiToBytes (str) {
+  var byteArray = []
+  for (var i = 0; i < str.length; ++i) {
+    // Node's code seems to be doing this and not & 0x7F..
+    byteArray.push(str.charCodeAt(i) & 0xFF)
+  }
+  return byteArray
+}
+
+function utf16leToBytes (str, units) {
+  var c, hi, lo
+  var byteArray = []
+  for (var i = 0; i < str.length; ++i) {
+    if ((units -= 2) < 0) break
+
+    c = str.charCodeAt(i)
+    hi = c >> 8
+    lo = c % 256
+    byteArray.push(lo)
+    byteArray.push(hi)
+  }
+
+  return byteArray
+}
+
+function base64ToBytes (str) {
+  return base64.toByteArray(base64clean(str))
+}
+
+function blitBuffer (src, dst, offset, length) {
+  for (var i = 0; i < length; ++i) {
+    if ((i + offset >= dst.length) || (i >= src.length)) break
+    dst[i + offset] = src[i]
+  }
+  return i
+}
+
+function isnan (val) {
+  return val !== val // eslint-disable-line no-self-compare
+}
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"base64-js":199,"ieee754":208,"isarray":211}],206:[function(require,module,exports){
+(function (Buffer){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// NOTE: These type checking functions intentionally don't use `instanceof`
+// because it is fragile and can be easily faked with `Object.create()`.
+
+function isArray(arg) {
+  if (Array.isArray) {
+    return Array.isArray(arg);
+  }
+  return objectToString(arg) === '[object Array]';
+}
+exports.isArray = isArray;
+
+function isBoolean(arg) {
+  return typeof arg === 'boolean';
+}
+exports.isBoolean = isBoolean;
+
+function isNull(arg) {
+  return arg === null;
+}
+exports.isNull = isNull;
+
+function isNullOrUndefined(arg) {
+  return arg == null;
+}
+exports.isNullOrUndefined = isNullOrUndefined;
+
+function isNumber(arg) {
+  return typeof arg === 'number';
+}
+exports.isNumber = isNumber;
+
+function isString(arg) {
+  return typeof arg === 'string';
+}
+exports.isString = isString;
+
+function isSymbol(arg) {
+  return typeof arg === 'symbol';
+}
+exports.isSymbol = isSymbol;
+
+function isUndefined(arg) {
+  return arg === void 0;
+}
+exports.isUndefined = isUndefined;
+
+function isRegExp(re) {
+  return objectToString(re) === '[object RegExp]';
+}
+exports.isRegExp = isRegExp;
+
+function isObject(arg) {
+  return typeof arg === 'object' && arg !== null;
+}
+exports.isObject = isObject;
+
+function isDate(d) {
+  return objectToString(d) === '[object Date]';
+}
+exports.isDate = isDate;
+
+function isError(e) {
+  return (objectToString(e) === '[object Error]' || e instanceof Error);
+}
+exports.isError = isError;
+
+function isFunction(arg) {
+  return typeof arg === 'function';
+}
+exports.isFunction = isFunction;
+
+function isPrimitive(arg) {
+  return arg === null ||
+         typeof arg === 'boolean' ||
+         typeof arg === 'number' ||
+         typeof arg === 'string' ||
+         typeof arg === 'symbol' ||  // ES6 symbol
+         typeof arg === 'undefined';
+}
+exports.isPrimitive = isPrimitive;
+
+exports.isBuffer = Buffer.isBuffer;
+
+function objectToString(o) {
+  return Object.prototype.toString.call(o);
+}
+
+}).call(this,{"isBuffer":require("../../is-buffer/index.js")})
+},{"../../is-buffer/index.js":210}],207:[function(require,module,exports){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+function EventEmitter() {
+  this._events = this._events || {};
+  this._maxListeners = this._maxListeners || undefined;
+}
+module.exports = EventEmitter;
+
+// Backwards-compat with node 0.10.x
+EventEmitter.EventEmitter = EventEmitter;
+
+EventEmitter.prototype._events = undefined;
+EventEmitter.prototype._maxListeners = undefined;
+
+// By default EventEmitters will print a warning if more than 10 listeners are
+// added to it. This is a useful default which helps finding memory leaks.
+EventEmitter.defaultMaxListeners = 10;
+
+// Obviously not all Emitters should be limited to 10. This function allows
+// that to be increased. Set to zero for unlimited.
+EventEmitter.prototype.setMaxListeners = function(n) {
+  if (!isNumber(n) || n < 0 || isNaN(n))
+    throw TypeError('n must be a positive number');
+  this._maxListeners = n;
+  return this;
+};
+
+EventEmitter.prototype.emit = function(type) {
+  var er, handler, len, args, i, listeners;
+
+  if (!this._events)
+    this._events = {};
+
+  // If there is no 'error' event listener then throw.
+  if (type === 'error') {
+    if (!this._events.error ||
+        (isObject(this._events.error) && !this._events.error.length)) {
+      er = arguments[1];
+      if (er instanceof Error) {
+        throw er; // Unhandled 'error' event
+      } else {
+        // At least give some kind of context to the user
+        var err = new Error('Uncaught, unspecified "error" event. (' + er + ')');
+        err.context = er;
+        throw err;
+      }
+    }
+  }
+
+  handler = this._events[type];
+
+  if (isUndefined(handler))
+    return false;
+
+  if (isFunction(handler)) {
+    switch (arguments.length) {
+      // fast cases
+      case 1:
+        handler.call(this);
+        break;
+      case 2:
+        handler.call(this, arguments[1]);
+        break;
+      case 3:
+        handler.call(this, arguments[1], arguments[2]);
+        break;
+      // slower
+      default:
+        args = Array.prototype.slice.call(arguments, 1);
+        handler.apply(this, args);
+    }
+  } else if (isObject(handler)) {
+    args = Array.prototype.slice.call(arguments, 1);
+    listeners = handler.slice();
+    len = listeners.length;
+    for (i = 0; i < len; i++)
+      listeners[i].apply(this, args);
+  }
+
+  return true;
+};
+
+EventEmitter.prototype.addListener = function(type, listener) {
+  var m;
+
+  if (!isFunction(listener))
+    throw TypeError('listener must be a function');
+
+  if (!this._events)
+    this._events = {};
+
+  // To avoid recursion in the case that type === "newListener"! Before
+  // adding it to the listeners, first emit "newListener".
+  if (this._events.newListener)
+    this.emit('newListener', type,
+              isFunction(listener.listener) ?
+              listener.listener : listener);
+
+  if (!this._events[type])
+    // Optimize the case of one listener. Don't need the extra array object.
+    this._events[type] = listener;
+  else if (isObject(this._events[type]))
+    // If we've already got an array, just append.
+    this._events[type].push(listener);
+  else
+    // Adding the second element, need to change to array.
+    this._events[type] = [this._events[type], listener];
+
+  // Check for listener leak
+  if (isObject(this._events[type]) && !this._events[type].warned) {
+    if (!isUndefined(this._maxListeners)) {
+      m = this._maxListeners;
+    } else {
+      m = EventEmitter.defaultMaxListeners;
+    }
+
+    if (m && m > 0 && this._events[type].length > m) {
+      this._events[type].warned = true;
+      console.error('(node) warning: possible EventEmitter memory ' +
+                    'leak detected. %d listeners added. ' +
+                    'Use emitter.setMaxListeners() to increase limit.',
+                    this._events[type].length);
+      if (typeof console.trace === 'function') {
+        // not supported in IE 10
+        console.trace();
+      }
+    }
+  }
+
+  return this;
+};
+
+EventEmitter.prototype.on = EventEmitter.prototype.addListener;
+
+EventEmitter.prototype.once = function(type, listener) {
+  if (!isFunction(listener))
+    throw TypeError('listener must be a function');
+
+  var fired = false;
+
+  function g() {
+    this.removeListener(type, g);
+
+    if (!fired) {
+      fired = true;
+      listener.apply(this, arguments);
+    }
+  }
+
+  g.listener = listener;
+  this.on(type, g);
+
+  return this;
+};
+
+// emits a 'removeListener' event iff the listener was removed
+EventEmitter.prototype.removeListener = function(type, listener) {
+  var list, position, length, i;
+
+  if (!isFunction(listener))
+    throw TypeError('listener must be a function');
+
+  if (!this._events || !this._events[type])
+    return this;
+
+  list = this._events[type];
+  length = list.length;
+  position = -1;
+
+  if (list === listener ||
+      (isFunction(list.listener) && list.listener === listener)) {
+    delete this._events[type];
+    if (this._events.removeListener)
+      this.emit('removeListener', type, listener);
+
+  } else if (isObject(list)) {
+    for (i = length; i-- > 0;) {
+      if (list[i] === listener ||
+          (list[i].listener && list[i].listener === listener)) {
+        position = i;
+        break;
+      }
+    }
+
+    if (position < 0)
+      return this;
+
+    if (list.length === 1) {
+      list.length = 0;
+      delete this._events[type];
+    } else {
+      list.splice(position, 1);
+    }
+
+    if (this._events.removeListener)
+      this.emit('removeListener', type, listener);
+  }
+
+  return this;
+};
+
+EventEmitter.prototype.removeAllListeners = function(type) {
+  var key, listeners;
+
+  if (!this._events)
+    return this;
+
+  // not listening for removeListener, no need to emit
+  if (!this._events.removeListener) {
+    if (arguments.length === 0)
+      this._events = {};
+    else if (this._events[type])
+      delete this._events[type];
+    return this;
+  }
+
+  // emit removeListener for all listeners on all events
+  if (arguments.length === 0) {
+    for (key in this._events) {
+      if (key === 'removeListener') continue;
+      this.removeAllListeners(key);
+    }
+    this.removeAllListeners('removeListener');
+    this._events = {};
+    return this;
+  }
+
+  listeners = this._events[type];
+
+  if (isFunction(listeners)) {
+    this.removeListener(type, listeners);
+  } else if (listeners) {
+    // LIFO order
+    while (listeners.length)
+      this.removeListener(type, listeners[listeners.length - 1]);
+  }
+  delete this._events[type];
+
+  return this;
+};
+
+EventEmitter.prototype.listeners = function(type) {
+  var ret;
+  if (!this._events || !this._events[type])
+    ret = [];
+  else if (isFunction(this._events[type]))
+    ret = [this._events[type]];
+  else
+    ret = this._events[type].slice();
+  return ret;
+};
+
+EventEmitter.prototype.listenerCount = function(type) {
+  if (this._events) {
+    var evlistener = this._events[type];
+
+    if (isFunction(evlistener))
+      return 1;
+    else if (evlistener)
+      return evlistener.length;
+  }
+  return 0;
+};
+
+EventEmitter.listenerCount = function(emitter, type) {
+  return emitter.listenerCount(type);
+};
+
+function isFunction(arg) {
+  return typeof arg === 'function';
+}
+
+function isNumber(arg) {
+  return typeof arg === 'number';
+}
+
+function isObject(arg) {
+  return typeof arg === 'object' && arg !== null;
+}
+
+function isUndefined(arg) {
+  return arg === void 0;
+}
+
+},{}],208:[function(require,module,exports){
+exports.read = function (buffer, offset, isLE, mLen, nBytes) {
+  var e, m
+  var eLen = nBytes * 8 - mLen - 1
+  var eMax = (1 << eLen) - 1
+  var eBias = eMax >> 1
+  var nBits = -7
+  var i = isLE ? (nBytes - 1) : 0
+  var d = isLE ? -1 : 1
+  var s = buffer[offset + i]
+
+  i += d
+
+  e = s & ((1 << (-nBits)) - 1)
+  s >>= (-nBits)
+  nBits += eLen
+  for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {}
+
+  m = e & ((1 << (-nBits)) - 1)
+  e >>= (-nBits)
+  nBits += mLen
+  for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {}
+
+  if (e === 0) {
+    e = 1 - eBias
+  } else if (e === eMax) {
+    return m ? NaN : ((s ? -1 : 1) * Infinity)
+  } else {
+    m = m + Math.pow(2, mLen)
+    e = e - eBias
+  }
+  return (s ? -1 : 1) * m * Math.pow(2, e - mLen)
+}
+
+exports.write = function (buffer, value, offset, isLE, mLen, nBytes) {
+  var e, m, c
+  var eLen = nBytes * 8 - mLen - 1
+  var eMax = (1 << eLen) - 1
+  var eBias = eMax >> 1
+  var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)
+  var i = isLE ? 0 : (nBytes - 1)
+  var d = isLE ? 1 : -1
+  var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0
+
+  value = Math.abs(value)
+
+  if (isNaN(value) || value === Infinity) {
+    m = isNaN(value) ? 1 : 0
+    e = eMax
+  } else {
+    e = Math.floor(Math.log(value) / Math.LN2)
+    if (value * (c = Math.pow(2, -e)) < 1) {
+      e--
+      c *= 2
+    }
+    if (e + eBias >= 1) {
+      value += rt / c
+    } else {
+      value += rt * Math.pow(2, 1 - eBias)
+    }
+    if (value * c >= 2) {
+      e++
+      c /= 2
+    }
+
+    if (e + eBias >= eMax) {
+      m = 0
+      e = eMax
+    } else if (e + eBias >= 1) {
+      m = (value * c - 1) * Math.pow(2, mLen)
+      e = e + eBias
+    } else {
+      m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)
+      e = 0
+    }
+  }
+
+  for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
+
+  e = (e << mLen) | m
+  eLen += mLen
+  for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
+
+  buffer[offset + i - d] |= s * 128
+}
+
+},{}],209:[function(require,module,exports){
+if (typeof Object.create === 'function') {
+  // implementation from standard node.js 'util' module
+  module.exports = function inherits(ctor, superCtor) {
+    ctor.super_ = superCtor
+    ctor.prototype = Object.create(superCtor.prototype, {
+      constructor: {
+        value: ctor,
+        enumerable: false,
+        writable: true,
+        configurable: true
+      }
+    });
+  };
+} else {
+  // old school shim for old browsers
+  module.exports = function inherits(ctor, superCtor) {
+    ctor.super_ = superCtor
+    var TempCtor = function () {}
+    TempCtor.prototype = superCtor.prototype
+    ctor.prototype = new TempCtor()
+    ctor.prototype.constructor = ctor
+  }
+}
+
+},{}],210:[function(require,module,exports){
+/*!
+ * Determine if an object is a Buffer
+ *
+ * @author   Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
+ * @license  MIT
+ */
+
+// The _isBuffer check is for Safari 5-7 support, because it's missing
+// Object.prototype.constructor. Remove this eventually
+module.exports = function (obj) {
+  return obj != null && (isBuffer(obj) || isSlowBuffer(obj) || !!obj._isBuffer)
+}
+
+function isBuffer (obj) {
+  return !!obj.constructor && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj)
+}
+
+// For Node v0.10 support. Remove this eventually.
+function isSlowBuffer (obj) {
+  return typeof obj.readFloatLE === 'function' && typeof obj.slice === 'function' && isBuffer(obj.slice(0, 0))
+}
+
+},{}],211:[function(require,module,exports){
+var toString = {}.toString;
+
+module.exports = Array.isArray || function (arr) {
+  return toString.call(arr) == '[object Array]';
+};
+
+},{}],212:[function(require,module,exports){
+'use strict';
+
+
+var TYPED_OK =  (typeof Uint8Array !== 'undefined') &&
+                (typeof Uint16Array !== 'undefined') &&
+                (typeof Int32Array !== 'undefined');
+
+
+exports.assign = function (obj /*from1, from2, from3, ...*/) {
+  var sources = Array.prototype.slice.call(arguments, 1);
+  while (sources.length) {
+    var source = sources.shift();
+    if (!source) { continue; }
+
+    if (typeof source !== 'object') {
+      throw new TypeError(source + 'must be non-object');
+    }
+
+    for (var p in source) {
+      if (source.hasOwnProperty(p)) {
+        obj[p] = source[p];
+      }
+    }
+  }
+
+  return obj;
 };
 
 
-
-exports.normalize=function(path){
-var isAbsolute=exports.isAbsolute(path),
-trailingSlash=substr(path,-1)==='/';
-
-
-path=normalizeArray(filter(path.split('/'),function(p){
-return!!p;
-}),!isAbsolute).join('/');
-
-if(!path&&!isAbsolute){
-path='.';
-}
-if(path&&trailingSlash){
-path+='/';
-}
-
-return(isAbsolute?'/':'')+path;
+// reduce buffer size, avoiding mem copy
+exports.shrinkBuf = function (buf, size) {
+  if (buf.length === size) { return buf; }
+  if (buf.subarray) { return buf.subarray(0, size); }
+  buf.length = size;
+  return buf;
 };
 
 
-exports.isAbsolute=function(path){
-return path.charAt(0)==='/';
+var fnTyped = {
+  arraySet: function (dest, src, src_offs, len, dest_offs) {
+    if (src.subarray && dest.subarray) {
+      dest.set(src.subarray(src_offs, src_offs + len), dest_offs);
+      return;
+    }
+    // Fallback to ordinary array
+    for (var i = 0; i < len; i++) {
+      dest[dest_offs + i] = src[src_offs + i];
+    }
+  },
+  // Join array of chunks to single array.
+  flattenChunks: function (chunks) {
+    var i, l, len, pos, chunk, result;
+
+    // calculate data length
+    len = 0;
+    for (i = 0, l = chunks.length; i < l; i++) {
+      len += chunks[i].length;
+    }
+
+    // join chunks
+    result = new Uint8Array(len);
+    pos = 0;
+    for (i = 0, l = chunks.length; i < l; i++) {
+      chunk = chunks[i];
+      result.set(chunk, pos);
+      pos += chunk.length;
+    }
+
+    return result;
+  }
+};
+
+var fnUntyped = {
+  arraySet: function (dest, src, src_offs, len, dest_offs) {
+    for (var i = 0; i < len; i++) {
+      dest[dest_offs + i] = src[src_offs + i];
+    }
+  },
+  // Join array of chunks to single array.
+  flattenChunks: function (chunks) {
+    return [].concat.apply([], chunks);
+  }
 };
 
 
-exports.join=function(){
-var paths=Array.prototype.slice.call(arguments,0);
-return exports.normalize(filter(paths,function(p,index){
-if(typeof p!=='string'){
-throw new TypeError('Arguments to path.join must be strings');
+// Enable/Disable typed arrays use, for testing
+//
+exports.setTyped = function (on) {
+  if (on) {
+    exports.Buf8  = Uint8Array;
+    exports.Buf16 = Uint16Array;
+    exports.Buf32 = Int32Array;
+    exports.assign(exports, fnTyped);
+  } else {
+    exports.Buf8  = Array;
+    exports.Buf16 = Array;
+    exports.Buf32 = Array;
+    exports.assign(exports, fnUntyped);
+  }
+};
+
+exports.setTyped(TYPED_OK);
+
+},{}],213:[function(require,module,exports){
+'use strict';
+
+// Note: adler32 takes 12% for level 0 and 2% for level 6.
+// It doesn't worth to make additional optimizationa as in original.
+// Small size is preferable.
+
+function adler32(adler, buf, len, pos) {
+  var s1 = (adler & 0xffff) |0,
+      s2 = ((adler >>> 16) & 0xffff) |0,
+      n = 0;
+
+  while (len !== 0) {
+    // Set limit ~ twice less than 5552, to keep
+    // s2 in 31-bits, because we force signed ints.
+    // in other case %= will fail.
+    n = len > 2000 ? 2000 : len;
+    len -= n;
+
+    do {
+      s1 = (s1 + buf[pos++]) |0;
+      s2 = (s2 + s1) |0;
+    } while (--n);
+
+    s1 %= 65521;
+    s2 %= 65521;
+  }
+
+  return (s1 | (s2 << 16)) |0;
 }
-return p;
-}).join('/'));
+
+
+module.exports = adler32;
+
+},{}],214:[function(require,module,exports){
+'use strict';
+
+
+module.exports = {
+
+  /* Allowed flush values; see deflate() and inflate() below for details */
+  Z_NO_FLUSH:         0,
+  Z_PARTIAL_FLUSH:    1,
+  Z_SYNC_FLUSH:       2,
+  Z_FULL_FLUSH:       3,
+  Z_FINISH:           4,
+  Z_BLOCK:            5,
+  Z_TREES:            6,
+
+  /* Return codes for the compression/decompression functions. Negative values
+  * are errors, positive values are used for special but normal events.
+  */
+  Z_OK:               0,
+  Z_STREAM_END:       1,
+  Z_NEED_DICT:        2,
+  Z_ERRNO:           -1,
+  Z_STREAM_ERROR:    -2,
+  Z_DATA_ERROR:      -3,
+  //Z_MEM_ERROR:     -4,
+  Z_BUF_ERROR:       -5,
+  //Z_VERSION_ERROR: -6,
+
+  /* compression levels */
+  Z_NO_COMPRESSION:         0,
+  Z_BEST_SPEED:             1,
+  Z_BEST_COMPRESSION:       9,
+  Z_DEFAULT_COMPRESSION:   -1,
+
+
+  Z_FILTERED:               1,
+  Z_HUFFMAN_ONLY:           2,
+  Z_RLE:                    3,
+  Z_FIXED:                  4,
+  Z_DEFAULT_STRATEGY:       0,
+
+  /* Possible values of the data_type field (though see inflate()) */
+  Z_BINARY:                 0,
+  Z_TEXT:                   1,
+  //Z_ASCII:                1, // = Z_TEXT (deprecated)
+  Z_UNKNOWN:                2,
+
+  /* The deflate compression method */
+  Z_DEFLATED:               8
+  //Z_NULL:                 null // Use -1 or null inline, depending on var type
+};
+
+},{}],215:[function(require,module,exports){
+'use strict';
+
+// Note: we can't get significant speed boost here.
+// So write code to minimize size - no pregenerated tables
+// and array tools dependencies.
+
+
+// Use ordinary array, since untyped makes no boost here
+function makeTable() {
+  var c, table = [];
+
+  for (var n = 0; n < 256; n++) {
+    c = n;
+    for (var k = 0; k < 8; k++) {
+      c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
+    }
+    table[n] = c;
+  }
+
+  return table;
+}
+
+// Create table on load. Just 255 signed longs. Not a problem.
+var crcTable = makeTable();
+
+
+function crc32(crc, buf, len, pos) {
+  var t = crcTable,
+      end = pos + len;
+
+  crc ^= -1;
+
+  for (var i = pos; i < end; i++) {
+    crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF];
+  }
+
+  return (crc ^ (-1)); // >>> 0;
+}
+
+
+module.exports = crc32;
+
+},{}],216:[function(require,module,exports){
+'use strict';
+
+var utils   = require('../utils/common');
+var trees   = require('./trees');
+var adler32 = require('./adler32');
+var crc32   = require('./crc32');
+var msg     = require('./messages');
+
+/* Public constants ==========================================================*/
+/* ===========================================================================*/
+
+
+/* Allowed flush values; see deflate() and inflate() below for details */
+var Z_NO_FLUSH      = 0;
+var Z_PARTIAL_FLUSH = 1;
+//var Z_SYNC_FLUSH    = 2;
+var Z_FULL_FLUSH    = 3;
+var Z_FINISH        = 4;
+var Z_BLOCK         = 5;
+//var Z_TREES         = 6;
+
+
+/* Return codes for the compression/decompression functions. Negative values
+ * are errors, positive values are used for special but normal events.
+ */
+var Z_OK            = 0;
+var Z_STREAM_END    = 1;
+//var Z_NEED_DICT     = 2;
+//var Z_ERRNO         = -1;
+var Z_STREAM_ERROR  = -2;
+var Z_DATA_ERROR    = -3;
+//var Z_MEM_ERROR     = -4;
+var Z_BUF_ERROR     = -5;
+//var Z_VERSION_ERROR = -6;
+
+
+/* compression levels */
+//var Z_NO_COMPRESSION      = 0;
+//var Z_BEST_SPEED          = 1;
+//var Z_BEST_COMPRESSION    = 9;
+var Z_DEFAULT_COMPRESSION = -1;
+
+
+var Z_FILTERED            = 1;
+var Z_HUFFMAN_ONLY        = 2;
+var Z_RLE                 = 3;
+var Z_FIXED               = 4;
+var Z_DEFAULT_STRATEGY    = 0;
+
+/* Possible values of the data_type field (though see inflate()) */
+//var Z_BINARY              = 0;
+//var Z_TEXT                = 1;
+//var Z_ASCII               = 1; // = Z_TEXT
+var Z_UNKNOWN             = 2;
+
+
+/* The deflate compression method */
+var Z_DEFLATED  = 8;
+
+/*============================================================================*/
+
+
+var MAX_MEM_LEVEL = 9;
+/* Maximum value for memLevel in deflateInit2 */
+var MAX_WBITS = 15;
+/* 32K LZ77 window */
+var DEF_MEM_LEVEL = 8;
+
+
+var LENGTH_CODES  = 29;
+/* number of length codes, not counting the special END_BLOCK code */
+var LITERALS      = 256;
+/* number of literal bytes 0..255 */
+var L_CODES       = LITERALS + 1 + LENGTH_CODES;
+/* number of Literal or Length codes, including the END_BLOCK code */
+var D_CODES       = 30;
+/* number of distance codes */
+var BL_CODES      = 19;
+/* number of codes used to transfer the bit lengths */
+var HEAP_SIZE     = 2 * L_CODES + 1;
+/* maximum heap size */
+var MAX_BITS  = 15;
+/* All codes must not exceed MAX_BITS bits */
+
+var MIN_MATCH = 3;
+var MAX_MATCH = 258;
+var MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1);
+
+var PRESET_DICT = 0x20;
+
+var INIT_STATE = 42;
+var EXTRA_STATE = 69;
+var NAME_STATE = 73;
+var COMMENT_STATE = 91;
+var HCRC_STATE = 103;
+var BUSY_STATE = 113;
+var FINISH_STATE = 666;
+
+var BS_NEED_MORE      = 1; /* block not completed, need more input or more output */
+var BS_BLOCK_DONE     = 2; /* block flush performed */
+var BS_FINISH_STARTED = 3; /* finish started, need only more output at next deflate */
+var BS_FINISH_DONE    = 4; /* finish done, accept no more input or output */
+
+var OS_CODE = 0x03; // Unix :) . Don't detect, use this default.
+
+function err(strm, errorCode) {
+  strm.msg = msg[errorCode];
+  return errorCode;
+}
+
+function rank(f) {
+  return ((f) << 1) - ((f) > 4 ? 9 : 0);
+}
+
+function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } }
+
+
+/* =========================================================================
+ * Flush as much pending output as possible. All deflate() output goes
+ * through this function so some applications may wish to modify it
+ * to avoid allocating a large strm->output buffer and copying into it.
+ * (See also read_buf()).
+ */
+function flush_pending(strm) {
+  var s = strm.state;
+
+  //_tr_flush_bits(s);
+  var len = s.pending;
+  if (len > strm.avail_out) {
+    len = strm.avail_out;
+  }
+  if (len === 0) { return; }
+
+  utils.arraySet(strm.output, s.pending_buf, s.pending_out, len, strm.next_out);
+  strm.next_out += len;
+  s.pending_out += len;
+  strm.total_out += len;
+  strm.avail_out -= len;
+  s.pending -= len;
+  if (s.pending === 0) {
+    s.pending_out = 0;
+  }
+}
+
+
+function flush_block_only(s, last) {
+  trees._tr_flush_block(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last);
+  s.block_start = s.strstart;
+  flush_pending(s.strm);
+}
+
+
+function put_byte(s, b) {
+  s.pending_buf[s.pending++] = b;
+}
+
+
+/* =========================================================================
+ * Put a short in the pending buffer. The 16-bit value is put in MSB order.
+ * IN assertion: the stream state is correct and there is enough room in
+ * pending_buf.
+ */
+function putShortMSB(s, b) {
+//  put_byte(s, (Byte)(b >> 8));
+//  put_byte(s, (Byte)(b & 0xff));
+  s.pending_buf[s.pending++] = (b >>> 8) & 0xff;
+  s.pending_buf[s.pending++] = b & 0xff;
+}
+
+
+/* ===========================================================================
+ * Read a new buffer from the current input stream, update the adler32
+ * and total number of bytes read.  All deflate() input goes through
+ * this function so some applications may wish to modify it to avoid
+ * allocating a large strm->input buffer and copying from it.
+ * (See also flush_pending()).
+ */
+function read_buf(strm, buf, start, size) {
+  var len = strm.avail_in;
+
+  if (len > size) { len = size; }
+  if (len === 0) { return 0; }
+
+  strm.avail_in -= len;
+
+  // zmemcpy(buf, strm->next_in, len);
+  utils.arraySet(buf, strm.input, strm.next_in, len, start);
+  if (strm.state.wrap === 1) {
+    strm.adler = adler32(strm.adler, buf, len, start);
+  }
+
+  else if (strm.state.wrap === 2) {
+    strm.adler = crc32(strm.adler, buf, len, start);
+  }
+
+  strm.next_in += len;
+  strm.total_in += len;
+
+  return len;
+}
+
+
+/* ===========================================================================
+ * Set match_start to the longest match starting at the given string and
+ * return its length. Matches shorter or equal to prev_length are discarded,
+ * in which case the result is equal to prev_length and match_start is
+ * garbage.
+ * IN assertions: cur_match is the head of the hash chain for the current
+ *   string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1
+ * OUT assertion: the match length is not greater than s->lookahead.
+ */
+function longest_match(s, cur_match) {
+  var chain_length = s.max_chain_length;      /* max hash chain length */
+  var scan = s.strstart; /* current string */
+  var match;                       /* matched string */
+  var len;                           /* length of current match */
+  var best_len = s.prev_length;              /* best match length so far */
+  var nice_match = s.nice_match;             /* stop if match long enough */
+  var limit = (s.strstart > (s.w_size - MIN_LOOKAHEAD)) ?
+      s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0/*NIL*/;
+
+  var _win = s.window; // shortcut
+
+  var wmask = s.w_mask;
+  var prev  = s.prev;
+
+  /* Stop when cur_match becomes <= limit. To simplify the code,
+   * we prevent matches with the string of window index 0.
+   */
+
+  var strend = s.strstart + MAX_MATCH;
+  var scan_end1  = _win[scan + best_len - 1];
+  var scan_end   = _win[scan + best_len];
+
+  /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16.
+   * It is easy to get rid of this optimization if necessary.
+   */
+  // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever");
+
+  /* Do not waste too much time if we already have a good match: */
+  if (s.prev_length >= s.good_match) {
+    chain_length >>= 2;
+  }
+  /* Do not look for matches beyond the end of the input. This is necessary
+   * to make deflate deterministic.
+   */
+  if (nice_match > s.lookahead) { nice_match = s.lookahead; }
+
+  // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead");
+
+  do {
+    // Assert(cur_match < s->strstart, "no future");
+    match = cur_match;
+
+    /* Skip to next match if the match length cannot increase
+     * or if the match length is less than 2.  Note that the checks below
+     * for insufficient lookahead only occur occasionally for performance
+     * reasons.  Therefore uninitialized memory will be accessed, and
+     * conditional jumps will be made that depend on those values.
+     * However the length of the match is limited to the lookahead, so
+     * the output of deflate is not affected by the uninitialized values.
+     */
+
+    if (_win[match + best_len]     !== scan_end  ||
+        _win[match + best_len - 1] !== scan_end1 ||
+        _win[match]                !== _win[scan] ||
+        _win[++match]              !== _win[scan + 1]) {
+      continue;
+    }
+
+    /* The check at best_len-1 can be removed because it will be made
+     * again later. (This heuristic is not always a win.)
+     * It is not necessary to compare scan[2] and match[2] since they
+     * are always equal when the other bytes match, given that
+     * the hash keys are equal and that HASH_BITS >= 8.
+     */
+    scan += 2;
+    match++;
+    // Assert(*scan == *match, "match[2]?");
+
+    /* We check for insufficient lookahead only every 8th comparison;
+     * the 256th check will be made at strstart+258.
+     */
+    do {
+      /*jshint noempty:false*/
+    } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
+             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
+             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
+             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
+             scan < strend);
+
+    // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
+
+    len = MAX_MATCH - (strend - scan);
+    scan = strend - MAX_MATCH;
+
+    if (len > best_len) {
+      s.match_start = cur_match;
+      best_len = len;
+      if (len >= nice_match) {
+        break;
+      }
+      scan_end1  = _win[scan + best_len - 1];
+      scan_end   = _win[scan + best_len];
+    }
+  } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0);
+
+  if (best_len <= s.lookahead) {
+    return best_len;
+  }
+  return s.lookahead;
+}
+
+
+/* ===========================================================================
+ * Fill the window when the lookahead becomes insufficient.
+ * Updates strstart and lookahead.
+ *
+ * IN assertion: lookahead < MIN_LOOKAHEAD
+ * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD
+ *    At least one byte has been read, or avail_in == 0; reads are
+ *    performed for at least two bytes (required for the zip translate_eol
+ *    option -- not supported here).
+ */
+function fill_window(s) {
+  var _w_size = s.w_size;
+  var p, n, m, more, str;
+
+  //Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead");
+
+  do {
+    more = s.window_size - s.lookahead - s.strstart;
+
+    // JS ints have 32 bit, block below not needed
+    /* Deal with !@#$% 64K limit: */
+    //if (sizeof(int) <= 2) {
+    //    if (more == 0 && s->strstart == 0 && s->lookahead == 0) {
+    //        more = wsize;
+    //
+    //  } else if (more == (unsigned)(-1)) {
+    //        /* Very unlikely, but possible on 16 bit machine if
+    //         * strstart == 0 && lookahead == 1 (input done a byte at time)
+    //         */
+    //        more--;
+    //    }
+    //}
+
+
+    /* If the window is almost full and there is insufficient lookahead,
+     * move the upper half to the lower one to make room in the upper half.
+     */
+    if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) {
+
+      utils.arraySet(s.window, s.window, _w_size, _w_size, 0);
+      s.match_start -= _w_size;
+      s.strstart -= _w_size;
+      /* we now have strstart >= MAX_DIST */
+      s.block_start -= _w_size;
+
+      /* Slide the hash table (could be avoided with 32 bit values
+       at the expense of memory usage). We slide even when level == 0
+       to keep the hash table consistent if we switch back to level > 0
+       later. (Using level 0 permanently is not an optimal usage of
+       zlib, so we don't care about this pathological case.)
+       */
+
+      n = s.hash_size;
+      p = n;
+      do {
+        m = s.head[--p];
+        s.head[p] = (m >= _w_size ? m - _w_size : 0);
+      } while (--n);
+
+      n = _w_size;
+      p = n;
+      do {
+        m = s.prev[--p];
+        s.prev[p] = (m >= _w_size ? m - _w_size : 0);
+        /* If n is not on any hash chain, prev[n] is garbage but
+         * its value will never be used.
+         */
+      } while (--n);
+
+      more += _w_size;
+    }
+    if (s.strm.avail_in === 0) {
+      break;
+    }
+
+    /* If there was no sliding:
+     *    strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 &&
+     *    more == window_size - lookahead - strstart
+     * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1)
+     * => more >= window_size - 2*WSIZE + 2
+     * In the BIG_MEM or MMAP case (not yet supported),
+     *   window_size == input_size + MIN_LOOKAHEAD  &&
+     *   strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD.
+     * Otherwise, window_size == 2*WSIZE so more >= 2.
+     * If there was sliding, more >= WSIZE. So in all cases, more >= 2.
+     */
+    //Assert(more >= 2, "more < 2");
+    n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more);
+    s.lookahead += n;
+
+    /* Initialize the hash value now that we have some input: */
+    if (s.lookahead + s.insert >= MIN_MATCH) {
+      str = s.strstart - s.insert;
+      s.ins_h = s.window[str];
+
+      /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */
+      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + 1]) & s.hash_mask;
+//#if MIN_MATCH != 3
+//        Call update_hash() MIN_MATCH-3 more times
+//#endif
+      while (s.insert) {
+        /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
+        s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask;
+
+        s.prev[str & s.w_mask] = s.head[s.ins_h];
+        s.head[s.ins_h] = str;
+        str++;
+        s.insert--;
+        if (s.lookahead + s.insert < MIN_MATCH) {
+          break;
+        }
+      }
+    }
+    /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage,
+     * but this is not important since only literal bytes will be emitted.
+     */
+
+  } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0);
+
+  /* If the WIN_INIT bytes after the end of the current data have never been
+   * written, then zero those bytes in order to avoid memory check reports of
+   * the use of uninitialized (or uninitialised as Julian writes) bytes by
+   * the longest match routines.  Update the high water mark for the next
+   * time through here.  WIN_INIT is set to MAX_MATCH since the longest match
+   * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead.
+   */
+//  if (s.high_water < s.window_size) {
+//    var curr = s.strstart + s.lookahead;
+//    var init = 0;
+//
+//    if (s.high_water < curr) {
+//      /* Previous high water mark below current data -- zero WIN_INIT
+//       * bytes or up to end of window, whichever is less.
+//       */
+//      init = s.window_size - curr;
+//      if (init > WIN_INIT)
+//        init = WIN_INIT;
+//      zmemzero(s->window + curr, (unsigned)init);
+//      s->high_water = curr + init;
+//    }
+//    else if (s->high_water < (ulg)curr + WIN_INIT) {
+//      /* High water mark at or above current data, but below current data
+//       * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up
+//       * to end of window, whichever is less.
+//       */
+//      init = (ulg)curr + WIN_INIT - s->high_water;
+//      if (init > s->window_size - s->high_water)
+//        init = s->window_size - s->high_water;
+//      zmemzero(s->window + s->high_water, (unsigned)init);
+//      s->high_water += init;
+//    }
+//  }
+//
+//  Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD,
+//    "not enough room for search");
+}
+
+/* ===========================================================================
+ * Copy without compression as much as possible from the input stream, return
+ * the current block state.
+ * This function does not insert new strings in the dictionary since
+ * uncompressible data is probably not useful. This function is used
+ * only for the level=0 compression option.
+ * NOTE: this function should be optimized to avoid extra copying from
+ * window to pending_buf.
+ */
+function deflate_stored(s, flush) {
+  /* Stored blocks are limited to 0xffff bytes, pending_buf is limited
+   * to pending_buf_size, and each stored block has a 5 byte header:
+   */
+  var max_block_size = 0xffff;
+
+  if (max_block_size > s.pending_buf_size - 5) {
+    max_block_size = s.pending_buf_size - 5;
+  }
+
+  /* Copy as much as possible from input to output: */
+  for (;;) {
+    /* Fill the window as much as possible: */
+    if (s.lookahead <= 1) {
+
+      //Assert(s->strstart < s->w_size+MAX_DIST(s) ||
+      //  s->block_start >= (long)s->w_size, "slide too late");
+//      if (!(s.strstart < s.w_size + (s.w_size - MIN_LOOKAHEAD) ||
+//        s.block_start >= s.w_size)) {
+//        throw  new Error("slide too late");
+//      }
+
+      fill_window(s);
+      if (s.lookahead === 0 && flush === Z_NO_FLUSH) {
+        return BS_NEED_MORE;
+      }
+
+      if (s.lookahead === 0) {
+        break;
+      }
+      /* flush the current block */
+    }
+    //Assert(s->block_start >= 0L, "block gone");
+//    if (s.block_start < 0) throw new Error("block gone");
+
+    s.strstart += s.lookahead;
+    s.lookahead = 0;
+
+    /* Emit a stored block if pending_buf will be full: */
+    var max_start = s.block_start + max_block_size;
+
+    if (s.strstart === 0 || s.strstart >= max_start) {
+      /* strstart == 0 is possible when wraparound on 16-bit machine */
+      s.lookahead = s.strstart - max_start;
+      s.strstart = max_start;
+      /*** FLUSH_BLOCK(s, 0); ***/
+      flush_block_only(s, false);
+      if (s.strm.avail_out === 0) {
+        return BS_NEED_MORE;
+      }
+      /***/
+
+
+    }
+    /* Flush if we may have to slide, otherwise block_start may become
+     * negative and the data will be gone:
+     */
+    if (s.strstart - s.block_start >= (s.w_size - MIN_LOOKAHEAD)) {
+      /*** FLUSH_BLOCK(s, 0); ***/
+      flush_block_only(s, false);
+      if (s.strm.avail_out === 0) {
+        return BS_NEED_MORE;
+      }
+      /***/
+    }
+  }
+
+  s.insert = 0;
+
+  if (flush === Z_FINISH) {
+    /*** FLUSH_BLOCK(s, 1); ***/
+    flush_block_only(s, true);
+    if (s.strm.avail_out === 0) {
+      return BS_FINISH_STARTED;
+    }
+    /***/
+    return BS_FINISH_DONE;
+  }
+
+  if (s.strstart > s.block_start) {
+    /*** FLUSH_BLOCK(s, 0); ***/
+    flush_block_only(s, false);
+    if (s.strm.avail_out === 0) {
+      return BS_NEED_MORE;
+    }
+    /***/
+  }
+
+  return BS_NEED_MORE;
+}
+
+/* ===========================================================================
+ * Compress as much as possible from the input stream, return the current
+ * block state.
+ * This function does not perform lazy evaluation of matches and inserts
+ * new strings in the dictionary only for unmatched strings or for short
+ * matches. It is used only for the fast compression options.
+ */
+function deflate_fast(s, flush) {
+  var hash_head;        /* head of the hash chain */
+  var bflush;           /* set if current block must be flushed */
+
+  for (;;) {
+    /* Make sure that we always have enough lookahead, except
+     * at the end of the input file. We need MAX_MATCH bytes
+     * for the next match, plus MIN_MATCH bytes to insert the
+     * string following the next match.
+     */
+    if (s.lookahead < MIN_LOOKAHEAD) {
+      fill_window(s);
+      if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {
+        return BS_NEED_MORE;
+      }
+      if (s.lookahead === 0) {
+        break; /* flush the current block */
+      }
+    }
+
+    /* Insert the string window[strstart .. strstart+2] in the
+     * dictionary, and set hash_head to the head of the hash chain:
+     */
+    hash_head = 0/*NIL*/;
+    if (s.lookahead >= MIN_MATCH) {
+      /*** INSERT_STRING(s, s.strstart, hash_head); ***/
+      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
+      hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
+      s.head[s.ins_h] = s.strstart;
+      /***/
+    }
+
+    /* Find the longest match, discarding those <= prev_length.
+     * At this point we have always match_length < MIN_MATCH
+     */
+    if (hash_head !== 0/*NIL*/ && ((s.strstart - hash_head) <= (s.w_size - MIN_LOOKAHEAD))) {
+      /* To simplify the code, we prevent matches with the string
+       * of window index 0 (in particular we have to avoid a match
+       * of the string with itself at the start of the input file).
+       */
+      s.match_length = longest_match(s, hash_head);
+      /* longest_match() sets match_start */
+    }
+    if (s.match_length >= MIN_MATCH) {
+      // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only
+
+      /*** _tr_tally_dist(s, s.strstart - s.match_start,
+                     s.match_length - MIN_MATCH, bflush); ***/
+      bflush = trees._tr_tally(s, s.strstart - s.match_start, s.match_length - MIN_MATCH);
+
+      s.lookahead -= s.match_length;
+
+      /* Insert new strings in the hash table only if the match length
+       * is not too large. This saves time but degrades compression.
+       */
+      if (s.match_length <= s.max_lazy_match/*max_insert_length*/ && s.lookahead >= MIN_MATCH) {
+        s.match_length--; /* string at strstart already in table */
+        do {
+          s.strstart++;
+          /*** INSERT_STRING(s, s.strstart, hash_head); ***/
+          s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
+          hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
+          s.head[s.ins_h] = s.strstart;
+          /***/
+          /* strstart never exceeds WSIZE-MAX_MATCH, so there are
+           * always MIN_MATCH bytes ahead.
+           */
+        } while (--s.match_length !== 0);
+        s.strstart++;
+      } else
+      {
+        s.strstart += s.match_length;
+        s.match_length = 0;
+        s.ins_h = s.window[s.strstart];
+        /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */
+        s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + 1]) & s.hash_mask;
+
+//#if MIN_MATCH != 3
+//                Call UPDATE_HASH() MIN_MATCH-3 more times
+//#endif
+        /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not
+         * matter since it will be recomputed at next deflate call.
+         */
+      }
+    } else {
+      /* No match, output a literal byte */
+      //Tracevv((stderr,"%c", s.window[s.strstart]));
+      /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
+      bflush = trees._tr_tally(s, 0, s.window[s.strstart]);
+
+      s.lookahead--;
+      s.strstart++;
+    }
+    if (bflush) {
+      /*** FLUSH_BLOCK(s, 0); ***/
+      flush_block_only(s, false);
+      if (s.strm.avail_out === 0) {
+        return BS_NEED_MORE;
+      }
+      /***/
+    }
+  }
+  s.insert = ((s.strstart < (MIN_MATCH - 1)) ? s.strstart : MIN_MATCH - 1);
+  if (flush === Z_FINISH) {
+    /*** FLUSH_BLOCK(s, 1); ***/
+    flush_block_only(s, true);
+    if (s.strm.avail_out === 0) {
+      return BS_FINISH_STARTED;
+    }
+    /***/
+    return BS_FINISH_DONE;
+  }
+  if (s.last_lit) {
+    /*** FLUSH_BLOCK(s, 0); ***/
+    flush_block_only(s, false);
+    if (s.strm.avail_out === 0) {
+      return BS_NEED_MORE;
+    }
+    /***/
+  }
+  return BS_BLOCK_DONE;
+}
+
+/* ===========================================================================
+ * Same as above, but achieves better compression. We use a lazy
+ * evaluation for matches: a match is finally adopted only if there is
+ * no better match at the next window position.
+ */
+function deflate_slow(s, flush) {
+  var hash_head;          /* head of hash chain */
+  var bflush;              /* set if current block must be flushed */
+
+  var max_insert;
+
+  /* Process the input block. */
+  for (;;) {
+    /* Make sure that we always have enough lookahead, except
+     * at the end of the input file. We need MAX_MATCH bytes
+     * for the next match, plus MIN_MATCH bytes to insert the
+     * string following the next match.
+     */
+    if (s.lookahead < MIN_LOOKAHEAD) {
+      fill_window(s);
+      if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {
+        return BS_NEED_MORE;
+      }
+      if (s.lookahead === 0) { break; } /* flush the current block */
+    }
+
+    /* Insert the string window[strstart .. strstart+2] in the
+     * dictionary, and set hash_head to the head of the hash chain:
+     */
+    hash_head = 0/*NIL*/;
+    if (s.lookahead >= MIN_MATCH) {
+      /*** INSERT_STRING(s, s.strstart, hash_head); ***/
+      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
+      hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
+      s.head[s.ins_h] = s.strstart;
+      /***/
+    }
+
+    /* Find the longest match, discarding those <= prev_length.
+     */
+    s.prev_length = s.match_length;
+    s.prev_match = s.match_start;
+    s.match_length = MIN_MATCH - 1;
+
+    if (hash_head !== 0/*NIL*/ && s.prev_length < s.max_lazy_match &&
+        s.strstart - hash_head <= (s.w_size - MIN_LOOKAHEAD)/*MAX_DIST(s)*/) {
+      /* To simplify the code, we prevent matches with the string
+       * of window index 0 (in particular we have to avoid a match
+       * of the string with itself at the start of the input file).
+       */
+      s.match_length = longest_match(s, hash_head);
+      /* longest_match() sets match_start */
+
+      if (s.match_length <= 5 &&
+         (s.strategy === Z_FILTERED || (s.match_length === MIN_MATCH && s.strstart - s.match_start > 4096/*TOO_FAR*/))) {
+
+        /* If prev_match is also MIN_MATCH, match_start is garbage
+         * but we will ignore the current match anyway.
+         */
+        s.match_length = MIN_MATCH - 1;
+      }
+    }
+    /* If there was a match at the previous step and the current
+     * match is not better, output the previous match:
+     */
+    if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) {
+      max_insert = s.strstart + s.lookahead - MIN_MATCH;
+      /* Do not insert strings in hash table beyond this. */
+
+      //check_match(s, s.strstart-1, s.prev_match, s.prev_length);
+
+      /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match,
+                     s.prev_length - MIN_MATCH, bflush);***/
+      bflush = trees._tr_tally(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH);
+      /* Insert in hash table all strings up to the end of the match.
+       * strstart-1 and strstart are already inserted. If there is not
+       * enough lookahead, the last two strings are not inserted in
+       * the hash table.
+       */
+      s.lookahead -= s.prev_length - 1;
+      s.prev_length -= 2;
+      do {
+        if (++s.strstart <= max_insert) {
+          /*** INSERT_STRING(s, s.strstart, hash_head); ***/
+          s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
+          hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
+          s.head[s.ins_h] = s.strstart;
+          /***/
+        }
+      } while (--s.prev_length !== 0);
+      s.match_available = 0;
+      s.match_length = MIN_MATCH - 1;
+      s.strstart++;
+
+      if (bflush) {
+        /*** FLUSH_BLOCK(s, 0); ***/
+        flush_block_only(s, false);
+        if (s.strm.avail_out === 0) {
+          return BS_NEED_MORE;
+        }
+        /***/
+      }
+
+    } else if (s.match_available) {
+      /* If there was no match at the previous position, output a
+       * single literal. If there was a match but the current match
+       * is longer, truncate the previous match to a single literal.
+       */
+      //Tracevv((stderr,"%c", s->window[s->strstart-1]));
+      /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/
+      bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]);
+
+      if (bflush) {
+        /*** FLUSH_BLOCK_ONLY(s, 0) ***/
+        flush_block_only(s, false);
+        /***/
+      }
+      s.strstart++;
+      s.lookahead--;
+      if (s.strm.avail_out === 0) {
+        return BS_NEED_MORE;
+      }
+    } else {
+      /* There is no previous match to compare with, wait for
+       * the next step to decide.
+       */
+      s.match_available = 1;
+      s.strstart++;
+      s.lookahead--;
+    }
+  }
+  //Assert (flush != Z_NO_FLUSH, "no flush?");
+  if (s.match_available) {
+    //Tracevv((stderr,"%c", s->window[s->strstart-1]));
+    /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/
+    bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]);
+
+    s.match_available = 0;
+  }
+  s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1;
+  if (flush === Z_FINISH) {
+    /*** FLUSH_BLOCK(s, 1); ***/
+    flush_block_only(s, true);
+    if (s.strm.avail_out === 0) {
+      return BS_FINISH_STARTED;
+    }
+    /***/
+    return BS_FINISH_DONE;
+  }
+  if (s.last_lit) {
+    /*** FLUSH_BLOCK(s, 0); ***/
+    flush_block_only(s, false);
+    if (s.strm.avail_out === 0) {
+      return BS_NEED_MORE;
+    }
+    /***/
+  }
+
+  return BS_BLOCK_DONE;
+}
+
+
+/* ===========================================================================
+ * For Z_RLE, simply look for runs of bytes, generate matches only of distance
+ * one.  Do not maintain a hash table.  (It will be regenerated if this run of
+ * deflate switches away from Z_RLE.)
+ */
+function deflate_rle(s, flush) {
+  var bflush;            /* set if current block must be flushed */
+  var prev;              /* byte at distance one to match */
+  var scan, strend;      /* scan goes up to strend for length of run */
+
+  var _win = s.window;
+
+  for (;;) {
+    /* Make sure that we always have enough lookahead, except
+     * at the end of the input file. We need MAX_MATCH bytes
+     * for the longest run, plus one for the unrolled loop.
+     */
+    if (s.lookahead <= MAX_MATCH) {
+      fill_window(s);
+      if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH) {
+        return BS_NEED_MORE;
+      }
+      if (s.lookahead === 0) { break; } /* flush the current block */
+    }
+
+    /* See how many times the previous byte repeats */
+    s.match_length = 0;
+    if (s.lookahead >= MIN_MATCH && s.strstart > 0) {
+      scan = s.strstart - 1;
+      prev = _win[scan];
+      if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) {
+        strend = s.strstart + MAX_MATCH;
+        do {
+          /*jshint noempty:false*/
+        } while (prev === _win[++scan] && prev === _win[++scan] &&
+                 prev === _win[++scan] && prev === _win[++scan] &&
+                 prev === _win[++scan] && prev === _win[++scan] &&
+                 prev === _win[++scan] && prev === _win[++scan] &&
+                 scan < strend);
+        s.match_length = MAX_MATCH - (strend - scan);
+        if (s.match_length > s.lookahead) {
+          s.match_length = s.lookahead;
+        }
+      }
+      //Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan");
+    }
+
+    /* Emit match if have run of MIN_MATCH or longer, else emit literal */
+    if (s.match_length >= MIN_MATCH) {
+      //check_match(s, s.strstart, s.strstart - 1, s.match_length);
+
+      /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/
+      bflush = trees._tr_tally(s, 1, s.match_length - MIN_MATCH);
+
+      s.lookahead -= s.match_length;
+      s.strstart += s.match_length;
+      s.match_length = 0;
+    } else {
+      /* No match, output a literal byte */
+      //Tracevv((stderr,"%c", s->window[s->strstart]));
+      /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
+      bflush = trees._tr_tally(s, 0, s.window[s.strstart]);
+
+      s.lookahead--;
+      s.strstart++;
+    }
+    if (bflush) {
+      /*** FLUSH_BLOCK(s, 0); ***/
+      flush_block_only(s, false);
+      if (s.strm.avail_out === 0) {
+        return BS_NEED_MORE;
+      }
+      /***/
+    }
+  }
+  s.insert = 0;
+  if (flush === Z_FINISH) {
+    /*** FLUSH_BLOCK(s, 1); ***/
+    flush_block_only(s, true);
+    if (s.strm.avail_out === 0) {
+      return BS_FINISH_STARTED;
+    }
+    /***/
+    return BS_FINISH_DONE;
+  }
+  if (s.last_lit) {
+    /*** FLUSH_BLOCK(s, 0); ***/
+    flush_block_only(s, false);
+    if (s.strm.avail_out === 0) {
+      return BS_NEED_MORE;
+    }
+    /***/
+  }
+  return BS_BLOCK_DONE;
+}
+
+/* ===========================================================================
+ * For Z_HUFFMAN_ONLY, do not look for matches.  Do not maintain a hash table.
+ * (It will be regenerated if this run of deflate switches away from Huffman.)
+ */
+function deflate_huff(s, flush) {
+  var bflush;             /* set if current block must be flushed */
+
+  for (;;) {
+    /* Make sure that we have a literal to write. */
+    if (s.lookahead === 0) {
+      fill_window(s);
+      if (s.lookahead === 0) {
+        if (flush === Z_NO_FLUSH) {
+          return BS_NEED_MORE;
+        }
+        break;      /* flush the current block */
+      }
+    }
+
+    /* Output a literal byte */
+    s.match_length = 0;
+    //Tracevv((stderr,"%c", s->window[s->strstart]));
+    /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
+    bflush = trees._tr_tally(s, 0, s.window[s.strstart]);
+    s.lookahead--;
+    s.strstart++;
+    if (bflush) {
+      /*** FLUSH_BLOCK(s, 0); ***/
+      flush_block_only(s, false);
+      if (s.strm.avail_out === 0) {
+        return BS_NEED_MORE;
+      }
+      /***/
+    }
+  }
+  s.insert = 0;
+  if (flush === Z_FINISH) {
+    /*** FLUSH_BLOCK(s, 1); ***/
+    flush_block_only(s, true);
+    if (s.strm.avail_out === 0) {
+      return BS_FINISH_STARTED;
+    }
+    /***/
+    return BS_FINISH_DONE;
+  }
+  if (s.last_lit) {
+    /*** FLUSH_BLOCK(s, 0); ***/
+    flush_block_only(s, false);
+    if (s.strm.avail_out === 0) {
+      return BS_NEED_MORE;
+    }
+    /***/
+  }
+  return BS_BLOCK_DONE;
+}
+
+/* Values for max_lazy_match, good_match and max_chain_length, depending on
+ * the desired pack level (0..9). The values given below have been tuned to
+ * exclude worst case performance for pathological files. Better values may be
+ * found for specific files.
+ */
+function Config(good_length, max_lazy, nice_length, max_chain, func) {
+  this.good_length = good_length;
+  this.max_lazy = max_lazy;
+  this.nice_length = nice_length;
+  this.max_chain = max_chain;
+  this.func = func;
+}
+
+var configuration_table;
+
+configuration_table = [
+  /*      good lazy nice chain */
+  new Config(0, 0, 0, 0, deflate_stored),          /* 0 store only */
+  new Config(4, 4, 8, 4, deflate_fast),            /* 1 max speed, no lazy matches */
+  new Config(4, 5, 16, 8, deflate_fast),           /* 2 */
+  new Config(4, 6, 32, 32, deflate_fast),          /* 3 */
+
+  new Config(4, 4, 16, 16, deflate_slow),          /* 4 lazy matches */
+  new Config(8, 16, 32, 32, deflate_slow),         /* 5 */
+  new Config(8, 16, 128, 128, deflate_slow),       /* 6 */
+  new Config(8, 32, 128, 256, deflate_slow),       /* 7 */
+  new Config(32, 128, 258, 1024, deflate_slow),    /* 8 */
+  new Config(32, 258, 258, 4096, deflate_slow)     /* 9 max compression */
+];
+
+
+/* ===========================================================================
+ * Initialize the "longest match" routines for a new zlib stream
+ */
+function lm_init(s) {
+  s.window_size = 2 * s.w_size;
+
+  /*** CLEAR_HASH(s); ***/
+  zero(s.head); // Fill with NIL (= 0);
+
+  /* Set the default configuration parameters:
+   */
+  s.max_lazy_match = configuration_table[s.level].max_lazy;
+  s.good_match = configuration_table[s.level].good_length;
+  s.nice_match = configuration_table[s.level].nice_length;
+  s.max_chain_length = configuration_table[s.level].max_chain;
+
+  s.strstart = 0;
+  s.block_start = 0;
+  s.lookahead = 0;
+  s.insert = 0;
+  s.match_length = s.prev_length = MIN_MATCH - 1;
+  s.match_available = 0;
+  s.ins_h = 0;
+}
+
+
+function DeflateState() {
+  this.strm = null;            /* pointer back to this zlib stream */
+  this.status = 0;            /* as the name implies */
+  this.pending_buf = null;      /* output still pending */
+  this.pending_buf_size = 0;  /* size of pending_buf */
+  this.pending_out = 0;       /* next pending byte to output to the stream */
+  this.pending = 0;           /* nb of bytes in the pending buffer */
+  this.wrap = 0;              /* bit 0 true for zlib, bit 1 true for gzip */
+  this.gzhead = null;         /* gzip header information to write */
+  this.gzindex = 0;           /* where in extra, name, or comment */
+  this.method = Z_DEFLATED; /* can only be DEFLATED */
+  this.last_flush = -1;   /* value of flush param for previous deflate call */
+
+  this.w_size = 0;  /* LZ77 window size (32K by default) */
+  this.w_bits = 0;  /* log2(w_size)  (8..16) */
+  this.w_mask = 0;  /* w_size - 1 */
+
+  this.window = null;
+  /* Sliding window. Input bytes are read into the second half of the window,
+   * and move to the first half later to keep a dictionary of at least wSize
+   * bytes. With this organization, matches are limited to a distance of
+   * wSize-MAX_MATCH bytes, but this ensures that IO is always
+   * performed with a length multiple of the block size.
+   */
+
+  this.window_size = 0;
+  /* Actual size of window: 2*wSize, except when the user input buffer
+   * is directly used as sliding window.
+   */
+
+  this.prev = null;
+  /* Link to older string with same hash index. To limit the size of this
+   * array to 64K, this link is maintained only for the last 32K strings.
+   * An index in this array is thus a window index modulo 32K.
+   */
+
+  this.head = null;   /* Heads of the hash chains or NIL. */
+
+  this.ins_h = 0;       /* hash index of string to be inserted */
+  this.hash_size = 0;   /* number of elements in hash table */
+  this.hash_bits = 0;   /* log2(hash_size) */
+  this.hash_mask = 0;   /* hash_size-1 */
+
+  this.hash_shift = 0;
+  /* Number of bits by which ins_h must be shifted at each input
+   * step. It must be such that after MIN_MATCH steps, the oldest
+   * byte no longer takes part in the hash key, that is:
+   *   hash_shift * MIN_MATCH >= hash_bits
+   */
+
+  this.block_start = 0;
+  /* Window position at the beginning of the current output block. Gets
+   * negative when the window is moved backwards.
+   */
+
+  this.match_length = 0;      /* length of best match */
+  this.prev_match = 0;        /* previous match */
+  this.match_available = 0;   /* set if previous match exists */
+  this.strstart = 0;          /* start of string to insert */
+  this.match_start = 0;       /* start of matching string */
+  this.lookahead = 0;         /* number of valid bytes ahead in window */
+
+  this.prev_length = 0;
+  /* Length of the best match at previous step. Matches not greater than this
+   * are discarded. This is used in the lazy match evaluation.
+   */
+
+  this.max_chain_length = 0;
+  /* To speed up deflation, hash chains are never searched beyond this
+   * length.  A higher limit improves compression ratio but degrades the
+   * speed.
+   */
+
+  this.max_lazy_match = 0;
+  /* Attempt to find a better match only when the current match is strictly
+   * smaller than this value. This mechanism is used only for compression
+   * levels >= 4.
+   */
+  // That's alias to max_lazy_match, don't use directly
+  //this.max_insert_length = 0;
+  /* Insert new strings in the hash table only if the match length is not
+   * greater than this length. This saves time but degrades compression.
+   * max_insert_length is used only for compression levels <= 3.
+   */
+
+  this.level = 0;     /* compression level (1..9) */
+  this.strategy = 0;  /* favor or force Huffman coding*/
+
+  this.good_match = 0;
+  /* Use a faster search when the previous match is longer than this */
+
+  this.nice_match = 0; /* Stop searching when current match exceeds this */
+
+              /* used by trees.c: */
+
+  /* Didn't use ct_data typedef below to suppress compiler warning */
+
+  // struct ct_data_s dyn_ltree[HEAP_SIZE];   /* literal and length tree */
+  // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */
+  // struct ct_data_s bl_tree[2*BL_CODES+1];  /* Huffman tree for bit lengths */
+
+  // Use flat array of DOUBLE size, with interleaved fata,
+  // because JS does not support effective
+  this.dyn_ltree  = new utils.Buf16(HEAP_SIZE * 2);
+  this.dyn_dtree  = new utils.Buf16((2 * D_CODES + 1) * 2);
+  this.bl_tree    = new utils.Buf16((2 * BL_CODES + 1) * 2);
+  zero(this.dyn_ltree);
+  zero(this.dyn_dtree);
+  zero(this.bl_tree);
+
+  this.l_desc   = null;         /* desc. for literal tree */
+  this.d_desc   = null;         /* desc. for distance tree */
+  this.bl_desc  = null;         /* desc. for bit length tree */
+
+  //ush bl_count[MAX_BITS+1];
+  this.bl_count = new utils.Buf16(MAX_BITS + 1);
+  /* number of codes at each bit length for an optimal tree */
+
+  //int heap[2*L_CODES+1];      /* heap used to build the Huffman trees */
+  this.heap = new utils.Buf16(2 * L_CODES + 1);  /* heap used to build the Huffman trees */
+  zero(this.heap);
+
+  this.heap_len = 0;               /* number of elements in the heap */
+  this.heap_max = 0;               /* element of largest frequency */
+  /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used.
+   * The same heap array is used to build all trees.
+   */
+
+  this.depth = new utils.Buf16(2 * L_CODES + 1); //uch depth[2*L_CODES+1];
+  zero(this.depth);
+  /* Depth of each subtree used as tie breaker for trees of equal frequency
+   */
+
+  this.l_buf = 0;          /* buffer index for literals or lengths */
+
+  this.lit_bufsize = 0;
+  /* Size of match buffer for literals/lengths.  There are 4 reasons for
+   * limiting lit_bufsize to 64K:
+   *   - frequencies can be kept in 16 bit counters
+   *   - if compression is not successful for the first block, all input
+   *     data is still in the window so we can still emit a stored block even
+   *     when input comes from standard input.  (This can also be done for
+   *     all blocks if lit_bufsize is not greater than 32K.)
+   *   - if compression is not successful for a file smaller than 64K, we can
+   *     even emit a stored file instead of a stored block (saving 5 bytes).
+   *     This is applicable only for zip (not gzip or zlib).
+   *   - creating new Huffman trees less frequently may not provide fast
+   *     adaptation to changes in the input data statistics. (Take for
+   *     example a binary file with poorly compressible code followed by
+   *     a highly compressible string table.) Smaller buffer sizes give
+   *     fast adaptation but have of course the overhead of transmitting
+   *     trees more frequently.
+   *   - I can't count above 4
+   */
+
+  this.last_lit = 0;      /* running index in l_buf */
+
+  this.d_buf = 0;
+  /* Buffer index for distances. To simplify the code, d_buf and l_buf have
+   * the same number of elements. To use different lengths, an extra flag
+   * array would be necessary.
+   */
+
+  this.opt_len = 0;       /* bit length of current block with optimal trees */
+  this.static_len = 0;    /* bit length of current block with static trees */
+  this.matches = 0;       /* number of string matches in current block */
+  this.insert = 0;        /* bytes at end of window left to insert */
+
+
+  this.bi_buf = 0;
+  /* Output buffer. bits are inserted starting at the bottom (least
+   * significant bits).
+   */
+  this.bi_valid = 0;
+  /* Number of valid bits in bi_buf.  All bits above the last valid bit
+   * are always zero.
+   */
+
+  // Used for window memory init. We safely ignore it for JS. That makes
+  // sense only for pointers and memory check tools.
+  //this.high_water = 0;
+  /* High water mark offset in window for initialized bytes -- bytes above
+   * this are set to zero in order to avoid memory check warnings when
+   * longest match routines access bytes past the input.  This is then
+   * updated to the new high water mark.
+   */
+}
+
+
+function deflateResetKeep(strm) {
+  var s;
+
+  if (!strm || !strm.state) {
+    return err(strm, Z_STREAM_ERROR);
+  }
+
+  strm.total_in = strm.total_out = 0;
+  strm.data_type = Z_UNKNOWN;
+
+  s = strm.state;
+  s.pending = 0;
+  s.pending_out = 0;
+
+  if (s.wrap < 0) {
+    s.wrap = -s.wrap;
+    /* was made negative by deflate(..., Z_FINISH); */
+  }
+  s.status = (s.wrap ? INIT_STATE : BUSY_STATE);
+  strm.adler = (s.wrap === 2) ?
+    0  // crc32(0, Z_NULL, 0)
+  :
+    1; // adler32(0, Z_NULL, 0)
+  s.last_flush = Z_NO_FLUSH;
+  trees._tr_init(s);
+  return Z_OK;
+}
+
+
+function deflateReset(strm) {
+  var ret = deflateResetKeep(strm);
+  if (ret === Z_OK) {
+    lm_init(strm.state);
+  }
+  return ret;
+}
+
+
+function deflateSetHeader(strm, head) {
+  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+  if (strm.state.wrap !== 2) { return Z_STREAM_ERROR; }
+  strm.state.gzhead = head;
+  return Z_OK;
+}
+
+
+function deflateInit2(strm, level, method, windowBits, memLevel, strategy) {
+  if (!strm) { // === Z_NULL
+    return Z_STREAM_ERROR;
+  }
+  var wrap = 1;
+
+  if (level === Z_DEFAULT_COMPRESSION) {
+    level = 6;
+  }
+
+  if (windowBits < 0) { /* suppress zlib wrapper */
+    wrap = 0;
+    windowBits = -windowBits;
+  }
+
+  else if (windowBits > 15) {
+    wrap = 2;           /* write gzip wrapper instead */
+    windowBits -= 16;
+  }
+
+
+  if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED ||
+    windowBits < 8 || windowBits > 15 || level < 0 || level > 9 ||
+    strategy < 0 || strategy > Z_FIXED) {
+    return err(strm, Z_STREAM_ERROR);
+  }
+
+
+  if (windowBits === 8) {
+    windowBits = 9;
+  }
+  /* until 256-byte window bug fixed */
+
+  var s = new DeflateState();
+
+  strm.state = s;
+  s.strm = strm;
+
+  s.wrap = wrap;
+  s.gzhead = null;
+  s.w_bits = windowBits;
+  s.w_size = 1 << s.w_bits;
+  s.w_mask = s.w_size - 1;
+
+  s.hash_bits = memLevel + 7;
+  s.hash_size = 1 << s.hash_bits;
+  s.hash_mask = s.hash_size - 1;
+  s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH);
+
+  s.window = new utils.Buf8(s.w_size * 2);
+  s.head = new utils.Buf16(s.hash_size);
+  s.prev = new utils.Buf16(s.w_size);
+
+  // Don't need mem init magic for JS.
+  //s.high_water = 0;  /* nothing written to s->window yet */
+
+  s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */
+
+  s.pending_buf_size = s.lit_bufsize * 4;
+
+  //overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2);
+  //s->pending_buf = (uchf *) overlay;
+  s.pending_buf = new utils.Buf8(s.pending_buf_size);
+
+  // It is offset from `s.pending_buf` (size is `s.lit_bufsize * 2`)
+  //s->d_buf = overlay + s->lit_bufsize/sizeof(ush);
+  s.d_buf = 1 * s.lit_bufsize;
+
+  //s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize;
+  s.l_buf = (1 + 2) * s.lit_bufsize;
+
+  s.level = level;
+  s.strategy = strategy;
+  s.method = method;
+
+  return deflateReset(strm);
+}
+
+function deflateInit(strm, level) {
+  return deflateInit2(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
+}
+
+
+function deflate(strm, flush) {
+  var old_flush, s;
+  var beg, val; // for gzip header write only
+
+  if (!strm || !strm.state ||
+    flush > Z_BLOCK || flush < 0) {
+    return strm ? err(strm, Z_STREAM_ERROR) : Z_STREAM_ERROR;
+  }
+
+  s = strm.state;
+
+  if (!strm.output ||
+      (!strm.input && strm.avail_in !== 0) ||
+      (s.status === FINISH_STATE && flush !== Z_FINISH)) {
+    return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR : Z_STREAM_ERROR);
+  }
+
+  s.strm = strm; /* just in case */
+  old_flush = s.last_flush;
+  s.last_flush = flush;
+
+  /* Write the header */
+  if (s.status === INIT_STATE) {
+
+    if (s.wrap === 2) { // GZIP header
+      strm.adler = 0;  //crc32(0L, Z_NULL, 0);
+      put_byte(s, 31);
+      put_byte(s, 139);
+      put_byte(s, 8);
+      if (!s.gzhead) { // s->gzhead == Z_NULL
+        put_byte(s, 0);
+        put_byte(s, 0);
+        put_byte(s, 0);
+        put_byte(s, 0);
+        put_byte(s, 0);
+        put_byte(s, s.level === 9 ? 2 :
+                    (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ?
+                     4 : 0));
+        put_byte(s, OS_CODE);
+        s.status = BUSY_STATE;
+      }
+      else {
+        put_byte(s, (s.gzhead.text ? 1 : 0) +
+                    (s.gzhead.hcrc ? 2 : 0) +
+                    (!s.gzhead.extra ? 0 : 4) +
+                    (!s.gzhead.name ? 0 : 8) +
+                    (!s.gzhead.comment ? 0 : 16)
+                );
+        put_byte(s, s.gzhead.time & 0xff);
+        put_byte(s, (s.gzhead.time >> 8) & 0xff);
+        put_byte(s, (s.gzhead.time >> 16) & 0xff);
+        put_byte(s, (s.gzhead.time >> 24) & 0xff);
+        put_byte(s, s.level === 9 ? 2 :
+                    (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ?
+                     4 : 0));
+        put_byte(s, s.gzhead.os & 0xff);
+        if (s.gzhead.extra && s.gzhead.extra.length) {
+          put_byte(s, s.gzhead.extra.length & 0xff);
+          put_byte(s, (s.gzhead.extra.length >> 8) & 0xff);
+        }
+        if (s.gzhead.hcrc) {
+          strm.adler = crc32(strm.adler, s.pending_buf, s.pending, 0);
+        }
+        s.gzindex = 0;
+        s.status = EXTRA_STATE;
+      }
+    }
+    else // DEFLATE header
+    {
+      var header = (Z_DEFLATED + ((s.w_bits - 8) << 4)) << 8;
+      var level_flags = -1;
+
+      if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) {
+        level_flags = 0;
+      } else if (s.level < 6) {
+        level_flags = 1;
+      } else if (s.level === 6) {
+        level_flags = 2;
+      } else {
+        level_flags = 3;
+      }
+      header |= (level_flags << 6);
+      if (s.strstart !== 0) { header |= PRESET_DICT; }
+      header += 31 - (header % 31);
+
+      s.status = BUSY_STATE;
+      putShortMSB(s, header);
+
+      /* Save the adler32 of the preset dictionary: */
+      if (s.strstart !== 0) {
+        putShortMSB(s, strm.adler >>> 16);
+        putShortMSB(s, strm.adler & 0xffff);
+      }
+      strm.adler = 1; // adler32(0L, Z_NULL, 0);
+    }
+  }
+
+//#ifdef GZIP
+  if (s.status === EXTRA_STATE) {
+    if (s.gzhead.extra/* != Z_NULL*/) {
+      beg = s.pending;  /* start of bytes to update crc */
+
+      while (s.gzindex < (s.gzhead.extra.length & 0xffff)) {
+        if (s.pending === s.pending_buf_size) {
+          if (s.gzhead.hcrc && s.pending > beg) {
+            strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
+          }
+          flush_pending(strm);
+          beg = s.pending;
+          if (s.pending === s.pending_buf_size) {
+            break;
+          }
+        }
+        put_byte(s, s.gzhead.extra[s.gzindex] & 0xff);
+        s.gzindex++;
+      }
+      if (s.gzhead.hcrc && s.pending > beg) {
+        strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
+      }
+      if (s.gzindex === s.gzhead.extra.length) {
+        s.gzindex = 0;
+        s.status = NAME_STATE;
+      }
+    }
+    else {
+      s.status = NAME_STATE;
+    }
+  }
+  if (s.status === NAME_STATE) {
+    if (s.gzhead.name/* != Z_NULL*/) {
+      beg = s.pending;  /* start of bytes to update crc */
+      //int val;
+
+      do {
+        if (s.pending === s.pending_buf_size) {
+          if (s.gzhead.hcrc && s.pending > beg) {
+            strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
+          }
+          flush_pending(strm);
+          beg = s.pending;
+          if (s.pending === s.pending_buf_size) {
+            val = 1;
+            break;
+          }
+        }
+        // JS specific: little magic to add zero terminator to end of string
+        if (s.gzindex < s.gzhead.name.length) {
+          val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff;
+        } else {
+          val = 0;
+        }
+        put_byte(s, val);
+      } while (val !== 0);
+
+      if (s.gzhead.hcrc && s.pending > beg) {
+        strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
+      }
+      if (val === 0) {
+        s.gzindex = 0;
+        s.status = COMMENT_STATE;
+      }
+    }
+    else {
+      s.status = COMMENT_STATE;
+    }
+  }
+  if (s.status === COMMENT_STATE) {
+    if (s.gzhead.comment/* != Z_NULL*/) {
+      beg = s.pending;  /* start of bytes to update crc */
+      //int val;
+
+      do {
+        if (s.pending === s.pending_buf_size) {
+          if (s.gzhead.hcrc && s.pending > beg) {
+            strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
+          }
+          flush_pending(strm);
+          beg = s.pending;
+          if (s.pending === s.pending_buf_size) {
+            val = 1;
+            break;
+          }
+        }
+        // JS specific: little magic to add zero terminator to end of string
+        if (s.gzindex < s.gzhead.comment.length) {
+          val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff;
+        } else {
+          val = 0;
+        }
+        put_byte(s, val);
+      } while (val !== 0);
+
+      if (s.gzhead.hcrc && s.pending > beg) {
+        strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
+      }
+      if (val === 0) {
+        s.status = HCRC_STATE;
+      }
+    }
+    else {
+      s.status = HCRC_STATE;
+    }
+  }
+  if (s.status === HCRC_STATE) {
+    if (s.gzhead.hcrc) {
+      if (s.pending + 2 > s.pending_buf_size) {
+        flush_pending(strm);
+      }
+      if (s.pending + 2 <= s.pending_buf_size) {
+        put_byte(s, strm.adler & 0xff);
+        put_byte(s, (strm.adler >> 8) & 0xff);
+        strm.adler = 0; //crc32(0L, Z_NULL, 0);
+        s.status = BUSY_STATE;
+      }
+    }
+    else {
+      s.status = BUSY_STATE;
+    }
+  }
+//#endif
+
+  /* Flush as much pending output as possible */
+  if (s.pending !== 0) {
+    flush_pending(strm);
+    if (strm.avail_out === 0) {
+      /* Since avail_out is 0, deflate will be called again with
+       * more output space, but possibly with both pending and
+       * avail_in equal to zero. There won't be anything to do,
+       * but this is not an error situation so make sure we
+       * return OK instead of BUF_ERROR at next call of deflate:
+       */
+      s.last_flush = -1;
+      return Z_OK;
+    }
+
+    /* Make sure there is something to do and avoid duplicate consecutive
+     * flushes. For repeated and useless calls with Z_FINISH, we keep
+     * returning Z_STREAM_END instead of Z_BUF_ERROR.
+     */
+  } else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) &&
+    flush !== Z_FINISH) {
+    return err(strm, Z_BUF_ERROR);
+  }
+
+  /* User must not provide more input after the first FINISH: */
+  if (s.status === FINISH_STATE && strm.avail_in !== 0) {
+    return err(strm, Z_BUF_ERROR);
+  }
+
+  /* Start a new block or continue the current one.
+   */
+  if (strm.avail_in !== 0 || s.lookahead !== 0 ||
+    (flush !== Z_NO_FLUSH && s.status !== FINISH_STATE)) {
+    var bstate = (s.strategy === Z_HUFFMAN_ONLY) ? deflate_huff(s, flush) :
+      (s.strategy === Z_RLE ? deflate_rle(s, flush) :
+        configuration_table[s.level].func(s, flush));
+
+    if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) {
+      s.status = FINISH_STATE;
+    }
+    if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) {
+      if (strm.avail_out === 0) {
+        s.last_flush = -1;
+        /* avoid BUF_ERROR next call, see above */
+      }
+      return Z_OK;
+      /* If flush != Z_NO_FLUSH && avail_out == 0, the next call
+       * of deflate should use the same flush parameter to make sure
+       * that the flush is complete. So we don't have to output an
+       * empty block here, this will be done at next call. This also
+       * ensures that for a very small output buffer, we emit at most
+       * one empty block.
+       */
+    }
+    if (bstate === BS_BLOCK_DONE) {
+      if (flush === Z_PARTIAL_FLUSH) {
+        trees._tr_align(s);
+      }
+      else if (flush !== Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */
+
+        trees._tr_stored_block(s, 0, 0, false);
+        /* For a full flush, this empty block will be recognized
+         * as a special marker by inflate_sync().
+         */
+        if (flush === Z_FULL_FLUSH) {
+          /*** CLEAR_HASH(s); ***/             /* forget history */
+          zero(s.head); // Fill with NIL (= 0);
+
+          if (s.lookahead === 0) {
+            s.strstart = 0;
+            s.block_start = 0;
+            s.insert = 0;
+          }
+        }
+      }
+      flush_pending(strm);
+      if (strm.avail_out === 0) {
+        s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */
+        return Z_OK;
+      }
+    }
+  }
+  //Assert(strm->avail_out > 0, "bug2");
+  //if (strm.avail_out <= 0) { throw new Error("bug2");}
+
+  if (flush !== Z_FINISH) { return Z_OK; }
+  if (s.wrap <= 0) { return Z_STREAM_END; }
+
+  /* Write the trailer */
+  if (s.wrap === 2) {
+    put_byte(s, strm.adler & 0xff);
+    put_byte(s, (strm.adler >> 8) & 0xff);
+    put_byte(s, (strm.adler >> 16) & 0xff);
+    put_byte(s, (strm.adler >> 24) & 0xff);
+    put_byte(s, strm.total_in & 0xff);
+    put_byte(s, (strm.total_in >> 8) & 0xff);
+    put_byte(s, (strm.total_in >> 16) & 0xff);
+    put_byte(s, (strm.total_in >> 24) & 0xff);
+  }
+  else
+  {
+    putShortMSB(s, strm.adler >>> 16);
+    putShortMSB(s, strm.adler & 0xffff);
+  }
+
+  flush_pending(strm);
+  /* If avail_out is zero, the application will call deflate again
+   * to flush the rest.
+   */
+  if (s.wrap > 0) { s.wrap = -s.wrap; }
+  /* write the trailer only once! */
+  return s.pending !== 0 ? Z_OK : Z_STREAM_END;
+}
+
+function deflateEnd(strm) {
+  var status;
+
+  if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) {
+    return Z_STREAM_ERROR;
+  }
+
+  status = strm.state.status;
+  if (status !== INIT_STATE &&
+    status !== EXTRA_STATE &&
+    status !== NAME_STATE &&
+    status !== COMMENT_STATE &&
+    status !== HCRC_STATE &&
+    status !== BUSY_STATE &&
+    status !== FINISH_STATE
+  ) {
+    return err(strm, Z_STREAM_ERROR);
+  }
+
+  strm.state = null;
+
+  return status === BUSY_STATE ? err(strm, Z_DATA_ERROR) : Z_OK;
+}
+
+
+/* =========================================================================
+ * Initializes the compression dictionary from the given byte
+ * sequence without producing any compressed output.
+ */
+function deflateSetDictionary(strm, dictionary) {
+  var dictLength = dictionary.length;
+
+  var s;
+  var str, n;
+  var wrap;
+  var avail;
+  var next;
+  var input;
+  var tmpDict;
+
+  if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) {
+    return Z_STREAM_ERROR;
+  }
+
+  s = strm.state;
+  wrap = s.wrap;
+
+  if (wrap === 2 || (wrap === 1 && s.status !== INIT_STATE) || s.lookahead) {
+    return Z_STREAM_ERROR;
+  }
+
+  /* when using zlib wrappers, compute Adler-32 for provided dictionary */
+  if (wrap === 1) {
+    /* adler32(strm->adler, dictionary, dictLength); */
+    strm.adler = adler32(strm.adler, dictionary, dictLength, 0);
+  }
+
+  s.wrap = 0;   /* avoid computing Adler-32 in read_buf */
+
+  /* if dictionary would fill window, just replace the history */
+  if (dictLength >= s.w_size) {
+    if (wrap === 0) {            /* already empty otherwise */
+      /*** CLEAR_HASH(s); ***/
+      zero(s.head); // Fill with NIL (= 0);
+      s.strstart = 0;
+      s.block_start = 0;
+      s.insert = 0;
+    }
+    /* use the tail */
+    // dictionary = dictionary.slice(dictLength - s.w_size);
+    tmpDict = new utils.Buf8(s.w_size);
+    utils.arraySet(tmpDict, dictionary, dictLength - s.w_size, s.w_size, 0);
+    dictionary = tmpDict;
+    dictLength = s.w_size;
+  }
+  /* insert dictionary into window and hash */
+  avail = strm.avail_in;
+  next = strm.next_in;
+  input = strm.input;
+  strm.avail_in = dictLength;
+  strm.next_in = 0;
+  strm.input = dictionary;
+  fill_window(s);
+  while (s.lookahead >= MIN_MATCH) {
+    str = s.strstart;
+    n = s.lookahead - (MIN_MATCH - 1);
+    do {
+      /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
+      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask;
+
+      s.prev[str & s.w_mask] = s.head[s.ins_h];
+
+      s.head[s.ins_h] = str;
+      str++;
+    } while (--n);
+    s.strstart = str;
+    s.lookahead = MIN_MATCH - 1;
+    fill_window(s);
+  }
+  s.strstart += s.lookahead;
+  s.block_start = s.strstart;
+  s.insert = s.lookahead;
+  s.lookahead = 0;
+  s.match_length = s.prev_length = MIN_MATCH - 1;
+  s.match_available = 0;
+  strm.next_in = next;
+  strm.input = input;
+  strm.avail_in = avail;
+  s.wrap = wrap;
+  return Z_OK;
+}
+
+
+exports.deflateInit = deflateInit;
+exports.deflateInit2 = deflateInit2;
+exports.deflateReset = deflateReset;
+exports.deflateResetKeep = deflateResetKeep;
+exports.deflateSetHeader = deflateSetHeader;
+exports.deflate = deflate;
+exports.deflateEnd = deflateEnd;
+exports.deflateSetDictionary = deflateSetDictionary;
+exports.deflateInfo = 'pako deflate (from Nodeca project)';
+
+/* Not implemented
+exports.deflateBound = deflateBound;
+exports.deflateCopy = deflateCopy;
+exports.deflateParams = deflateParams;
+exports.deflatePending = deflatePending;
+exports.deflatePrime = deflatePrime;
+exports.deflateTune = deflateTune;
+*/
+
+},{"../utils/common":212,"./adler32":213,"./crc32":215,"./messages":217,"./trees":218}],217:[function(require,module,exports){
+'use strict';
+
+module.exports = {
+  2:      'need dictionary',     /* Z_NEED_DICT       2  */
+  1:      'stream end',          /* Z_STREAM_END      1  */
+  0:      '',                    /* Z_OK              0  */
+  '-1':   'file error',          /* Z_ERRNO         (-1) */
+  '-2':   'stream error',        /* Z_STREAM_ERROR  (-2) */
+  '-3':   'data error',          /* Z_DATA_ERROR    (-3) */
+  '-4':   'insufficient memory', /* Z_MEM_ERROR     (-4) */
+  '-5':   'buffer error',        /* Z_BUF_ERROR     (-5) */
+  '-6':   'incompatible version' /* Z_VERSION_ERROR (-6) */
+};
+
+},{}],218:[function(require,module,exports){
+'use strict';
+
+
+var utils = require('../utils/common');
+
+/* Public constants ==========================================================*/
+/* ===========================================================================*/
+
+
+//var Z_FILTERED          = 1;
+//var Z_HUFFMAN_ONLY      = 2;
+//var Z_RLE               = 3;
+var Z_FIXED               = 4;
+//var Z_DEFAULT_STRATEGY  = 0;
+
+/* Possible values of the data_type field (though see inflate()) */
+var Z_BINARY              = 0;
+var Z_TEXT                = 1;
+//var Z_ASCII             = 1; // = Z_TEXT
+var Z_UNKNOWN             = 2;
+
+/*============================================================================*/
+
+
+function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } }
+
+// From zutil.h
+
+var STORED_BLOCK = 0;
+var STATIC_TREES = 1;
+var DYN_TREES    = 2;
+/* The three kinds of block type */
+
+var MIN_MATCH    = 3;
+var MAX_MATCH    = 258;
+/* The minimum and maximum match lengths */
+
+// From deflate.h
+/* ===========================================================================
+ * Internal compression state.
+ */
+
+var LENGTH_CODES  = 29;
+/* number of length codes, not counting the special END_BLOCK code */
+
+var LITERALS      = 256;
+/* number of literal bytes 0..255 */
+
+var L_CODES       = LITERALS + 1 + LENGTH_CODES;
+/* number of Literal or Length codes, including the END_BLOCK code */
+
+var D_CODES       = 30;
+/* number of distance codes */
+
+var BL_CODES      = 19;
+/* number of codes used to transfer the bit lengths */
+
+var HEAP_SIZE     = 2 * L_CODES + 1;
+/* maximum heap size */
+
+var MAX_BITS      = 15;
+/* All codes must not exceed MAX_BITS bits */
+
+var Buf_size      = 16;
+/* size of bit buffer in bi_buf */
+
+
+/* ===========================================================================
+ * Constants
+ */
+
+var MAX_BL_BITS = 7;
+/* Bit length codes must not exceed MAX_BL_BITS bits */
+
+var END_BLOCK   = 256;
+/* end of block literal code */
+
+var REP_3_6     = 16;
+/* repeat previous bit length 3-6 times (2 bits of repeat count) */
+
+var REPZ_3_10   = 17;
+/* repeat a zero length 3-10 times  (3 bits of repeat count) */
+
+var REPZ_11_138 = 18;
+/* repeat a zero length 11-138 times  (7 bits of repeat count) */
+
+/* eslint-disable comma-spacing,array-bracket-spacing */
+var extra_lbits =   /* extra bits for each length code */
+  [0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0];
+
+var extra_dbits =   /* extra bits for each distance code */
+  [0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];
+
+var extra_blbits =  /* extra bits for each bit length code */
+  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7];
+
+var bl_order =
+  [16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15];
+/* eslint-enable comma-spacing,array-bracket-spacing */
+
+/* The lengths of the bit length codes are sent in order of decreasing
+ * probability, to avoid transmitting the lengths for unused bit length codes.
+ */
+
+/* ===========================================================================
+ * Local data. These are initialized only once.
+ */
+
+// We pre-fill arrays with 0 to avoid uninitialized gaps
+
+var DIST_CODE_LEN = 512; /* see definition of array dist_code below */
+
+// !!!! Use flat array insdead of structure, Freq = i*2, Len = i*2+1
+var static_ltree  = new Array((L_CODES + 2) * 2);
+zero(static_ltree);
+/* The static literal tree. Since the bit lengths are imposed, there is no
+ * need for the L_CODES extra codes used during heap construction. However
+ * The codes 286 and 287 are needed to build a canonical tree (see _tr_init
+ * below).
+ */
+
+var static_dtree  = new Array(D_CODES * 2);
+zero(static_dtree);
+/* The static distance tree. (Actually a trivial tree since all codes use
+ * 5 bits.)
+ */
+
+var _dist_code    = new Array(DIST_CODE_LEN);
+zero(_dist_code);
+/* Distance codes. The first 256 values correspond to the distances
+ * 3 .. 258, the last 256 values correspond to the top 8 bits of
+ * the 15 bit distances.
+ */
+
+var _length_code  = new Array(MAX_MATCH - MIN_MATCH + 1);
+zero(_length_code);
+/* length code for each normalized match length (0 == MIN_MATCH) */
+
+var base_length   = new Array(LENGTH_CODES);
+zero(base_length);
+/* First normalized length for each code (0 = MIN_MATCH) */
+
+var base_dist     = new Array(D_CODES);
+zero(base_dist);
+/* First normalized distance for each code (0 = distance of 1) */
+
+
+function StaticTreeDesc(static_tree, extra_bits, extra_base, elems, max_length) {
+
+  this.static_tree  = static_tree;  /* static tree or NULL */
+  this.extra_bits   = extra_bits;   /* extra bits for each code or NULL */
+  this.extra_base   = extra_base;   /* base index for extra_bits */
+  this.elems        = elems;        /* max number of elements in the tree */
+  this.max_length   = max_length;   /* max bit length for the codes */
+
+  // show if `static_tree` has data or dummy - needed for monomorphic objects
+  this.has_stree    = static_tree && static_tree.length;
+}
+
+
+var static_l_desc;
+var static_d_desc;
+var static_bl_desc;
+
+
+function TreeDesc(dyn_tree, stat_desc) {
+  this.dyn_tree = dyn_tree;     /* the dynamic tree */
+  this.max_code = 0;            /* largest code with non zero frequency */
+  this.stat_desc = stat_desc;   /* the corresponding static tree */
+}
+
+
+
+function d_code(dist) {
+  return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)];
+}
+
+
+/* ===========================================================================
+ * Output a short LSB first on the stream.
+ * IN assertion: there is enough room in pendingBuf.
+ */
+function put_short(s, w) {
+//    put_byte(s, (uch)((w) & 0xff));
+//    put_byte(s, (uch)((ush)(w) >> 8));
+  s.pending_buf[s.pending++] = (w) & 0xff;
+  s.pending_buf[s.pending++] = (w >>> 8) & 0xff;
+}
+
+
+/* ===========================================================================
+ * Send a value on a given number of bits.
+ * IN assertion: length <= 16 and value fits in length bits.
+ */
+function send_bits(s, value, length) {
+  if (s.bi_valid > (Buf_size - length)) {
+    s.bi_buf |= (value << s.bi_valid) & 0xffff;
+    put_short(s, s.bi_buf);
+    s.bi_buf = value >> (Buf_size - s.bi_valid);
+    s.bi_valid += length - Buf_size;
+  } else {
+    s.bi_buf |= (value << s.bi_valid) & 0xffff;
+    s.bi_valid += length;
+  }
+}
+
+
+function send_code(s, c, tree) {
+  send_bits(s, tree[c * 2]/*.Code*/, tree[c * 2 + 1]/*.Len*/);
+}
+
+
+/* ===========================================================================
+ * Reverse the first len bits of a code, using straightforward code (a faster
+ * method would use a table)
+ * IN assertion: 1 <= len <= 15
+ */
+function bi_reverse(code, len) {
+  var res = 0;
+  do {
+    res |= code & 1;
+    code >>>= 1;
+    res <<= 1;
+  } while (--len > 0);
+  return res >>> 1;
+}
+
+
+/* ===========================================================================
+ * Flush the bit buffer, keeping at most 7 bits in it.
+ */
+function bi_flush(s) {
+  if (s.bi_valid === 16) {
+    put_short(s, s.bi_buf);
+    s.bi_buf = 0;
+    s.bi_valid = 0;
+
+  } else if (s.bi_valid >= 8) {
+    s.pending_buf[s.pending++] = s.bi_buf & 0xff;
+    s.bi_buf >>= 8;
+    s.bi_valid -= 8;
+  }
+}
+
+
+/* ===========================================================================
+ * Compute the optimal bit lengths for a tree and update the total bit length
+ * for the current block.
+ * IN assertion: the fields freq and dad are set, heap[heap_max] and
+ *    above are the tree nodes sorted by increasing frequency.
+ * OUT assertions: the field len is set to the optimal bit length, the
+ *     array bl_count contains the frequencies for each bit length.
+ *     The length opt_len is updated; static_len is also updated if stree is
+ *     not null.
+ */
+function gen_bitlen(s, desc)
+//    deflate_state *s;
+//    tree_desc *desc;    /* the tree descriptor */
+{
+  var tree            = desc.dyn_tree;
+  var max_code        = desc.max_code;
+  var stree           = desc.stat_desc.static_tree;
+  var has_stree       = desc.stat_desc.has_stree;
+  var extra           = desc.stat_desc.extra_bits;
+  var base            = desc.stat_desc.extra_base;
+  var max_length      = desc.stat_desc.max_length;
+  var h;              /* heap index */
+  var n, m;           /* iterate over the tree elements */
+  var bits;           /* bit length */
+  var xbits;          /* extra bits */
+  var f;              /* frequency */
+  var overflow = 0;   /* number of elements with bit length too large */
+
+  for (bits = 0; bits <= MAX_BITS; bits++) {
+    s.bl_count[bits] = 0;
+  }
+
+  /* In a first pass, compute the optimal bit lengths (which may
+   * overflow in the case of the bit length tree).
+   */
+  tree[s.heap[s.heap_max] * 2 + 1]/*.Len*/ = 0; /* root of the heap */
+
+  for (h = s.heap_max + 1; h < HEAP_SIZE; h++) {
+    n = s.heap[h];
+    bits = tree[tree[n * 2 + 1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1;
+    if (bits > max_length) {
+      bits = max_length;
+      overflow++;
+    }
+    tree[n * 2 + 1]/*.Len*/ = bits;
+    /* We overwrite tree[n].Dad which is no longer needed */
+
+    if (n > max_code) { continue; } /* not a leaf node */
+
+    s.bl_count[bits]++;
+    xbits = 0;
+    if (n >= base) {
+      xbits = extra[n - base];
+    }
+    f = tree[n * 2]/*.Freq*/;
+    s.opt_len += f * (bits + xbits);
+    if (has_stree) {
+      s.static_len += f * (stree[n * 2 + 1]/*.Len*/ + xbits);
+    }
+  }
+  if (overflow === 0) { return; }
+
+  // Trace((stderr,"\nbit length overflow\n"));
+  /* This happens for example on obj2 and pic of the Calgary corpus */
+
+  /* Find the first bit length which could increase: */
+  do {
+    bits = max_length - 1;
+    while (s.bl_count[bits] === 0) { bits--; }
+    s.bl_count[bits]--;      /* move one leaf down the tree */
+    s.bl_count[bits + 1] += 2; /* move one overflow item as its brother */
+    s.bl_count[max_length]--;
+    /* The brother of the overflow item also moves one step up,
+     * but this does not affect bl_count[max_length]
+     */
+    overflow -= 2;
+  } while (overflow > 0);
+
+  /* Now recompute all bit lengths, scanning in increasing frequency.
+   * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all
+   * lengths instead of fixing only the wrong ones. This idea is taken
+   * from 'ar' written by Haruhiko Okumura.)
+   */
+  for (bits = max_length; bits !== 0; bits--) {
+    n = s.bl_count[bits];
+    while (n !== 0) {
+      m = s.heap[--h];
+      if (m > max_code) { continue; }
+      if (tree[m * 2 + 1]/*.Len*/ !== bits) {
+        // Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits));
+        s.opt_len += (bits - tree[m * 2 + 1]/*.Len*/) * tree[m * 2]/*.Freq*/;
+        tree[m * 2 + 1]/*.Len*/ = bits;
+      }
+      n--;
+    }
+  }
+}
+
+
+/* ===========================================================================
+ * Generate the codes for a given tree and bit counts (which need not be
+ * optimal).
+ * IN assertion: the array bl_count contains the bit length statistics for
+ * the given tree and the field len is set for all tree elements.
+ * OUT assertion: the field code is set for all tree elements of non
+ *     zero code length.
+ */
+function gen_codes(tree, max_code, bl_count)
+//    ct_data *tree;             /* the tree to decorate */
+//    int max_code;              /* largest code with non zero frequency */
+//    ushf *bl_count;            /* number of codes at each bit length */
+{
+  var next_code = new Array(MAX_BITS + 1); /* next code value for each bit length */
+  var code = 0;              /* running code value */
+  var bits;                  /* bit index */
+  var n;                     /* code index */
+
+  /* The distribution counts are first used to generate the code values
+   * without bit reversal.
+   */
+  for (bits = 1; bits <= MAX_BITS; bits++) {
+    next_code[bits] = code = (code + bl_count[bits - 1]) << 1;
+  }
+  /* Check that the bit counts in bl_count are consistent. The last code
+   * must be all ones.
+   */
+  //Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
+  //        "inconsistent bit counts");
+  //Tracev((stderr,"\ngen_codes: max_code %d ", max_code));
+
+  for (n = 0;  n <= max_code; n++) {
+    var len = tree[n * 2 + 1]/*.Len*/;
+    if (len === 0) { continue; }
+    /* Now reverse the bits */
+    tree[n * 2]/*.Code*/ = bi_reverse(next_code[len]++, len);
+
+    //Tracecv(tree != static_ltree, (stderr,"\nn %3d %c l %2d c %4x (%x) ",
+    //     n, (isgraph(n) ? n : ' '), len, tree[n].Code, next_code[len]-1));
+  }
+}
+
+
+/* ===========================================================================
+ * Initialize the various 'constant' tables.
+ */
+function tr_static_init() {
+  var n;        /* iterates over tree elements */
+  var bits;     /* bit counter */
+  var length;   /* length value */
+  var code;     /* code value */
+  var dist;     /* distance index */
+  var bl_count = new Array(MAX_BITS + 1);
+  /* number of codes at each bit length for an optimal tree */
+
+  // do check in _tr_init()
+  //if (static_init_done) return;
+
+  /* For some embedded targets, global variables are not initialized: */
+/*#ifdef NO_INIT_GLOBAL_POINTERS
+  static_l_desc.static_tree = static_ltree;
+  static_l_desc.extra_bits = extra_lbits;
+  static_d_desc.static_tree = static_dtree;
+  static_d_desc.extra_bits = extra_dbits;
+  static_bl_desc.extra_bits = extra_blbits;
+#endif*/
+
+  /* Initialize the mapping length (0..255) -> length code (0..28) */
+  length = 0;
+  for (code = 0; code < LENGTH_CODES - 1; code++) {
+    base_length[code] = length;
+    for (n = 0; n < (1 << extra_lbits[code]); n++) {
+      _length_code[length++] = code;
+    }
+  }
+  //Assert (length == 256, "tr_static_init: length != 256");
+  /* Note that the length 255 (match length 258) can be represented
+   * in two different ways: code 284 + 5 bits or code 285, so we
+   * overwrite length_code[255] to use the best encoding:
+   */
+  _length_code[length - 1] = code;
+
+  /* Initialize the mapping dist (0..32K) -> dist code (0..29) */
+  dist = 0;
+  for (code = 0; code < 16; code++) {
+    base_dist[code] = dist;
+    for (n = 0; n < (1 << extra_dbits[code]); n++) {
+      _dist_code[dist++] = code;
+    }
+  }
+  //Assert (dist == 256, "tr_static_init: dist != 256");
+  dist >>= 7; /* from now on, all distances are divided by 128 */
+  for (; code < D_CODES; code++) {
+    base_dist[code] = dist << 7;
+    for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) {
+      _dist_code[256 + dist++] = code;
+    }
+  }
+  //Assert (dist == 256, "tr_static_init: 256+dist != 512");
+
+  /* Construct the codes of the static literal tree */
+  for (bits = 0; bits <= MAX_BITS; bits++) {
+    bl_count[bits] = 0;
+  }
+
+  n = 0;
+  while (n <= 143) {
+    static_ltree[n * 2 + 1]/*.Len*/ = 8;
+    n++;
+    bl_count[8]++;
+  }
+  while (n <= 255) {
+    static_ltree[n * 2 + 1]/*.Len*/ = 9;
+    n++;
+    bl_count[9]++;
+  }
+  while (n <= 279) {
+    static_ltree[n * 2 + 1]/*.Len*/ = 7;
+    n++;
+    bl_count[7]++;
+  }
+  while (n <= 287) {
+    static_ltree[n * 2 + 1]/*.Len*/ = 8;
+    n++;
+    bl_count[8]++;
+  }
+  /* Codes 286 and 287 do not exist, but we must include them in the
+   * tree construction to get a canonical Huffman tree (longest code
+   * all ones)
+   */
+  gen_codes(static_ltree, L_CODES + 1, bl_count);
+
+  /* The static distance tree is trivial: */
+  for (n = 0; n < D_CODES; n++) {
+    static_dtree[n * 2 + 1]/*.Len*/ = 5;
+    static_dtree[n * 2]/*.Code*/ = bi_reverse(n, 5);
+  }
+
+  // Now data ready and we can init static trees
+  static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS + 1, L_CODES, MAX_BITS);
+  static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0,          D_CODES, MAX_BITS);
+  static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0,         BL_CODES, MAX_BL_BITS);
+
+  //static_init_done = true;
+}
+
+
+/* ===========================================================================
+ * Initialize a new block.
+ */
+function init_block(s) {
+  var n; /* iterates over tree elements */
+
+  /* Initialize the trees. */
+  for (n = 0; n < L_CODES;  n++) { s.dyn_ltree[n * 2]/*.Freq*/ = 0; }
+  for (n = 0; n < D_CODES;  n++) { s.dyn_dtree[n * 2]/*.Freq*/ = 0; }
+  for (n = 0; n < BL_CODES; n++) { s.bl_tree[n * 2]/*.Freq*/ = 0; }
+
+  s.dyn_ltree[END_BLOCK * 2]/*.Freq*/ = 1;
+  s.opt_len = s.static_len = 0;
+  s.last_lit = s.matches = 0;
+}
+
+
+/* ===========================================================================
+ * Flush the bit buffer and align the output on a byte boundary
+ */
+function bi_windup(s)
+{
+  if (s.bi_valid > 8) {
+    put_short(s, s.bi_buf);
+  } else if (s.bi_valid > 0) {
+    //put_byte(s, (Byte)s->bi_buf);
+    s.pending_buf[s.pending++] = s.bi_buf;
+  }
+  s.bi_buf = 0;
+  s.bi_valid = 0;
+}
+
+/* ===========================================================================
+ * Copy a stored block, storing first the length and its
+ * one's complement if requested.
+ */
+function copy_block(s, buf, len, header)
+//DeflateState *s;
+//charf    *buf;    /* the input data */
+//unsigned len;     /* its length */
+//int      header;  /* true if block header must be written */
+{
+  bi_windup(s);        /* align on byte boundary */
+
+  if (header) {
+    put_short(s, len);
+    put_short(s, ~len);
+  }
+//  while (len--) {
+//    put_byte(s, *buf++);
+//  }
+  utils.arraySet(s.pending_buf, s.window, buf, len, s.pending);
+  s.pending += len;
+}
+
+/* ===========================================================================
+ * Compares to subtrees, using the tree depth as tie breaker when
+ * the subtrees have equal frequency. This minimizes the worst case length.
+ */
+function smaller(tree, n, m, depth) {
+  var _n2 = n * 2;
+  var _m2 = m * 2;
+  return (tree[_n2]/*.Freq*/ < tree[_m2]/*.Freq*/ ||
+         (tree[_n2]/*.Freq*/ === tree[_m2]/*.Freq*/ && depth[n] <= depth[m]));
+}
+
+/* ===========================================================================
+ * Restore the heap property by moving down the tree starting at node k,
+ * exchanging a node with the smallest of its two sons if necessary, stopping
+ * when the heap property is re-established (each father smaller than its
+ * two sons).
+ */
+function pqdownheap(s, tree, k)
+//    deflate_state *s;
+//    ct_data *tree;  /* the tree to restore */
+//    int k;               /* node to move down */
+{
+  var v = s.heap[k];
+  var j = k << 1;  /* left son of k */
+  while (j <= s.heap_len) {
+    /* Set j to the smallest of the two sons: */
+    if (j < s.heap_len &&
+      smaller(tree, s.heap[j + 1], s.heap[j], s.depth)) {
+      j++;
+    }
+    /* Exit if v is smaller than both sons */
+    if (smaller(tree, v, s.heap[j], s.depth)) { break; }
+
+    /* Exchange v with the smallest son */
+    s.heap[k] = s.heap[j];
+    k = j;
+
+    /* And continue down the tree, setting j to the left son of k */
+    j <<= 1;
+  }
+  s.heap[k] = v;
+}
+
+
+// inlined manually
+// var SMALLEST = 1;
+
+/* ===========================================================================
+ * Send the block data compressed using the given Huffman trees
+ */
+function compress_block(s, ltree, dtree)
+//    deflate_state *s;
+//    const ct_data *ltree; /* literal tree */
+//    const ct_data *dtree; /* distance tree */
+{
+  var dist;           /* distance of matched string */
+  var lc;             /* match length or unmatched char (if dist == 0) */
+  var lx = 0;         /* running index in l_buf */
+  var code;           /* the code to send */
+  var extra;          /* number of extra bits to send */
+
+  if (s.last_lit !== 0) {
+    do {
+      dist = (s.pending_buf[s.d_buf + lx * 2] << 8) | (s.pending_buf[s.d_buf + lx * 2 + 1]);
+      lc = s.pending_buf[s.l_buf + lx];
+      lx++;
+
+      if (dist === 0) {
+        send_code(s, lc, ltree); /* send a literal byte */
+        //Tracecv(isgraph(lc), (stderr," '%c' ", lc));
+      } else {
+        /* Here, lc is the match length - MIN_MATCH */
+        code = _length_code[lc];
+        send_code(s, code + LITERALS + 1, ltree); /* send the length code */
+        extra = extra_lbits[code];
+        if (extra !== 0) {
+          lc -= base_length[code];
+          send_bits(s, lc, extra);       /* send the extra length bits */
+        }
+        dist--; /* dist is now the match distance - 1 */
+        code = d_code(dist);
+        //Assert (code < D_CODES, "bad d_code");
+
+        send_code(s, code, dtree);       /* send the distance code */
+        extra = extra_dbits[code];
+        if (extra !== 0) {
+          dist -= base_dist[code];
+          send_bits(s, dist, extra);   /* send the extra distance bits */
+        }
+      } /* literal or match pair ? */
+
+      /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */
+      //Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx,
+      //       "pendingBuf overflow");
+
+    } while (lx < s.last_lit);
+  }
+
+  send_code(s, END_BLOCK, ltree);
+}
+
+
+/* ===========================================================================
+ * Construct one Huffman tree and assigns the code bit strings and lengths.
+ * Update the total bit length for the current block.
+ * IN assertion: the field freq is set for all tree elements.
+ * OUT assertions: the fields len and code are set to the optimal bit length
+ *     and corresponding code. The length opt_len is updated; static_len is
+ *     also updated if stree is not null. The field max_code is set.
+ */
+function build_tree(s, desc)
+//    deflate_state *s;
+//    tree_desc *desc; /* the tree descriptor */
+{
+  var tree     = desc.dyn_tree;
+  var stree    = desc.stat_desc.static_tree;
+  var has_stree = desc.stat_desc.has_stree;
+  var elems    = desc.stat_desc.elems;
+  var n, m;          /* iterate over heap elements */
+  var max_code = -1; /* largest code with non zero frequency */
+  var node;          /* new node being created */
+
+  /* Construct the initial heap, with least frequent element in
+   * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
+   * heap[0] is not used.
+   */
+  s.heap_len = 0;
+  s.heap_max = HEAP_SIZE;
+
+  for (n = 0; n < elems; n++) {
+    if (tree[n * 2]/*.Freq*/ !== 0) {
+      s.heap[++s.heap_len] = max_code = n;
+      s.depth[n] = 0;
+
+    } else {
+      tree[n * 2 + 1]/*.Len*/ = 0;
+    }
+  }
+
+  /* The pkzip format requires that at least one distance code exists,
+   * and that at least one bit should be sent even if there is only one
+   * possible code. So to avoid special checks later on we force at least
+   * two codes of non zero frequency.
+   */
+  while (s.heap_len < 2) {
+    node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0);
+    tree[node * 2]/*.Freq*/ = 1;
+    s.depth[node] = 0;
+    s.opt_len--;
+
+    if (has_stree) {
+      s.static_len -= stree[node * 2 + 1]/*.Len*/;
+    }
+    /* node is 0 or 1 so it does not have extra bits */
+  }
+  desc.max_code = max_code;
+
+  /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
+   * establish sub-heaps of increasing lengths:
+   */
+  for (n = (s.heap_len >> 1/*int /2*/); n >= 1; n--) { pqdownheap(s, tree, n); }
+
+  /* Construct the Huffman tree by repeatedly combining the least two
+   * frequent nodes.
+   */
+  node = elems;              /* next internal node of the tree */
+  do {
+    //pqremove(s, tree, n);  /* n = node of least frequency */
+    /*** pqremove ***/
+    n = s.heap[1/*SMALLEST*/];
+    s.heap[1/*SMALLEST*/] = s.heap[s.heap_len--];
+    pqdownheap(s, tree, 1/*SMALLEST*/);
+    /***/
+
+    m = s.heap[1/*SMALLEST*/]; /* m = node of next least frequency */
+
+    s.heap[--s.heap_max] = n; /* keep the nodes sorted by frequency */
+    s.heap[--s.heap_max] = m;
+
+    /* Create a new node father of n and m */
+    tree[node * 2]/*.Freq*/ = tree[n * 2]/*.Freq*/ + tree[m * 2]/*.Freq*/;
+    s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1;
+    tree[n * 2 + 1]/*.Dad*/ = tree[m * 2 + 1]/*.Dad*/ = node;
+
+    /* and insert the new node in the heap */
+    s.heap[1/*SMALLEST*/] = node++;
+    pqdownheap(s, tree, 1/*SMALLEST*/);
+
+  } while (s.heap_len >= 2);
+
+  s.heap[--s.heap_max] = s.heap[1/*SMALLEST*/];
+
+  /* At this point, the fields freq and dad are set. We can now
+   * generate the bit lengths.
+   */
+  gen_bitlen(s, desc);
+
+  /* The field len is now set, we can generate the bit codes */
+  gen_codes(tree, max_code, s.bl_count);
+}
+
+
+/* ===========================================================================
+ * Scan a literal or distance tree to determine the frequencies of the codes
+ * in the bit length tree.
+ */
+function scan_tree(s, tree, max_code)
+//    deflate_state *s;
+//    ct_data *tree;   /* the tree to be scanned */
+//    int max_code;    /* and its largest code of non zero frequency */
+{
+  var n;                     /* iterates over all tree elements */
+  var prevlen = -1;          /* last emitted length */
+  var curlen;                /* length of current code */
+
+  var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */
+
+  var count = 0;             /* repeat count of the current code */
+  var max_count = 7;         /* max repeat count */
+  var min_count = 4;         /* min repeat count */
+
+  if (nextlen === 0) {
+    max_count = 138;
+    min_count = 3;
+  }
+  tree[(max_code + 1) * 2 + 1]/*.Len*/ = 0xffff; /* guard */
+
+  for (n = 0; n <= max_code; n++) {
+    curlen = nextlen;
+    nextlen = tree[(n + 1) * 2 + 1]/*.Len*/;
+
+    if (++count < max_count && curlen === nextlen) {
+      continue;
+
+    } else if (count < min_count) {
+      s.bl_tree[curlen * 2]/*.Freq*/ += count;
+
+    } else if (curlen !== 0) {
+
+      if (curlen !== prevlen) { s.bl_tree[curlen * 2]/*.Freq*/++; }
+      s.bl_tree[REP_3_6 * 2]/*.Freq*/++;
+
+    } else if (count <= 10) {
+      s.bl_tree[REPZ_3_10 * 2]/*.Freq*/++;
+
+    } else {
+      s.bl_tree[REPZ_11_138 * 2]/*.Freq*/++;
+    }
+
+    count = 0;
+    prevlen = curlen;
+
+    if (nextlen === 0) {
+      max_count = 138;
+      min_count = 3;
+
+    } else if (curlen === nextlen) {
+      max_count = 6;
+      min_count = 3;
+
+    } else {
+      max_count = 7;
+      min_count = 4;
+    }
+  }
+}
+
+
+/* ===========================================================================
+ * Send a literal or distance tree in compressed form, using the codes in
+ * bl_tree.
+ */
+function send_tree(s, tree, max_code)
+//    deflate_state *s;
+//    ct_data *tree; /* the tree to be scanned */
+//    int max_code;       /* and its largest code of non zero frequency */
+{
+  var n;                     /* iterates over all tree elements */
+  var prevlen = -1;          /* last emitted length */
+  var curlen;                /* length of current code */
+
+  var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */
+
+  var count = 0;             /* repeat count of the current code */
+  var max_count = 7;         /* max repeat count */
+  var min_count = 4;         /* min repeat count */
+
+  /* tree[max_code+1].Len = -1; */  /* guard already set */
+  if (nextlen === 0) {
+    max_count = 138;
+    min_count = 3;
+  }
+
+  for (n = 0; n <= max_code; n++) {
+    curlen = nextlen;
+    nextlen = tree[(n + 1) * 2 + 1]/*.Len*/;
+
+    if (++count < max_count && curlen === nextlen) {
+      continue;
+
+    } else if (count < min_count) {
+      do { send_code(s, curlen, s.bl_tree); } while (--count !== 0);
+
+    } else if (curlen !== 0) {
+      if (curlen !== prevlen) {
+        send_code(s, curlen, s.bl_tree);
+        count--;
+      }
+      //Assert(count >= 3 && count <= 6, " 3_6?");
+      send_code(s, REP_3_6, s.bl_tree);
+      send_bits(s, count - 3, 2);
+
+    } else if (count <= 10) {
+      send_code(s, REPZ_3_10, s.bl_tree);
+      send_bits(s, count - 3, 3);
+
+    } else {
+      send_code(s, REPZ_11_138, s.bl_tree);
+      send_bits(s, count - 11, 7);
+    }
+
+    count = 0;
+    prevlen = curlen;
+    if (nextlen === 0) {
+      max_count = 138;
+      min_count = 3;
+
+    } else if (curlen === nextlen) {
+      max_count = 6;
+      min_count = 3;
+
+    } else {
+      max_count = 7;
+      min_count = 4;
+    }
+  }
+}
+
+
+/* ===========================================================================
+ * Construct the Huffman tree for the bit lengths and return the index in
+ * bl_order of the last bit length code to send.
+ */
+function build_bl_tree(s) {
+  var max_blindex;  /* index of last bit length code of non zero freq */
+
+  /* Determine the bit length frequencies for literal and distance trees */
+  scan_tree(s, s.dyn_ltree, s.l_desc.max_code);
+  scan_tree(s, s.dyn_dtree, s.d_desc.max_code);
+
+  /* Build the bit length tree: */
+  build_tree(s, s.bl_desc);
+  /* opt_len now includes the length of the tree representations, except
+   * the lengths of the bit lengths codes and the 5+5+4 bits for the counts.
+   */
+
+  /* Determine the number of bit length codes to send. The pkzip format
+   * requires that at least 4 bit length codes be sent. (appnote.txt says
+   * 3 but the actual value used is 4.)
+   */
+  for (max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) {
+    if (s.bl_tree[bl_order[max_blindex] * 2 + 1]/*.Len*/ !== 0) {
+      break;
+    }
+  }
+  /* Update opt_len to include the bit length tree and counts */
+  s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4;
+  //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld",
+  //        s->opt_len, s->static_len));
+
+  return max_blindex;
+}
+
+
+/* ===========================================================================
+ * Send the header for a block using dynamic Huffman trees: the counts, the
+ * lengths of the bit length codes, the literal tree and the distance tree.
+ * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
+ */
+function send_all_trees(s, lcodes, dcodes, blcodes)
+//    deflate_state *s;
+//    int lcodes, dcodes, blcodes; /* number of codes for each tree */
+{
+  var rank;                    /* index in bl_order */
+
+  //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes");
+  //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES,
+  //        "too many codes");
+  //Tracev((stderr, "\nbl counts: "));
+  send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */
+  send_bits(s, dcodes - 1,   5);
+  send_bits(s, blcodes - 4,  4); /* not -3 as stated in appnote.txt */
+  for (rank = 0; rank < blcodes; rank++) {
+    //Tracev((stderr, "\nbl code %2d ", bl_order[rank]));
+    send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1]/*.Len*/, 3);
+  }
+  //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent));
+
+  send_tree(s, s.dyn_ltree, lcodes - 1); /* literal tree */
+  //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent));
+
+  send_tree(s, s.dyn_dtree, dcodes - 1); /* distance tree */
+  //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent));
+}
+
+
+/* ===========================================================================
+ * Check if the data type is TEXT or BINARY, using the following algorithm:
+ * - TEXT if the two conditions below are satisfied:
+ *    a) There are no non-portable control characters belonging to the
+ *       "black list" (0..6, 14..25, 28..31).
+ *    b) There is at least one printable character belonging to the
+ *       "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255).
+ * - BINARY otherwise.
+ * - The following partially-portable control characters form a
+ *   "gray list" that is ignored in this detection algorithm:
+ *   (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}).
+ * IN assertion: the fields Freq of dyn_ltree are set.
+ */
+function detect_data_type(s) {
+  /* black_mask is the bit mask of black-listed bytes
+   * set bits 0..6, 14..25, and 28..31
+   * 0xf3ffc07f = binary 11110011111111111100000001111111
+   */
+  var black_mask = 0xf3ffc07f;
+  var n;
+
+  /* Check for non-textual ("black-listed") bytes. */
+  for (n = 0; n <= 31; n++, black_mask >>>= 1) {
+    if ((black_mask & 1) && (s.dyn_ltree[n * 2]/*.Freq*/ !== 0)) {
+      return Z_BINARY;
+    }
+  }
+
+  /* Check for textual ("white-listed") bytes. */
+  if (s.dyn_ltree[9 * 2]/*.Freq*/ !== 0 || s.dyn_ltree[10 * 2]/*.Freq*/ !== 0 ||
+      s.dyn_ltree[13 * 2]/*.Freq*/ !== 0) {
+    return Z_TEXT;
+  }
+  for (n = 32; n < LITERALS; n++) {
+    if (s.dyn_ltree[n * 2]/*.Freq*/ !== 0) {
+      return Z_TEXT;
+    }
+  }
+
+  /* There are no "black-listed" or "white-listed" bytes:
+   * this stream either is empty or has tolerated ("gray-listed") bytes only.
+   */
+  return Z_BINARY;
+}
+
+
+var static_init_done = false;
+
+/* ===========================================================================
+ * Initialize the tree data structures for a new zlib stream.
+ */
+function _tr_init(s)
+{
+
+  if (!static_init_done) {
+    tr_static_init();
+    static_init_done = true;
+  }
+
+  s.l_desc  = new TreeDesc(s.dyn_ltree, static_l_desc);
+  s.d_desc  = new TreeDesc(s.dyn_dtree, static_d_desc);
+  s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc);
+
+  s.bi_buf = 0;
+  s.bi_valid = 0;
+
+  /* Initialize the first block of the first file: */
+  init_block(s);
+}
+
+
+/* ===========================================================================
+ * Send a stored block
+ */
+function _tr_stored_block(s, buf, stored_len, last)
+//DeflateState *s;
+//charf *buf;       /* input block */
+//ulg stored_len;   /* length of input block */
+//int last;         /* one if this is the last block for a file */
+{
+  send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3);    /* send block type */
+  copy_block(s, buf, stored_len, true); /* with header */
+}
+
+
+/* ===========================================================================
+ * Send one empty static block to give enough lookahead for inflate.
+ * This takes 10 bits, of which 7 may remain in the bit buffer.
+ */
+function _tr_align(s) {
+  send_bits(s, STATIC_TREES << 1, 3);
+  send_code(s, END_BLOCK, static_ltree);
+  bi_flush(s);
+}
+
+
+/* ===========================================================================
+ * Determine the best encoding for the current block: dynamic trees, static
+ * trees or store, and output the encoded block to the zip file.
+ */
+function _tr_flush_block(s, buf, stored_len, last)
+//DeflateState *s;
+//charf *buf;       /* input block, or NULL if too old */
+//ulg stored_len;   /* length of input block */
+//int last;         /* one if this is the last block for a file */
+{
+  var opt_lenb, static_lenb;  /* opt_len and static_len in bytes */
+  var max_blindex = 0;        /* index of last bit length code of non zero freq */
+
+  /* Build the Huffman trees unless a stored block is forced */
+  if (s.level > 0) {
+
+    /* Check if the file is binary or text */
+    if (s.strm.data_type === Z_UNKNOWN) {
+      s.strm.data_type = detect_data_type(s);
+    }
+
+    /* Construct the literal and distance trees */
+    build_tree(s, s.l_desc);
+    // Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len,
+    //        s->static_len));
+
+    build_tree(s, s.d_desc);
+    // Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len,
+    //        s->static_len));
+    /* At this point, opt_len and static_len are the total bit lengths of
+     * the compressed block data, excluding the tree representations.
+     */
+
+    /* Build the bit length tree for the above two trees, and get the index
+     * in bl_order of the last bit length code to send.
+     */
+    max_blindex = build_bl_tree(s);
+
+    /* Determine the best encoding. Compute the block lengths in bytes. */
+    opt_lenb = (s.opt_len + 3 + 7) >>> 3;
+    static_lenb = (s.static_len + 3 + 7) >>> 3;
+
+    // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ",
+    //        opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len,
+    //        s->last_lit));
+
+    if (static_lenb <= opt_lenb) { opt_lenb = static_lenb; }
+
+  } else {
+    // Assert(buf != (char*)0, "lost buf");
+    opt_lenb = static_lenb = stored_len + 5; /* force a stored block */
+  }
+
+  if ((stored_len + 4 <= opt_lenb) && (buf !== -1)) {
+    /* 4: two words for the lengths */
+
+    /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.
+     * Otherwise we can't have processed more than WSIZE input bytes since
+     * the last block flush, because compression would have been
+     * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to
+     * transform a block into a stored block.
+     */
+    _tr_stored_block(s, buf, stored_len, last);
+
+  } else if (s.strategy === Z_FIXED || static_lenb === opt_lenb) {
+
+    send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3);
+    compress_block(s, static_ltree, static_dtree);
+
+  } else {
+    send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3);
+    send_all_trees(s, s.l_desc.max_code + 1, s.d_desc.max_code + 1, max_blindex + 1);
+    compress_block(s, s.dyn_ltree, s.dyn_dtree);
+  }
+  // Assert (s->compressed_len == s->bits_sent, "bad compressed size");
+  /* The above check is made mod 2^32, for files larger than 512 MB
+   * and uLong implemented on 32 bits.
+   */
+  init_block(s);
+
+  if (last) {
+    bi_windup(s);
+  }
+  // Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3,
+  //       s->compressed_len-7*last));
+}
+
+/* ===========================================================================
+ * Save the match info and tally the frequency counts. Return true if
+ * the current block must be flushed.
+ */
+function _tr_tally(s, dist, lc)
+//    deflate_state *s;
+//    unsigned dist;  /* distance of matched string */
+//    unsigned lc;    /* match length-MIN_MATCH or unmatched char (if dist==0) */
+{
+  //var out_length, in_length, dcode;
+
+  s.pending_buf[s.d_buf + s.last_lit * 2]     = (dist >>> 8) & 0xff;
+  s.pending_buf[s.d_buf + s.last_lit * 2 + 1] = dist & 0xff;
+
+  s.pending_buf[s.l_buf + s.last_lit] = lc & 0xff;
+  s.last_lit++;
+
+  if (dist === 0) {
+    /* lc is the unmatched char */
+    s.dyn_ltree[lc * 2]/*.Freq*/++;
+  } else {
+    s.matches++;
+    /* Here, lc is the match length - MIN_MATCH */
+    dist--;             /* dist = match distance - 1 */
+    //Assert((ush)dist < (ush)MAX_DIST(s) &&
+    //       (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) &&
+    //       (ush)d_code(dist) < (ush)D_CODES,  "_tr_tally: bad match");
+
+    s.dyn_ltree[(_length_code[lc] + LITERALS + 1) * 2]/*.Freq*/++;
+    s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++;
+  }
+
+// (!) This block is disabled in zlib defailts,
+// don't enable it for binary compatibility
+
+//#ifdef TRUNCATE_BLOCK
+//  /* Try to guess if it is profitable to stop the current block here */
+//  if ((s.last_lit & 0x1fff) === 0 && s.level > 2) {
+//    /* Compute an upper bound for the compressed length */
+//    out_length = s.last_lit*8;
+//    in_length = s.strstart - s.block_start;
+//
+//    for (dcode = 0; dcode < D_CODES; dcode++) {
+//      out_length += s.dyn_dtree[dcode*2]/*.Freq*/ * (5 + extra_dbits[dcode]);
+//    }
+//    out_length >>>= 3;
+//    //Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ",
+//    //       s->last_lit, in_length, out_length,
+//    //       100L - out_length*100L/in_length));
+//    if (s.matches < (s.last_lit>>1)/*int /2*/ && out_length < (in_length>>1)/*int /2*/) {
+//      return true;
+//    }
+//  }
+//#endif
+
+  return (s.last_lit === s.lit_bufsize - 1);
+  /* We avoid equality with lit_bufsize because of wraparound at 64K
+   * on 16 bit machines and because stored blocks are restricted to
+   * 64K-1 bytes.
+   */
+}
+
+exports._tr_init  = _tr_init;
+exports._tr_stored_block = _tr_stored_block;
+exports._tr_flush_block  = _tr_flush_block;
+exports._tr_tally = _tr_tally;
+exports._tr_align = _tr_align;
+
+},{"../utils/common":212}],219:[function(require,module,exports){
+'use strict';
+
+
+function ZStream() {
+  /* next input byte */
+  this.input = null; // JS specific, because we have no pointers
+  this.next_in = 0;
+  /* number of bytes available at input */
+  this.avail_in = 0;
+  /* total number of input bytes read so far */
+  this.total_in = 0;
+  /* next output byte should be put there */
+  this.output = null; // JS specific, because we have no pointers
+  this.next_out = 0;
+  /* remaining free space at output */
+  this.avail_out = 0;
+  /* total number of bytes output so far */
+  this.total_out = 0;
+  /* last error message, NULL if no error */
+  this.msg = ''/*Z_NULL*/;
+  /* not visible by applications */
+  this.state = null;
+  /* best guess about the data type: binary or text */
+  this.data_type = 2/*Z_UNKNOWN*/;
+  /* adler32 value of the uncompressed data */
+  this.adler = 0;
+}
+
+module.exports = ZStream;
+
+},{}],220:[function(require,module,exports){
+(function (process){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// resolves . and .. elements in a path array with directory names there
+// must be no slashes, empty elements, or device names (c:\) in the array
+// (so also no leading and trailing slashes - it does not distinguish
+// relative and absolute paths)
+function normalizeArray(parts, allowAboveRoot) {
+  // if the path tries to go above the root, `up` ends up > 0
+  var up = 0;
+  for (var i = parts.length - 1; i >= 0; i--) {
+    var last = parts[i];
+    if (last === '.') {
+      parts.splice(i, 1);
+    } else if (last === '..') {
+      parts.splice(i, 1);
+      up++;
+    } else if (up) {
+      parts.splice(i, 1);
+      up--;
+    }
+  }
+
+  // if the path is allowed to go above the root, restore leading ..s
+  if (allowAboveRoot) {
+    for (; up--; up) {
+      parts.unshift('..');
+    }
+  }
+
+  return parts;
+}
+
+// Split a filename into [root, dir, basename, ext], unix version
+// 'root' is just a slash, or nothing.
+var splitPathRe =
+    /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
+var splitPath = function(filename) {
+  return splitPathRe.exec(filename).slice(1);
+};
+
+// path.resolve([from ...], to)
+// posix version
+exports.resolve = function() {
+  var resolvedPath = '',
+      resolvedAbsolute = false;
+
+  for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
+    var path = (i >= 0) ? arguments[i] : process.cwd();
+
+    // Skip empty and invalid entries
+    if (typeof path !== 'string') {
+      throw new TypeError('Arguments to path.resolve must be strings');
+    } else if (!path) {
+      continue;
+    }
+
+    resolvedPath = path + '/' + resolvedPath;
+    resolvedAbsolute = path.charAt(0) === '/';
+  }
+
+  // At this point the path should be resolved to a full absolute path, but
+  // handle relative paths to be safe (might happen when process.cwd() fails)
+
+  // Normalize the path
+  resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) {
+    return !!p;
+  }), !resolvedAbsolute).join('/');
+
+  return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
+};
+
+// path.normalize(path)
+// posix version
+exports.normalize = function(path) {
+  var isAbsolute = exports.isAbsolute(path),
+      trailingSlash = substr(path, -1) === '/';
+
+  // Normalize the path
+  path = normalizeArray(filter(path.split('/'), function(p) {
+    return !!p;
+  }), !isAbsolute).join('/');
+
+  if (!path && !isAbsolute) {
+    path = '.';
+  }
+  if (path && trailingSlash) {
+    path += '/';
+  }
+
+  return (isAbsolute ? '/' : '') + path;
+};
+
+// posix version
+exports.isAbsolute = function(path) {
+  return path.charAt(0) === '/';
+};
+
+// posix version
+exports.join = function() {
+  var paths = Array.prototype.slice.call(arguments, 0);
+  return exports.normalize(filter(paths, function(p, index) {
+    if (typeof p !== 'string') {
+      throw new TypeError('Arguments to path.join must be strings');
+    }
+    return p;
+  }).join('/'));
 };
 
 
+// path.relative(from, to)
+// posix version
+exports.relative = function(from, to) {
+  from = exports.resolve(from).substr(1);
+  to = exports.resolve(to).substr(1);
 
+  function trim(arr) {
+    var start = 0;
+    for (; start < arr.length; start++) {
+      if (arr[start] !== '') break;
+    }
 
-exports.relative=function(from,to){
-from=exports.resolve(from).substr(1);
-to=exports.resolve(to).substr(1);
+    var end = arr.length - 1;
+    for (; end >= 0; end--) {
+      if (arr[end] !== '') break;
+    }
 
-function trim(arr){
-var start=0;
-for(;start<arr.length;start++){
-if(arr[start]!=='')break;
-}
+    if (start > end) return [];
+    return arr.slice(start, end - start + 1);
+  }
 
-var end=arr.length-1;
-for(;end>=0;end--){
-if(arr[end]!=='')break;
-}
+  var fromParts = trim(from.split('/'));
+  var toParts = trim(to.split('/'));
 
-if(start>end)return[];
-return arr.slice(start,end-start+1);
-}
+  var length = Math.min(fromParts.length, toParts.length);
+  var samePartsLength = length;
+  for (var i = 0; i < length; i++) {
+    if (fromParts[i] !== toParts[i]) {
+      samePartsLength = i;
+      break;
+    }
+  }
 
-var fromParts=trim(from.split('/'));
-var toParts=trim(to.split('/'));
+  var outputParts = [];
+  for (var i = samePartsLength; i < fromParts.length; i++) {
+    outputParts.push('..');
+  }
 
-var length=Math.min(fromParts.length,toParts.length);
-var samePartsLength=length;
-for(var i=0;i<length;i++){
-if(fromParts[i]!==toParts[i]){
-samePartsLength=i;
-break;
-}
-}
+  outputParts = outputParts.concat(toParts.slice(samePartsLength));
 
-var outputParts=[];
-for(var i=samePartsLength;i<fromParts.length;i++){
-outputParts.push('..');
-}
-
-outputParts=outputParts.concat(toParts.slice(samePartsLength));
-
-return outputParts.join('/');
+  return outputParts.join('/');
 };
 
-exports.sep='/';
-exports.delimiter=':';
+exports.sep = '/';
+exports.delimiter = ':';
 
-exports.dirname=function(path){
-var result=splitPath(path),
-root=result[0],
-dir=result[1];
+exports.dirname = function(path) {
+  var result = splitPath(path),
+      root = result[0],
+      dir = result[1];
 
-if(!root&&!dir){
+  if (!root && !dir) {
+    // No dirname whatsoever
+    return '.';
+  }
 
-return'.';
-}
+  if (dir) {
+    // It has a dirname, strip trailing slash
+    dir = dir.substr(0, dir.length - 1);
+  }
 
-if(dir){
-
-dir=dir.substr(0,dir.length-1);
-}
-
-return root+dir;
+  return root + dir;
 };
 
 
-exports.basename=function(path,ext){
-var f=splitPath(path)[2];
-
-if(ext&&f.substr(-1*ext.length)===ext){
-f=f.substr(0,f.length-ext.length);
-}
-return f;
+exports.basename = function(path, ext) {
+  var f = splitPath(path)[2];
+  // TODO: make this comparison case-insensitive on windows?
+  if (ext && f.substr(-1 * ext.length) === ext) {
+    f = f.substr(0, f.length - ext.length);
+  }
+  return f;
 };
 
 
-exports.extname=function(path){
-return splitPath(path)[3];
+exports.extname = function(path) {
+  return splitPath(path)[3];
 };
 
-function filter(xs,f){
-if(xs.filter)return xs.filter(f);
-var res=[];
-for(var i=0;i<xs.length;i++){
-if(f(xs[i],i,xs))res.push(xs[i]);
-}
-return res;
+function filter (xs, f) {
+    if (xs.filter) return xs.filter(f);
+    var res = [];
+    for (var i = 0; i < xs.length; i++) {
+        if (f(xs[i], i, xs)) res.push(xs[i]);
+    }
+    return res;
 }
 
+// String.prototype.substr - negative index don't work in IE8
+var substr = 'ab'.substr(-1) === 'b'
+    ? function (str, start, len) { return str.substr(start, len) }
+    : function (str, start, len) {
+        if (start < 0) start = str.length + start;
+        return str.substr(start, len);
+    }
+;
 
-var substr='ab'.substr(-1)==='b'?
-function(str,start,len){return str.substr(start,len);}:
-function(str,start,len){
-if(start<0)start=str.length+start;
-return str.substr(start,len);
-};
+}).call(this,require('_process'))
+},{"_process":222}],221:[function(require,module,exports){
+(function (process){
+'use strict';
 
+if (!process.version ||
+    process.version.indexOf('v0.') === 0 ||
+    process.version.indexOf('v1.') === 0 && process.version.indexOf('v1.8.') !== 0) {
+  module.exports = nextTick;
+} else {
+  module.exports = process.nextTick;
+}
 
-}).call(this,require('_process'));
-},{"_process":204}],204:[function(require,module,exports){
+function nextTick(fn, arg1, arg2, arg3) {
+  if (typeof fn !== 'function') {
+    throw new TypeError('"callback" argument must be a function');
+  }
+  var len = arguments.length;
+  var args, i;
+  switch (len) {
+  case 0:
+  case 1:
+    return process.nextTick(fn);
+  case 2:
+    return process.nextTick(function afterTickOne() {
+      fn.call(null, arg1);
+    });
+  case 3:
+    return process.nextTick(function afterTickTwo() {
+      fn.call(null, arg1, arg2);
+    });
+  case 4:
+    return process.nextTick(function afterTickThree() {
+      fn.call(null, arg1, arg2, arg3);
+    });
+  default:
+    args = new Array(len - 1);
+    i = 0;
+    while (i < args.length) {
+      args[i++] = arguments[i];
+    }
+    return process.nextTick(function afterTick() {
+      fn.apply(null, args);
+    });
+  }
+}
 
-var process=module.exports={};
+}).call(this,require('_process'))
+},{"_process":222}],222:[function(require,module,exports){
+// shim for using process in browser
+var process = module.exports = {};
 
-
-
-
-
+// cached from whatever global is present so that test runners that stub it
+// don't break things.  But we need to wrap it in a try catch in case it is
+// wrapped in strict mode code which doesn't define any globals.  It's inside a
+// function because try/catches deoptimize in certain engines.
 
 var cachedSetTimeout;
 var cachedClearTimeout;
 
-function defaultSetTimout(){
-throw new Error('setTimeout has not been defined');
+function defaultSetTimout() {
+    throw new Error('setTimeout has not been defined');
 }
-function defaultClearTimeout(){
-throw new Error('clearTimeout has not been defined');
+function defaultClearTimeout () {
+    throw new Error('clearTimeout has not been defined');
 }
-(function(){
-try{
-if(typeof setTimeout==='function'){
-cachedSetTimeout=setTimeout;
-}else{
-cachedSetTimeout=defaultSetTimout;
-}
-}catch(e){
-cachedSetTimeout=defaultSetTimout;
-}
-try{
-if(typeof clearTimeout==='function'){
-cachedClearTimeout=clearTimeout;
-}else{
-cachedClearTimeout=defaultClearTimeout;
-}
-}catch(e){
-cachedClearTimeout=defaultClearTimeout;
-}
-})();
-function runTimeout(fun){
-if(cachedSetTimeout===setTimeout){
-
-return setTimeout(fun,0);
-}
-
-if((cachedSetTimeout===defaultSetTimout||!cachedSetTimeout)&&setTimeout){
-cachedSetTimeout=setTimeout;
-return setTimeout(fun,0);
-}
-try{
-
-return cachedSetTimeout(fun,0);
-}catch(e){
-try{
-
-return cachedSetTimeout.call(null,fun,0);
-}catch(e){
-
-return cachedSetTimeout.call(this,fun,0);
-}
-}
+(function () {
+    try {
+        if (typeof setTimeout === 'function') {
+            cachedSetTimeout = setTimeout;
+        } else {
+            cachedSetTimeout = defaultSetTimout;
+        }
+    } catch (e) {
+        cachedSetTimeout = defaultSetTimout;
+    }
+    try {
+        if (typeof clearTimeout === 'function') {
+            cachedClearTimeout = clearTimeout;
+        } else {
+            cachedClearTimeout = defaultClearTimeout;
+        }
+    } catch (e) {
+        cachedClearTimeout = defaultClearTimeout;
+    }
+} ())
+function runTimeout(fun) {
+    if (cachedSetTimeout === setTimeout) {
+        //normal enviroments in sane situations
+        return setTimeout(fun, 0);
+    }
+    // if setTimeout wasn't available but was latter defined
+    if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
+        cachedSetTimeout = setTimeout;
+        return setTimeout(fun, 0);
+    }
+    try {
+        // when when somebody has screwed with setTimeout but no I.E. maddness
+        return cachedSetTimeout(fun, 0);
+    } catch(e){
+        try {
+            // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
+            return cachedSetTimeout.call(null, fun, 0);
+        } catch(e){
+            // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
+            return cachedSetTimeout.call(this, fun, 0);
+        }
+    }
 
 
 }
-function runClearTimeout(marker){
-if(cachedClearTimeout===clearTimeout){
-
-return clearTimeout(marker);
-}
-
-if((cachedClearTimeout===defaultClearTimeout||!cachedClearTimeout)&&clearTimeout){
-cachedClearTimeout=clearTimeout;
-return clearTimeout(marker);
-}
-try{
-
-return cachedClearTimeout(marker);
-}catch(e){
-try{
-
-return cachedClearTimeout.call(null,marker);
-}catch(e){
-
-
-return cachedClearTimeout.call(this,marker);
-}
-}
+function runClearTimeout(marker) {
+    if (cachedClearTimeout === clearTimeout) {
+        //normal enviroments in sane situations
+        return clearTimeout(marker);
+    }
+    // if clearTimeout wasn't available but was latter defined
+    if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
+        cachedClearTimeout = clearTimeout;
+        return clearTimeout(marker);
+    }
+    try {
+        // when when somebody has screwed with setTimeout but no I.E. maddness
+        return cachedClearTimeout(marker);
+    } catch (e){
+        try {
+            // When we are in I.E. but the script has been evaled so I.E. doesn't  trust the global object when called normally
+            return cachedClearTimeout.call(null, marker);
+        } catch (e){
+            // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
+            // Some versions of I.E. have different rules for clearTimeout vs setTimeout
+            return cachedClearTimeout.call(this, marker);
+        }
+    }
 
 
 
 }
-var queue=[];
-var draining=false;
+var queue = [];
+var draining = false;
 var currentQueue;
-var queueIndex=-1;
+var queueIndex = -1;
 
-function cleanUpNextTick(){
-if(!draining||!currentQueue){
-return;
-}
-draining=false;
-if(currentQueue.length){
-queue=currentQueue.concat(queue);
-}else{
-queueIndex=-1;
-}
-if(queue.length){
-drainQueue();
-}
+function cleanUpNextTick() {
+    if (!draining || !currentQueue) {
+        return;
+    }
+    draining = false;
+    if (currentQueue.length) {
+        queue = currentQueue.concat(queue);
+    } else {
+        queueIndex = -1;
+    }
+    if (queue.length) {
+        drainQueue();
+    }
 }
 
-function drainQueue(){
-if(draining){
-return;
-}
-var timeout=runTimeout(cleanUpNextTick);
-draining=true;
+function drainQueue() {
+    if (draining) {
+        return;
+    }
+    var timeout = runTimeout(cleanUpNextTick);
+    draining = true;
 
-var len=queue.length;
-while(len){
-currentQueue=queue;
-queue=[];
-while(++queueIndex<len){
-if(currentQueue){
-currentQueue[queueIndex].run();
-}
-}
-queueIndex=-1;
-len=queue.length;
-}
-currentQueue=null;
-draining=false;
-runClearTimeout(timeout);
+    var len = queue.length;
+    while(len) {
+        currentQueue = queue;
+        queue = [];
+        while (++queueIndex < len) {
+            if (currentQueue) {
+                currentQueue[queueIndex].run();
+            }
+        }
+        queueIndex = -1;
+        len = queue.length;
+    }
+    currentQueue = null;
+    draining = false;
+    runClearTimeout(timeout);
 }
 
-process.nextTick=function(fun){
-var args=new Array(arguments.length-1);
-if(arguments.length>1){
-for(var i=1;i<arguments.length;i++){
-args[i-1]=arguments[i];
+process.nextTick = function (fun) {
+    var args = new Array(arguments.length - 1);
+    if (arguments.length > 1) {
+        for (var i = 1; i < arguments.length; i++) {
+            args[i - 1] = arguments[i];
+        }
+    }
+    queue.push(new Item(fun, args));
+    if (queue.length === 1 && !draining) {
+        runTimeout(drainQueue);
+    }
+};
+
+// v8 likes predictible objects
+function Item(fun, array) {
+    this.fun = fun;
+    this.array = array;
 }
+Item.prototype.run = function () {
+    this.fun.apply(null, this.array);
+};
+process.title = 'browser';
+process.browser = true;
+process.env = {};
+process.argv = [];
+process.version = ''; // empty string to avoid regexp issues
+process.versions = {};
+
+function noop() {}
+
+process.on = noop;
+process.addListener = noop;
+process.once = noop;
+process.off = noop;
+process.removeListener = noop;
+process.removeAllListeners = noop;
+process.emit = noop;
+
+process.binding = function (name) {
+    throw new Error('process.binding is not supported');
+};
+
+process.cwd = function () { return '/' };
+process.chdir = function (dir) {
+    throw new Error('process.chdir is not supported');
+};
+process.umask = function() { return 0; };
+
+},{}],223:[function(require,module,exports){
+// a duplex stream is just a stream that is both readable and writable.
+// Since JS doesn't have multiple prototypal inheritance, this class
+// prototypally inherits from Readable, and then parasitically from
+// Writable.
+
+'use strict';
+
+/*<replacement>*/
+
+var objectKeys = Object.keys || function (obj) {
+  var keys = [];
+  for (var key in obj) {
+    keys.push(key);
+  }return keys;
+};
+/*</replacement>*/
+
+module.exports = Duplex;
+
+/*<replacement>*/
+var processNextTick = require('process-nextick-args');
+/*</replacement>*/
+
+/*<replacement>*/
+var util = require('core-util-is');
+util.inherits = require('inherits');
+/*</replacement>*/
+
+var Readable = require('./_stream_readable');
+var Writable = require('./_stream_writable');
+
+util.inherits(Duplex, Readable);
+
+var keys = objectKeys(Writable.prototype);
+for (var v = 0; v < keys.length; v++) {
+  var method = keys[v];
+  if (!Duplex.prototype[method]) Duplex.prototype[method] = Writable.prototype[method];
 }
-queue.push(new Item(fun,args));
-if(queue.length===1&&!draining){
-runTimeout(drainQueue);
+
+function Duplex(options) {
+  if (!(this instanceof Duplex)) return new Duplex(options);
+
+  Readable.call(this, options);
+  Writable.call(this, options);
+
+  if (options && options.readable === false) this.readable = false;
+
+  if (options && options.writable === false) this.writable = false;
+
+  this.allowHalfOpen = true;
+  if (options && options.allowHalfOpen === false) this.allowHalfOpen = false;
+
+  this.once('end', onend);
 }
+
+// the no-half-open enforcer
+function onend() {
+  // if we allow half-open state, or if the writable side ended,
+  // then we're ok.
+  if (this.allowHalfOpen || this._writableState.ended) return;
+
+  // no more data can be written.
+  // But allow more writes to happen in this tick.
+  processNextTick(onEndNT, this);
+}
+
+function onEndNT(self) {
+  self.end();
+}
+
+function forEach(xs, f) {
+  for (var i = 0, l = xs.length; i < l; i++) {
+    f(xs[i], i);
+  }
+}
+},{"./_stream_readable":224,"./_stream_writable":226,"core-util-is":206,"inherits":209,"process-nextick-args":221}],224:[function(require,module,exports){
+(function (process){
+'use strict';
+
+module.exports = Readable;
+
+/*<replacement>*/
+var processNextTick = require('process-nextick-args');
+/*</replacement>*/
+
+/*<replacement>*/
+var isArray = require('isarray');
+/*</replacement>*/
+
+/*<replacement>*/
+var Duplex;
+/*</replacement>*/
+
+Readable.ReadableState = ReadableState;
+
+/*<replacement>*/
+var EE = require('events').EventEmitter;
+
+var EElistenerCount = function (emitter, type) {
+  return emitter.listeners(type).length;
+};
+/*</replacement>*/
+
+/*<replacement>*/
+var Stream;
+(function () {
+  try {
+    Stream = require('st' + 'ream');
+  } catch (_) {} finally {
+    if (!Stream) Stream = require('events').EventEmitter;
+  }
+})();
+/*</replacement>*/
+
+var Buffer = require('buffer').Buffer;
+/*<replacement>*/
+var bufferShim = require('buffer-shims');
+/*</replacement>*/
+
+/*<replacement>*/
+var util = require('core-util-is');
+util.inherits = require('inherits');
+/*</replacement>*/
+
+/*<replacement>*/
+var debugUtil = require('util');
+var debug = void 0;
+if (debugUtil && debugUtil.debuglog) {
+  debug = debugUtil.debuglog('stream');
+} else {
+  debug = function () {};
+}
+/*</replacement>*/
+
+var BufferList = require('./internal/streams/BufferList');
+var StringDecoder;
+
+util.inherits(Readable, Stream);
+
+function prependListener(emitter, event, fn) {
+  // Sadly this is not cacheable as some libraries bundle their own
+  // event emitter implementation with them.
+  if (typeof emitter.prependListener === 'function') {
+    return emitter.prependListener(event, fn);
+  } else {
+    // This is a hack to make sure that our error handler is attached before any
+    // userland ones.  NEVER DO THIS. This is here only because this code needs
+    // to continue to work with older versions of Node.js that do not include
+    // the prependListener() method. The goal is to eventually remove this hack.
+    if (!emitter._events || !emitter._events[event]) emitter.on(event, fn);else if (isArray(emitter._events[event])) emitter._events[event].unshift(fn);else emitter._events[event] = [fn, emitter._events[event]];
+  }
+}
+
+function ReadableState(options, stream) {
+  Duplex = Duplex || require('./_stream_duplex');
+
+  options = options || {};
+
+  // object stream flag. Used to make read(n) ignore n and to
+  // make all the buffer merging and length checks go away
+  this.objectMode = !!options.objectMode;
+
+  if (stream instanceof Duplex) this.objectMode = this.objectMode || !!options.readableObjectMode;
+
+  // the point at which it stops calling _read() to fill the buffer
+  // Note: 0 is a valid value, means "don't call _read preemptively ever"
+  var hwm = options.highWaterMark;
+  var defaultHwm = this.objectMode ? 16 : 16 * 1024;
+  this.highWaterMark = hwm || hwm === 0 ? hwm : defaultHwm;
+
+  // cast to ints.
+  this.highWaterMark = ~ ~this.highWaterMark;
+
+  // A linked list is used to store data chunks instead of an array because the
+  // linked list can remove elements from the beginning faster than
+  // array.shift()
+  this.buffer = new BufferList();
+  this.length = 0;
+  this.pipes = null;
+  this.pipesCount = 0;
+  this.flowing = null;
+  this.ended = false;
+  this.endEmitted = false;
+  this.reading = false;
+
+  // a flag to be able to tell if the onwrite cb is called immediately,
+  // or on a later tick.  We set this to true at first, because any
+  // actions that shouldn't happen until "later" should generally also
+  // not happen before the first write call.
+  this.sync = true;
+
+  // whenever we return null, then we set a flag to say
+  // that we're awaiting a 'readable' event emission.
+  this.needReadable = false;
+  this.emittedReadable = false;
+  this.readableListening = false;
+  this.resumeScheduled = false;
+
+  // Crypto is kind of old and crusty.  Historically, its default string
+  // encoding is 'binary' so we have to make this configurable.
+  // Everything else in the universe uses 'utf8', though.
+  this.defaultEncoding = options.defaultEncoding || 'utf8';
+
+  // when piping, we only care about 'readable' events that happen
+  // after read()ing all the bytes and not getting any pushback.
+  this.ranOut = false;
+
+  // the number of writers that are awaiting a drain event in .pipe()s
+  this.awaitDrain = 0;
+
+  // if true, a maybeReadMore has been scheduled
+  this.readingMore = false;
+
+  this.decoder = null;
+  this.encoding = null;
+  if (options.encoding) {
+    if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder;
+    this.decoder = new StringDecoder(options.encoding);
+    this.encoding = options.encoding;
+  }
+}
+
+function Readable(options) {
+  Duplex = Duplex || require('./_stream_duplex');
+
+  if (!(this instanceof Readable)) return new Readable(options);
+
+  this._readableState = new ReadableState(options, this);
+
+  // legacy
+  this.readable = true;
+
+  if (options && typeof options.read === 'function') this._read = options.read;
+
+  Stream.call(this);
+}
+
+// Manually shove something into the read() buffer.
+// This returns true if the highWaterMark has not been hit yet,
+// similar to how Writable.write() returns true if you should
+// write() some more.
+Readable.prototype.push = function (chunk, encoding) {
+  var state = this._readableState;
+
+  if (!state.objectMode && typeof chunk === 'string') {
+    encoding = encoding || state.defaultEncoding;
+    if (encoding !== state.encoding) {
+      chunk = bufferShim.from(chunk, encoding);
+      encoding = '';
+    }
+  }
+
+  return readableAddChunk(this, state, chunk, encoding, false);
+};
+
+// Unshift should *always* be something directly out of read()
+Readable.prototype.unshift = function (chunk) {
+  var state = this._readableState;
+  return readableAddChunk(this, state, chunk, '', true);
+};
+
+Readable.prototype.isPaused = function () {
+  return this._readableState.flowing === false;
+};
+
+function readableAddChunk(stream, state, chunk, encoding, addToFront) {
+  var er = chunkInvalid(state, chunk);
+  if (er) {
+    stream.emit('error', er);
+  } else if (chunk === null) {
+    state.reading = false;
+    onEofChunk(stream, state);
+  } else if (state.objectMode || chunk && chunk.length > 0) {
+    if (state.ended && !addToFront) {
+      var e = new Error('stream.push() after EOF');
+      stream.emit('error', e);
+    } else if (state.endEmitted && addToFront) {
+      var _e = new Error('stream.unshift() after end event');
+      stream.emit('error', _e);
+    } else {
+      var skipAdd;
+      if (state.decoder && !addToFront && !encoding) {
+        chunk = state.decoder.write(chunk);
+        skipAdd = !state.objectMode && chunk.length === 0;
+      }
+
+      if (!addToFront) state.reading = false;
+
+      // Don't add to the buffer if we've decoded to an empty string chunk and
+      // we're not in object mode
+      if (!skipAdd) {
+        // if we want the data now, just emit it.
+        if (state.flowing && state.length === 0 && !state.sync) {
+          stream.emit('data', chunk);
+          stream.read(0);
+        } else {
+          // update the buffer info.
+          state.length += state.objectMode ? 1 : chunk.length;
+          if (addToFront) state.buffer.unshift(chunk);else state.buffer.push(chunk);
+
+          if (state.needReadable) emitReadable(stream);
+        }
+      }
+
+      maybeReadMore(stream, state);
+    }
+  } else if (!addToFront) {
+    state.reading = false;
+  }
+
+  return needMoreData(state);
+}
+
+// if it's past the high water mark, we can push in some more.
+// Also, if we have no data yet, we can stand some
+// more bytes.  This is to work around cases where hwm=0,
+// such as the repl.  Also, if the push() triggered a
+// readable event, and the user called read(largeNumber) such that
+// needReadable was set, then we ought to push more, so that another
+// 'readable' event will be triggered.
+function needMoreData(state) {
+  return !state.ended && (state.needReadable || state.length < state.highWaterMark || state.length === 0);
+}
+
+// backwards compatibility.
+Readable.prototype.setEncoding = function (enc) {
+  if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder;
+  this._readableState.decoder = new StringDecoder(enc);
+  this._readableState.encoding = enc;
+  return this;
+};
+
+// Don't raise the hwm > 8MB
+var MAX_HWM = 0x800000;
+function computeNewHighWaterMark(n) {
+  if (n >= MAX_HWM) {
+    n = MAX_HWM;
+  } else {
+    // Get the next highest power of 2 to prevent increasing hwm excessively in
+    // tiny amounts
+    n--;
+    n |= n >>> 1;
+    n |= n >>> 2;
+    n |= n >>> 4;
+    n |= n >>> 8;
+    n |= n >>> 16;
+    n++;
+  }
+  return n;
+}
+
+// This function is designed to be inlinable, so please take care when making
+// changes to the function body.
+function howMuchToRead(n, state) {
+  if (n <= 0 || state.length === 0 && state.ended) return 0;
+  if (state.objectMode) return 1;
+  if (n !== n) {
+    // Only flow one buffer at a time
+    if (state.flowing && state.length) return state.buffer.head.data.length;else return state.length;
+  }
+  // If we're asking for more than the current hwm, then raise the hwm.
+  if (n > state.highWaterMark) state.highWaterMark = computeNewHighWaterMark(n);
+  if (n <= state.length) return n;
+  // Don't have enough
+  if (!state.ended) {
+    state.needReadable = true;
+    return 0;
+  }
+  return state.length;
+}
+
+// you can override either this method, or the async _read(n) below.
+Readable.prototype.read = function (n) {
+  debug('read', n);
+  n = parseInt(n, 10);
+  var state = this._readableState;
+  var nOrig = n;
+
+  if (n !== 0) state.emittedReadable = false;
+
+  // if we're doing read(0) to trigger a readable event, but we
+  // already have a bunch of data in the buffer, then just trigger
+  // the 'readable' event and move on.
+  if (n === 0 && state.needReadable && (state.length >= state.highWaterMark || state.ended)) {
+    debug('read: emitReadable', state.length, state.ended);
+    if (state.length === 0 && state.ended) endReadable(this);else emitReadable(this);
+    return null;
+  }
+
+  n = howMuchToRead(n, state);
+
+  // if we've ended, and we're now clear, then finish it up.
+  if (n === 0 && state.ended) {
+    if (state.length === 0) endReadable(this);
+    return null;
+  }
+
+  // All the actual chunk generation logic needs to be
+  // *below* the call to _read.  The reason is that in certain
+  // synthetic stream cases, such as passthrough streams, _read
+  // may be a completely synchronous operation which may change
+  // the state of the read buffer, providing enough data when
+  // before there was *not* enough.
+  //
+  // So, the steps are:
+  // 1. Figure out what the state of things will be after we do
+  // a read from the buffer.
+  //
+  // 2. If that resulting state will trigger a _read, then call _read.
+  // Note that this may be asynchronous, or synchronous.  Yes, it is
+  // deeply ugly to write APIs this way, but that still doesn't mean
+  // that the Readable class should behave improperly, as streams are
+  // designed to be sync/async agnostic.
+  // Take note if the _read call is sync or async (ie, if the read call
+  // has returned yet), so that we know whether or not it's safe to emit
+  // 'readable' etc.
+  //
+  // 3. Actually pull the requested chunks out of the buffer and return.
+
+  // if we need a readable event, then we need to do some reading.
+  var doRead = state.needReadable;
+  debug('need readable', doRead);
+
+  // if we currently have less than the highWaterMark, then also read some
+  if (state.length === 0 || state.length - n < state.highWaterMark) {
+    doRead = true;
+    debug('length less than watermark', doRead);
+  }
+
+  // however, if we've ended, then there's no point, and if we're already
+  // reading, then it's unnecessary.
+  if (state.ended || state.reading) {
+    doRead = false;
+    debug('reading or ended', doRead);
+  } else if (doRead) {
+    debug('do read');
+    state.reading = true;
+    state.sync = true;
+    // if the length is currently zero, then we *need* a readable event.
+    if (state.length === 0) state.needReadable = true;
+    // call internal read method
+    this._read(state.highWaterMark);
+    state.sync = false;
+    // If _read pushed data synchronously, then `reading` will be false,
+    // and we need to re-evaluate how much data we can return to the user.
+    if (!state.reading) n = howMuchToRead(nOrig, state);
+  }
+
+  var ret;
+  if (n > 0) ret = fromList(n, state);else ret = null;
+
+  if (ret === null) {
+    state.needReadable = true;
+    n = 0;
+  } else {
+    state.length -= n;
+  }
+
+  if (state.length === 0) {
+    // If we have nothing in the buffer, then we want to know
+    // as soon as we *do* get something into the buffer.
+    if (!state.ended) state.needReadable = true;
+
+    // If we tried to read() past the EOF, then emit end on the next tick.
+    if (nOrig !== n && state.ended) endReadable(this);
+  }
+
+  if (ret !== null) this.emit('data', ret);
+
+  return ret;
+};
+
+function chunkInvalid(state, chunk) {
+  var er = null;
+  if (!Buffer.isBuffer(chunk) && typeof chunk !== 'string' && chunk !== null && chunk !== undefined && !state.objectMode) {
+    er = new TypeError('Invalid non-string/buffer chunk');
+  }
+  return er;
+}
+
+function onEofChunk(stream, state) {
+  if (state.ended) return;
+  if (state.decoder) {
+    var chunk = state.decoder.end();
+    if (chunk && chunk.length) {
+      state.buffer.push(chunk);
+      state.length += state.objectMode ? 1 : chunk.length;
+    }
+  }
+  state.ended = true;
+
+  // emit 'readable' now to make sure it gets picked up.
+  emitReadable(stream);
+}
+
+// Don't emit readable right away in sync mode, because this can trigger
+// another read() call => stack overflow.  This way, it might trigger
+// a nextTick recursion warning, but that's not so bad.
+function emitReadable(stream) {
+  var state = stream._readableState;
+  state.needReadable = false;
+  if (!state.emittedReadable) {
+    debug('emitReadable', state.flowing);
+    state.emittedReadable = true;
+    if (state.sync) processNextTick(emitReadable_, stream);else emitReadable_(stream);
+  }
+}
+
+function emitReadable_(stream) {
+  debug('emit readable');
+  stream.emit('readable');
+  flow(stream);
+}
+
+// at this point, the user has presumably seen the 'readable' event,
+// and called read() to consume some data.  that may have triggered
+// in turn another _read(n) call, in which case reading = true if
+// it's in progress.
+// However, if we're not ended, or reading, and the length < hwm,
+// then go ahead and try to read some more preemptively.
+function maybeReadMore(stream, state) {
+  if (!state.readingMore) {
+    state.readingMore = true;
+    processNextTick(maybeReadMore_, stream, state);
+  }
+}
+
+function maybeReadMore_(stream, state) {
+  var len = state.length;
+  while (!state.reading && !state.flowing && !state.ended && state.length < state.highWaterMark) {
+    debug('maybeReadMore read 0');
+    stream.read(0);
+    if (len === state.length)
+      // didn't get any data, stop spinning.
+      break;else len = state.length;
+  }
+  state.readingMore = false;
+}
+
+// abstract method.  to be overridden in specific implementation classes.
+// call cb(er, data) where data is <= n in length.
+// for virtual (non-string, non-buffer) streams, "length" is somewhat
+// arbitrary, and perhaps not very meaningful.
+Readable.prototype._read = function (n) {
+  this.emit('error', new Error('_read() is not implemented'));
+};
+
+Readable.prototype.pipe = function (dest, pipeOpts) {
+  var src = this;
+  var state = this._readableState;
+
+  switch (state.pipesCount) {
+    case 0:
+      state.pipes = dest;
+      break;
+    case 1:
+      state.pipes = [state.pipes, dest];
+      break;
+    default:
+      state.pipes.push(dest);
+      break;
+  }
+  state.pipesCount += 1;
+  debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts);
+
+  var doEnd = (!pipeOpts || pipeOpts.end !== false) && dest !== process.stdout && dest !== process.stderr;
+
+  var endFn = doEnd ? onend : cleanup;
+  if (state.endEmitted) processNextTick(endFn);else src.once('end', endFn);
+
+  dest.on('unpipe', onunpipe);
+  function onunpipe(readable) {
+    debug('onunpipe');
+    if (readable === src) {
+      cleanup();
+    }
+  }
+
+  function onend() {
+    debug('onend');
+    dest.end();
+  }
+
+  // when the dest drains, it reduces the awaitDrain counter
+  // on the source.  This would be more elegant with a .once()
+  // handler in flow(), but adding and removing repeatedly is
+  // too slow.
+  var ondrain = pipeOnDrain(src);
+  dest.on('drain', ondrain);
+
+  var cleanedUp = false;
+  function cleanup() {
+    debug('cleanup');
+    // cleanup event handlers once the pipe is broken
+    dest.removeListener('close', onclose);
+    dest.removeListener('finish', onfinish);
+    dest.removeListener('drain', ondrain);
+    dest.removeListener('error', onerror);
+    dest.removeListener('unpipe', onunpipe);
+    src.removeListener('end', onend);
+    src.removeListener('end', cleanup);
+    src.removeListener('data', ondata);
+
+    cleanedUp = true;
+
+    // if the reader is waiting for a drain event from this
+    // specific writer, then it would cause it to never start
+    // flowing again.
+    // So, if this is awaiting a drain, then we just call it now.
+    // If we don't know, then assume that we are waiting for one.
+    if (state.awaitDrain && (!dest._writableState || dest._writableState.needDrain)) ondrain();
+  }
+
+  // If the user pushes more data while we're writing to dest then we'll end up
+  // in ondata again. However, we only want to increase awaitDrain once because
+  // dest will only emit one 'drain' event for the multiple writes.
+  // => Introduce a guard on increasing awaitDrain.
+  var increasedAwaitDrain = false;
+  src.on('data', ondata);
+  function ondata(chunk) {
+    debug('ondata');
+    increasedAwaitDrain = false;
+    var ret = dest.write(chunk);
+    if (false === ret && !increasedAwaitDrain) {
+      // If the user unpiped during `dest.write()`, it is possible
+      // to get stuck in a permanently paused state if that write
+      // also returned false.
+      // => Check whether `dest` is still a piping destination.
+      if ((state.pipesCount === 1 && state.pipes === dest || state.pipesCount > 1 && indexOf(state.pipes, dest) !== -1) && !cleanedUp) {
+        debug('false write response, pause', src._readableState.awaitDrain);
+        src._readableState.awaitDrain++;
+        increasedAwaitDrain = true;
+      }
+      src.pause();
+    }
+  }
+
+  // if the dest has an error, then stop piping into it.
+  // however, don't suppress the throwing behavior for this.
+  function onerror(er) {
+    debug('onerror', er);
+    unpipe();
+    dest.removeListener('error', onerror);
+    if (EElistenerCount(dest, 'error') === 0) dest.emit('error', er);
+  }
+
+  // Make sure our error handler is attached before userland ones.
+  prependListener(dest, 'error', onerror);
+
+  // Both close and finish should trigger unpipe, but only once.
+  function onclose() {
+    dest.removeListener('finish', onfinish);
+    unpipe();
+  }
+  dest.once('close', onclose);
+  function onfinish() {
+    debug('onfinish');
+    dest.removeListener('close', onclose);
+    unpipe();
+  }
+  dest.once('finish', onfinish);
+
+  function unpipe() {
+    debug('unpipe');
+    src.unpipe(dest);
+  }
+
+  // tell the dest that it's being piped to
+  dest.emit('pipe', src);
+
+  // start the flow if it hasn't been started already.
+  if (!state.flowing) {
+    debug('pipe resume');
+    src.resume();
+  }
+
+  return dest;
+};
+
+function pipeOnDrain(src) {
+  return function () {
+    var state = src._readableState;
+    debug('pipeOnDrain', state.awaitDrain);
+    if (state.awaitDrain) state.awaitDrain--;
+    if (state.awaitDrain === 0 && EElistenerCount(src, 'data')) {
+      state.flowing = true;
+      flow(src);
+    }
+  };
+}
+
+Readable.prototype.unpipe = function (dest) {
+  var state = this._readableState;
+
+  // if we're not piping anywhere, then do nothing.
+  if (state.pipesCount === 0) return this;
+
+  // just one destination.  most common case.
+  if (state.pipesCount === 1) {
+    // passed in one, but it's not the right one.
+    if (dest && dest !== state.pipes) return this;
+
+    if (!dest) dest = state.pipes;
+
+    // got a match.
+    state.pipes = null;
+    state.pipesCount = 0;
+    state.flowing = false;
+    if (dest) dest.emit('unpipe', this);
+    return this;
+  }
+
+  // slow case. multiple pipe destinations.
+
+  if (!dest) {
+    // remove all.
+    var dests = state.pipes;
+    var len = state.pipesCount;
+    state.pipes = null;
+    state.pipesCount = 0;
+    state.flowing = false;
+
+    for (var i = 0; i < len; i++) {
+      dests[i].emit('unpipe', this);
+    }return this;
+  }
+
+  // try to find the right one.
+  var index = indexOf(state.pipes, dest);
+  if (index === -1) return this;
+
+  state.pipes.splice(index, 1);
+  state.pipesCount -= 1;
+  if (state.pipesCount === 1) state.pipes = state.pipes[0];
+
+  dest.emit('unpipe', this);
+
+  return this;
+};
+
+// set up data events if they are asked for
+// Ensure readable listeners eventually get something
+Readable.prototype.on = function (ev, fn) {
+  var res = Stream.prototype.on.call(this, ev, fn);
+
+  if (ev === 'data') {
+    // Start flowing on next tick if stream isn't explicitly paused
+    if (this._readableState.flowing !== false) this.resume();
+  } else if (ev === 'readable') {
+    var state = this._readableState;
+    if (!state.endEmitted && !state.readableListening) {
+      state.readableListening = state.needReadable = true;
+      state.emittedReadable = false;
+      if (!state.reading) {
+        processNextTick(nReadingNextTick, this);
+      } else if (state.length) {
+        emitReadable(this, state);
+      }
+    }
+  }
+
+  return res;
+};
+Readable.prototype.addListener = Readable.prototype.on;
+
+function nReadingNextTick(self) {
+  debug('readable nexttick read 0');
+  self.read(0);
+}
+
+// pause() and resume() are remnants of the legacy readable stream API
+// If the user uses them, then switch into old mode.
+Readable.prototype.resume = function () {
+  var state = this._readableState;
+  if (!state.flowing) {
+    debug('resume');
+    state.flowing = true;
+    resume(this, state);
+  }
+  return this;
+};
+
+function resume(stream, state) {
+  if (!state.resumeScheduled) {
+    state.resumeScheduled = true;
+    processNextTick(resume_, stream, state);
+  }
+}
+
+function resume_(stream, state) {
+  if (!state.reading) {
+    debug('resume read 0');
+    stream.read(0);
+  }
+
+  state.resumeScheduled = false;
+  state.awaitDrain = 0;
+  stream.emit('resume');
+  flow(stream);
+  if (state.flowing && !state.reading) stream.read(0);
+}
+
+Readable.prototype.pause = function () {
+  debug('call pause flowing=%j', this._readableState.flowing);
+  if (false !== this._readableState.flowing) {
+    debug('pause');
+    this._readableState.flowing = false;
+    this.emit('pause');
+  }
+  return this;
+};
+
+function flow(stream) {
+  var state = stream._readableState;
+  debug('flow', state.flowing);
+  while (state.flowing && stream.read() !== null) {}
+}
+
+// wrap an old-style stream as the async data source.
+// This is *not* part of the readable stream interface.
+// It is an ugly unfortunate mess of history.
+Readable.prototype.wrap = function (stream) {
+  var state = this._readableState;
+  var paused = false;
+
+  var self = this;
+  stream.on('end', function () {
+    debug('wrapped end');
+    if (state.decoder && !state.ended) {
+      var chunk = state.decoder.end();
+      if (chunk && chunk.length) self.push(chunk);
+    }
+
+    self.push(null);
+  });
+
+  stream.on('data', function (chunk) {
+    debug('wrapped data');
+    if (state.decoder) chunk = state.decoder.write(chunk);
+
+    // don't skip over falsy values in objectMode
+    if (state.objectMode && (chunk === null || chunk === undefined)) return;else if (!state.objectMode && (!chunk || !chunk.length)) return;
+
+    var ret = self.push(chunk);
+    if (!ret) {
+      paused = true;
+      stream.pause();
+    }
+  });
+
+  // proxy all the other methods.
+  // important when wrapping filters and duplexes.
+  for (var i in stream) {
+    if (this[i] === undefined && typeof stream[i] === 'function') {
+      this[i] = function (method) {
+        return function () {
+          return stream[method].apply(stream, arguments);
+        };
+      }(i);
+    }
+  }
+
+  // proxy certain important events.
+  var events = ['error', 'close', 'destroy', 'pause', 'resume'];
+  forEach(events, function (ev) {
+    stream.on(ev, self.emit.bind(self, ev));
+  });
+
+  // when we try to consume some more bytes, simply unpause the
+  // underlying stream.
+  self._read = function (n) {
+    debug('wrapped _read', n);
+    if (paused) {
+      paused = false;
+      stream.resume();
+    }
+  };
+
+  return self;
+};
+
+// exposed for testing purposes only.
+Readable._fromList = fromList;
+
+// Pluck off n bytes from an array of buffers.
+// Length is the combined lengths of all the buffers in the list.
+// This function is designed to be inlinable, so please take care when making
+// changes to the function body.
+function fromList(n, state) {
+  // nothing buffered
+  if (state.length === 0) return null;
+
+  var ret;
+  if (state.objectMode) ret = state.buffer.shift();else if (!n || n >= state.length) {
+    // read it all, truncate the list
+    if (state.decoder) ret = state.buffer.join('');else if (state.buffer.length === 1) ret = state.buffer.head.data;else ret = state.buffer.concat(state.length);
+    state.buffer.clear();
+  } else {
+    // read part of list
+    ret = fromListPartial(n, state.buffer, state.decoder);
+  }
+
+  return ret;
+}
+
+// Extracts only enough buffered data to satisfy the amount requested.
+// This function is designed to be inlinable, so please take care when making
+// changes to the function body.
+function fromListPartial(n, list, hasStrings) {
+  var ret;
+  if (n < list.head.data.length) {
+    // slice is the same for buffers and strings
+    ret = list.head.data.slice(0, n);
+    list.head.data = list.head.data.slice(n);
+  } else if (n === list.head.data.length) {
+    // first chunk is a perfect match
+    ret = list.shift();
+  } else {
+    // result spans more than one buffer
+    ret = hasStrings ? copyFromBufferString(n, list) : copyFromBuffer(n, list);
+  }
+  return ret;
+}
+
+// Copies a specified amount of characters from the list of buffered data
+// chunks.
+// This function is designed to be inlinable, so please take care when making
+// changes to the function body.
+function copyFromBufferString(n, list) {
+  var p = list.head;
+  var c = 1;
+  var ret = p.data;
+  n -= ret.length;
+  while (p = p.next) {
+    var str = p.data;
+    var nb = n > str.length ? str.length : n;
+    if (nb === str.length) ret += str;else ret += str.slice(0, n);
+    n -= nb;
+    if (n === 0) {
+      if (nb === str.length) {
+        ++c;
+        if (p.next) list.head = p.next;else list.head = list.tail = null;
+      } else {
+        list.head = p;
+        p.data = str.slice(nb);
+      }
+      break;
+    }
+    ++c;
+  }
+  list.length -= c;
+  return ret;
+}
+
+// Copies a specified amount of bytes from the list of buffered data chunks.
+// This function is designed to be inlinable, so please take care when making
+// changes to the function body.
+function copyFromBuffer(n, list) {
+  var ret = bufferShim.allocUnsafe(n);
+  var p = list.head;
+  var c = 1;
+  p.data.copy(ret);
+  n -= p.data.length;
+  while (p = p.next) {
+    var buf = p.data;
+    var nb = n > buf.length ? buf.length : n;
+    buf.copy(ret, ret.length - n, 0, nb);
+    n -= nb;
+    if (n === 0) {
+      if (nb === buf.length) {
+        ++c;
+        if (p.next) list.head = p.next;else list.head = list.tail = null;
+      } else {
+        list.head = p;
+        p.data = buf.slice(nb);
+      }
+      break;
+    }
+    ++c;
+  }
+  list.length -= c;
+  return ret;
+}
+
+function endReadable(stream) {
+  var state = stream._readableState;
+
+  // If we get here before consuming all the bytes, then that is a
+  // bug in node.  Should never happen.
+  if (state.length > 0) throw new Error('"endReadable()" called on non-empty stream');
+
+  if (!state.endEmitted) {
+    state.ended = true;
+    processNextTick(endReadableNT, state, stream);
+  }
+}
+
+function endReadableNT(state, stream) {
+  // Check that we didn't get one last unshift.
+  if (!state.endEmitted && state.length === 0) {
+    state.endEmitted = true;
+    stream.readable = false;
+    stream.emit('end');
+  }
+}
+
+function forEach(xs, f) {
+  for (var i = 0, l = xs.length; i < l; i++) {
+    f(xs[i], i);
+  }
+}
+
+function indexOf(xs, x) {
+  for (var i = 0, l = xs.length; i < l; i++) {
+    if (xs[i] === x) return i;
+  }
+  return -1;
+}
+}).call(this,require('_process'))
+},{"./_stream_duplex":223,"./internal/streams/BufferList":227,"_process":222,"buffer":205,"buffer-shims":204,"core-util-is":206,"events":207,"inherits":209,"isarray":211,"process-nextick-args":221,"string_decoder/":229,"util":200}],225:[function(require,module,exports){
+// a transform stream is a readable/writable stream where you do
+// something with the data.  Sometimes it's called a "filter",
+// but that's not a great name for it, since that implies a thing where
+// some bits pass through, and others are simply ignored.  (That would
+// be a valid example of a transform, of course.)
+//
+// While the output is causally related to the input, it's not a
+// necessarily symmetric or synchronous transformation.  For example,
+// a zlib stream might take multiple plain-text writes(), and then
+// emit a single compressed chunk some time in the future.
+//
+// Here's how this works:
+//
+// The Transform stream has all the aspects of the readable and writable
+// stream classes.  When you write(chunk), that calls _write(chunk,cb)
+// internally, and returns false if there's a lot of pending writes
+// buffered up.  When you call read(), that calls _read(n) until
+// there's enough pending readable data buffered up.
+//
+// In a transform stream, the written data is placed in a buffer.  When
+// _read(n) is called, it transforms the queued up data, calling the
+// buffered _write cb's as it consumes chunks.  If consuming a single
+// written chunk would result in multiple output chunks, then the first
+// outputted bit calls the readcb, and subsequent chunks just go into
+// the read buffer, and will cause it to emit 'readable' if necessary.
+//
+// This way, back-pressure is actually determined by the reading side,
+// since _read has to be called to start processing a new chunk.  However,
+// a pathological inflate type of transform can cause excessive buffering
+// here.  For example, imagine a stream where every byte of input is
+// interpreted as an integer from 0-255, and then results in that many
+// bytes of output.  Writing the 4 bytes {ff,ff,ff,ff} would result in
+// 1kb of data being output.  In this case, you could write a very small
+// amount of input, and end up with a very large amount of output.  In
+// such a pathological inflating mechanism, there'd be no way to tell
+// the system to stop doing the transform.  A single 4MB write could
+// cause the system to run out of memory.
+//
+// However, even in such a pathological case, only a single written chunk
+// would be consumed, and then the rest would wait (un-transformed) until
+// the results of the previous transformed chunk were consumed.
+
+'use strict';
+
+module.exports = Transform;
+
+var Duplex = require('./_stream_duplex');
+
+/*<replacement>*/
+var util = require('core-util-is');
+util.inherits = require('inherits');
+/*</replacement>*/
+
+util.inherits(Transform, Duplex);
+
+function TransformState(stream) {
+  this.afterTransform = function (er, data) {
+    return afterTransform(stream, er, data);
+  };
+
+  this.needTransform = false;
+  this.transforming = false;
+  this.writecb = null;
+  this.writechunk = null;
+  this.writeencoding = null;
+}
+
+function afterTransform(stream, er, data) {
+  var ts = stream._transformState;
+  ts.transforming = false;
+
+  var cb = ts.writecb;
+
+  if (!cb) return stream.emit('error', new Error('no writecb in Transform class'));
+
+  ts.writechunk = null;
+  ts.writecb = null;
+
+  if (data !== null && data !== undefined) stream.push(data);
+
+  cb(er);
+
+  var rs = stream._readableState;
+  rs.reading = false;
+  if (rs.needReadable || rs.length < rs.highWaterMark) {
+    stream._read(rs.highWaterMark);
+  }
+}
+
+function Transform(options) {
+  if (!(this instanceof Transform)) return new Transform(options);
+
+  Duplex.call(this, options);
+
+  this._transformState = new TransformState(this);
+
+  var stream = this;
+
+  // start out asking for a readable event once data is transformed.
+  this._readableState.needReadable = true;
+
+  // we have implemented the _read method, and done the other things
+  // that Readable wants before the first _read call, so unset the
+  // sync guard flag.
+  this._readableState.sync = false;
+
+  if (options) {
+    if (typeof options.transform === 'function') this._transform = options.transform;
+
+    if (typeof options.flush === 'function') this._flush = options.flush;
+  }
+
+  // When the writable side finishes, then flush out anything remaining.
+  this.once('prefinish', function () {
+    if (typeof this._flush === 'function') this._flush(function (er, data) {
+      done(stream, er, data);
+    });else done(stream);
+  });
+}
+
+Transform.prototype.push = function (chunk, encoding) {
+  this._transformState.needTransform = false;
+  return Duplex.prototype.push.call(this, chunk, encoding);
+};
+
+// This is the part where you do stuff!
+// override this function in implementation classes.
+// 'chunk' is an input chunk.
+//
+// Call `push(newChunk)` to pass along transformed output
+// to the readable side.  You may call 'push' zero or more times.
+//
+// Call `cb(err)` when you are done with this chunk.  If you pass
+// an error, then that'll put the hurt on the whole operation.  If you
+// never call cb(), then you'll never get another chunk.
+Transform.prototype._transform = function (chunk, encoding, cb) {
+  throw new Error('_transform() is not implemented');
+};
+
+Transform.prototype._write = function (chunk, encoding, cb) {
+  var ts = this._transformState;
+  ts.writecb = cb;
+  ts.writechunk = chunk;
+  ts.writeencoding = encoding;
+  if (!ts.transforming) {
+    var rs = this._readableState;
+    if (ts.needTransform || rs.needReadable || rs.length < rs.highWaterMark) this._read(rs.highWaterMark);
+  }
+};
+
+// Doesn't matter what the args are here.
+// _transform does all the work.
+// That we got here means that the readable side wants more data.
+Transform.prototype._read = function (n) {
+  var ts = this._transformState;
+
+  if (ts.writechunk !== null && ts.writecb && !ts.transforming) {
+    ts.transforming = true;
+    this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform);
+  } else {
+    // mark that we need a transform, so that any data that comes in
+    // will get processed, now that we've asked for it.
+    ts.needTransform = true;
+  }
+};
+
+function done(stream, er, data) {
+  if (er) return stream.emit('error', er);
+
+  if (data !== null && data !== undefined) stream.push(data);
+
+  // if there's nothing in the write buffer, then that means
+  // that nothing more will ever be provided
+  var ws = stream._writableState;
+  var ts = stream._transformState;
+
+  if (ws.length) throw new Error('Calling transform done when ws.length != 0');
+
+  if (ts.transforming) throw new Error('Calling transform done when still transforming');
+
+  return stream.push(null);
+}
+},{"./_stream_duplex":223,"core-util-is":206,"inherits":209}],226:[function(require,module,exports){
+(function (process){
+// A bit simpler than readable streams.
+// Implement an async ._write(chunk, encoding, cb), and it'll handle all
+// the drain event emission and buffering.
+
+'use strict';
+
+module.exports = Writable;
+
+/*<replacement>*/
+var processNextTick = require('process-nextick-args');
+/*</replacement>*/
+
+/*<replacement>*/
+var asyncWrite = !process.browser && ['v0.10', 'v0.9.'].indexOf(process.version.slice(0, 5)) > -1 ? setImmediate : processNextTick;
+/*</replacement>*/
+
+/*<replacement>*/
+var Duplex;
+/*</replacement>*/
+
+Writable.WritableState = WritableState;
+
+/*<replacement>*/
+var util = require('core-util-is');
+util.inherits = require('inherits');
+/*</replacement>*/
+
+/*<replacement>*/
+var internalUtil = {
+  deprecate: require('util-deprecate')
+};
+/*</replacement>*/
+
+/*<replacement>*/
+var Stream;
+(function () {
+  try {
+    Stream = require('st' + 'ream');
+  } catch (_) {} finally {
+    if (!Stream) Stream = require('events').EventEmitter;
+  }
+})();
+/*</replacement>*/
+
+var Buffer = require('buffer').Buffer;
+/*<replacement>*/
+var bufferShim = require('buffer-shims');
+/*</replacement>*/
+
+util.inherits(Writable, Stream);
+
+function nop() {}
+
+function WriteReq(chunk, encoding, cb) {
+  this.chunk = chunk;
+  this.encoding = encoding;
+  this.callback = cb;
+  this.next = null;
+}
+
+function WritableState(options, stream) {
+  Duplex = Duplex || require('./_stream_duplex');
+
+  options = options || {};
+
+  // object stream flag to indicate whether or not this stream
+  // contains buffers or objects.
+  this.objectMode = !!options.objectMode;
+
+  if (stream instanceof Duplex) this.objectMode = this.objectMode || !!options.writableObjectMode;
+
+  // the point at which write() starts returning false
+  // Note: 0 is a valid value, means that we always return false if
+  // the entire buffer is not flushed immediately on write()
+  var hwm = options.highWaterMark;
+  var defaultHwm = this.objectMode ? 16 : 16 * 1024;
+  this.highWaterMark = hwm || hwm === 0 ? hwm : defaultHwm;
+
+  // cast to ints.
+  this.highWaterMark = ~ ~this.highWaterMark;
+
+  // drain event flag.
+  this.needDrain = false;
+  // at the start of calling end()
+  this.ending = false;
+  // when end() has been called, and returned
+  this.ended = false;
+  // when 'finish' is emitted
+  this.finished = false;
+
+  // should we decode strings into buffers before passing to _write?
+  // this is here so that some node-core streams can optimize string
+  // handling at a lower level.
+  var noDecode = options.decodeStrings === false;
+  this.decodeStrings = !noDecode;
+
+  // Crypto is kind of old and crusty.  Historically, its default string
+  // encoding is 'binary' so we have to make this configurable.
+  // Everything else in the universe uses 'utf8', though.
+  this.defaultEncoding = options.defaultEncoding || 'utf8';
+
+  // not an actual buffer we keep track of, but a measurement
+  // of how much we're waiting to get pushed to some underlying
+  // socket or file.
+  this.length = 0;
+
+  // a flag to see when we're in the middle of a write.
+  this.writing = false;
+
+  // when true all writes will be buffered until .uncork() call
+  this.corked = 0;
+
+  // a flag to be able to tell if the onwrite cb is called immediately,
+  // or on a later tick.  We set this to true at first, because any
+  // actions that shouldn't happen until "later" should generally also
+  // not happen before the first write call.
+  this.sync = true;
+
+  // a flag to know if we're processing previously buffered items, which
+  // may call the _write() callback in the same tick, so that we don't
+  // end up in an overlapped onwrite situation.
+  this.bufferProcessing = false;
+
+  // the callback that's passed to _write(chunk,cb)
+  this.onwrite = function (er) {
+    onwrite(stream, er);
+  };
+
+  // the callback that the user supplies to write(chunk,encoding,cb)
+  this.writecb = null;
+
+  // the amount that is being written when _write is called.
+  this.writelen = 0;
+
+  this.bufferedRequest = null;
+  this.lastBufferedRequest = null;
+
+  // number of pending user-supplied write callbacks
+  // this must be 0 before 'finish' can be emitted
+  this.pendingcb = 0;
+
+  // emit prefinish if the only thing we're waiting for is _write cbs
+  // This is relevant for synchronous Transform streams
+  this.prefinished = false;
+
+  // True if the error was already emitted and should not be thrown again
+  this.errorEmitted = false;
+
+  // count buffered requests
+  this.bufferedRequestCount = 0;
+
+  // allocate the first CorkedRequest, there is always
+  // one allocated and free to use, and we maintain at most two
+  this.corkedRequestsFree = new CorkedRequest(this);
+}
+
+WritableState.prototype.getBuffer = function getBuffer() {
+  var current = this.bufferedRequest;
+  var out = [];
+  while (current) {
+    out.push(current);
+    current = current.next;
+  }
+  return out;
+};
+
+(function () {
+  try {
+    Object.defineProperty(WritableState.prototype, 'buffer', {
+      get: internalUtil.deprecate(function () {
+        return this.getBuffer();
+      }, '_writableState.buffer is deprecated. Use _writableState.getBuffer ' + 'instead.')
+    });
+  } catch (_) {}
+})();
+
+// Test _writableState for inheritance to account for Duplex streams,
+// whose prototype chain only points to Readable.
+var realHasInstance;
+if (typeof Symbol === 'function' && Symbol.hasInstance && typeof Function.prototype[Symbol.hasInstance] === 'function') {
+  realHasInstance = Function.prototype[Symbol.hasInstance];
+  Object.defineProperty(Writable, Symbol.hasInstance, {
+    value: function (object) {
+      if (realHasInstance.call(this, object)) return true;
+
+      return object && object._writableState instanceof WritableState;
+    }
+  });
+} else {
+  realHasInstance = function (object) {
+    return object instanceof this;
+  };
+}
+
+function Writable(options) {
+  Duplex = Duplex || require('./_stream_duplex');
+
+  // Writable ctor is applied to Duplexes, too.
+  // `realHasInstance` is necessary because using plain `instanceof`
+  // would return false, as no `_writableState` property is attached.
+
+  // Trying to use the custom `instanceof` for Writable here will also break the
+  // Node.js LazyTransform implementation, which has a non-trivial getter for
+  // `_writableState` that would lead to infinite recursion.
+  if (!realHasInstance.call(Writable, this) && !(this instanceof Duplex)) {
+    return new Writable(options);
+  }
+
+  this._writableState = new WritableState(options, this);
+
+  // legacy.
+  this.writable = true;
+
+  if (options) {
+    if (typeof options.write === 'function') this._write = options.write;
+
+    if (typeof options.writev === 'function') this._writev = options.writev;
+  }
+
+  Stream.call(this);
+}
+
+// Otherwise people can pipe Writable streams, which is just wrong.
+Writable.prototype.pipe = function () {
+  this.emit('error', new Error('Cannot pipe, not readable'));
+};
+
+function writeAfterEnd(stream, cb) {
+  var er = new Error('write after end');
+  // TODO: defer error events consistently everywhere, not just the cb
+  stream.emit('error', er);
+  processNextTick(cb, er);
+}
+
+// If we get something that is not a buffer, string, null, or undefined,
+// and we're not in objectMode, then that's an error.
+// Otherwise stream chunks are all considered to be of length=1, and the
+// watermarks determine how many objects to keep in the buffer, rather than
+// how many bytes or characters.
+function validChunk(stream, state, chunk, cb) {
+  var valid = true;
+  var er = false;
+  // Always throw error if a null is written
+  // if we are not in object mode then throw
+  // if it is not a buffer, string, or undefined.
+  if (chunk === null) {
+    er = new TypeError('May not write null values to stream');
+  } else if (!Buffer.isBuffer(chunk) && typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) {
+    er = new TypeError('Invalid non-string/buffer chunk');
+  }
+  if (er) {
+    stream.emit('error', er);
+    processNextTick(cb, er);
+    valid = false;
+  }
+  return valid;
+}
+
+Writable.prototype.write = function (chunk, encoding, cb) {
+  var state = this._writableState;
+  var ret = false;
+
+  if (typeof encoding === 'function') {
+    cb = encoding;
+    encoding = null;
+  }
+
+  if (Buffer.isBuffer(chunk)) encoding = 'buffer';else if (!encoding) encoding = state.defaultEncoding;
+
+  if (typeof cb !== 'function') cb = nop;
+
+  if (state.ended) writeAfterEnd(this, cb);else if (validChunk(this, state, chunk, cb)) {
+    state.pendingcb++;
+    ret = writeOrBuffer(this, state, chunk, encoding, cb);
+  }
+
+  return ret;
+};
+
+Writable.prototype.cork = function () {
+  var state = this._writableState;
+
+  state.corked++;
+};
+
+Writable.prototype.uncork = function () {
+  var state = this._writableState;
+
+  if (state.corked) {
+    state.corked--;
+
+    if (!state.writing && !state.corked && !state.finished && !state.bufferProcessing && state.bufferedRequest) clearBuffer(this, state);
+  }
+};
+
+Writable.prototype.setDefaultEncoding = function setDefaultEncoding(encoding) {
+  // node::ParseEncoding() requires lower case.
+  if (typeof encoding === 'string') encoding = encoding.toLowerCase();
+  if (!(['hex', 'utf8', 'utf-8', 'ascii', 'binary', 'base64', 'ucs2', 'ucs-2', 'utf16le', 'utf-16le', 'raw'].indexOf((encoding + '').toLowerCase()) > -1)) throw new TypeError('Unknown encoding: ' + encoding);
+  this._writableState.defaultEncoding = encoding;
+  return this;
+};
+
+function decodeChunk(state, chunk, encoding) {
+  if (!state.objectMode && state.decodeStrings !== false && typeof chunk === 'string') {
+    chunk = bufferShim.from(chunk, encoding);
+  }
+  return chunk;
+}
+
+// if we're already writing something, then just put this
+// in the queue, and wait our turn.  Otherwise, call _write
+// If we return false, then we need a drain event, so set that flag.
+function writeOrBuffer(stream, state, chunk, encoding, cb) {
+  chunk = decodeChunk(state, chunk, encoding);
+
+  if (Buffer.isBuffer(chunk)) encoding = 'buffer';
+  var len = state.objectMode ? 1 : chunk.length;
+
+  state.length += len;
+
+  var ret = state.length < state.highWaterMark;
+  // we must ensure that previous needDrain will not be reset to false.
+  if (!ret) state.needDrain = true;
+
+  if (state.writing || state.corked) {
+    var last = state.lastBufferedRequest;
+    state.lastBufferedRequest = new WriteReq(chunk, encoding, cb);
+    if (last) {
+      last.next = state.lastBufferedRequest;
+    } else {
+      state.bufferedRequest = state.lastBufferedRequest;
+    }
+    state.bufferedRequestCount += 1;
+  } else {
+    doWrite(stream, state, false, len, chunk, encoding, cb);
+  }
+
+  return ret;
+}
+
+function doWrite(stream, state, writev, len, chunk, encoding, cb) {
+  state.writelen = len;
+  state.writecb = cb;
+  state.writing = true;
+  state.sync = true;
+  if (writev) stream._writev(chunk, state.onwrite);else stream._write(chunk, encoding, state.onwrite);
+  state.sync = false;
+}
+
+function onwriteError(stream, state, sync, er, cb) {
+  --state.pendingcb;
+  if (sync) processNextTick(cb, er);else cb(er);
+
+  stream._writableState.errorEmitted = true;
+  stream.emit('error', er);
+}
+
+function onwriteStateUpdate(state) {
+  state.writing = false;
+  state.writecb = null;
+  state.length -= state.writelen;
+  state.writelen = 0;
+}
+
+function onwrite(stream, er) {
+  var state = stream._writableState;
+  var sync = state.sync;
+  var cb = state.writecb;
+
+  onwriteStateUpdate(state);
+
+  if (er) onwriteError(stream, state, sync, er, cb);else {
+    // Check if we're actually ready to finish, but don't emit yet
+    var finished = needFinish(state);
+
+    if (!finished && !state.corked && !state.bufferProcessing && state.bufferedRequest) {
+      clearBuffer(stream, state);
+    }
+
+    if (sync) {
+      /*<replacement>*/
+      asyncWrite(afterWrite, stream, state, finished, cb);
+      /*</replacement>*/
+    } else {
+        afterWrite(stream, state, finished, cb);
+      }
+  }
+}
+
+function afterWrite(stream, state, finished, cb) {
+  if (!finished) onwriteDrain(stream, state);
+  state.pendingcb--;
+  cb();
+  finishMaybe(stream, state);
+}
+
+// Must force callback to be called on nextTick, so that we don't
+// emit 'drain' before the write() consumer gets the 'false' return
+// value, and has a chance to attach a 'drain' listener.
+function onwriteDrain(stream, state) {
+  if (state.length === 0 && state.needDrain) {
+    state.needDrain = false;
+    stream.emit('drain');
+  }
+}
+
+// if there's something in the buffer waiting, then process it
+function clearBuffer(stream, state) {
+  state.bufferProcessing = true;
+  var entry = state.bufferedRequest;
+
+  if (stream._writev && entry && entry.next) {
+    // Fast case, write everything using _writev()
+    var l = state.bufferedRequestCount;
+    var buffer = new Array(l);
+    var holder = state.corkedRequestsFree;
+    holder.entry = entry;
+
+    var count = 0;
+    while (entry) {
+      buffer[count] = entry;
+      entry = entry.next;
+      count += 1;
+    }
+
+    doWrite(stream, state, true, state.length, buffer, '', holder.finish);
+
+    // doWrite is almost always async, defer these to save a bit of time
+    // as the hot path ends with doWrite
+    state.pendingcb++;
+    state.lastBufferedRequest = null;
+    if (holder.next) {
+      state.corkedRequestsFree = holder.next;
+      holder.next = null;
+    } else {
+      state.corkedRequestsFree = new CorkedRequest(state);
+    }
+  } else {
+    // Slow case, write chunks one-by-one
+    while (entry) {
+      var chunk = entry.chunk;
+      var encoding = entry.encoding;
+      var cb = entry.callback;
+      var len = state.objectMode ? 1 : chunk.length;
+
+      doWrite(stream, state, false, len, chunk, encoding, cb);
+      entry = entry.next;
+      // if we didn't call the onwrite immediately, then
+      // it means that we need to wait until it does.
+      // also, that means that the chunk and cb are currently
+      // being processed, so move the buffer counter past them.
+      if (state.writing) {
+        break;
+      }
+    }
+
+    if (entry === null) state.lastBufferedRequest = null;
+  }
+
+  state.bufferedRequestCount = 0;
+  state.bufferedRequest = entry;
+  state.bufferProcessing = false;
+}
+
+Writable.prototype._write = function (chunk, encoding, cb) {
+  cb(new Error('_write() is not implemented'));
+};
+
+Writable.prototype._writev = null;
+
+Writable.prototype.end = function (chunk, encoding, cb) {
+  var state = this._writableState;
+
+  if (typeof chunk === 'function') {
+    cb = chunk;
+    chunk = null;
+    encoding = null;
+  } else if (typeof encoding === 'function') {
+    cb = encoding;
+    encoding = null;
+  }
+
+  if (chunk !== null && chunk !== undefined) this.write(chunk, encoding);
+
+  // .end() fully uncorks
+  if (state.corked) {
+    state.corked = 1;
+    this.uncork();
+  }
+
+  // ignore unnecessary end() calls.
+  if (!state.ending && !state.finished) endWritable(this, state, cb);
+};
+
+function needFinish(state) {
+  return state.ending && state.length === 0 && state.bufferedRequest === null && !state.finished && !state.writing;
+}
+
+function prefinish(stream, state) {
+  if (!state.prefinished) {
+    state.prefinished = true;
+    stream.emit('prefinish');
+  }
+}
+
+function finishMaybe(stream, state) {
+  var need = needFinish(state);
+  if (need) {
+    if (state.pendingcb === 0) {
+      prefinish(stream, state);
+      state.finished = true;
+      stream.emit('finish');
+    } else {
+      prefinish(stream, state);
+    }
+  }
+  return need;
+}
+
+function endWritable(stream, state, cb) {
+  state.ending = true;
+  finishMaybe(stream, state);
+  if (cb) {
+    if (state.finished) processNextTick(cb);else stream.once('finish', cb);
+  }
+  state.ended = true;
+  stream.writable = false;
+}
+
+// It seems a linked list but it is not
+// there will be only 2 of these for each stream
+function CorkedRequest(state) {
+  var _this = this;
+
+  this.next = null;
+  this.entry = null;
+
+  this.finish = function (err) {
+    var entry = _this.entry;
+    _this.entry = null;
+    while (entry) {
+      var cb = entry.callback;
+      state.pendingcb--;
+      cb(err);
+      entry = entry.next;
+    }
+    if (state.corkedRequestsFree) {
+      state.corkedRequestsFree.next = _this;
+    } else {
+      state.corkedRequestsFree = _this;
+    }
+  };
+}
+}).call(this,require('_process'))
+},{"./_stream_duplex":223,"_process":222,"buffer":205,"buffer-shims":204,"core-util-is":206,"events":207,"inherits":209,"process-nextick-args":221,"util-deprecate":230}],227:[function(require,module,exports){
+'use strict';
+
+var Buffer = require('buffer').Buffer;
+/*<replacement>*/
+var bufferShim = require('buffer-shims');
+/*</replacement>*/
+
+module.exports = BufferList;
+
+function BufferList() {
+  this.head = null;
+  this.tail = null;
+  this.length = 0;
+}
+
+BufferList.prototype.push = function (v) {
+  var entry = { data: v, next: null };
+  if (this.length > 0) this.tail.next = entry;else this.head = entry;
+  this.tail = entry;
+  ++this.length;
+};
+
+BufferList.prototype.unshift = function (v) {
+  var entry = { data: v, next: this.head };
+  if (this.length === 0) this.tail = entry;
+  this.head = entry;
+  ++this.length;
+};
+
+BufferList.prototype.shift = function () {
+  if (this.length === 0) return;
+  var ret = this.head.data;
+  if (this.length === 1) this.head = this.tail = null;else this.head = this.head.next;
+  --this.length;
+  return ret;
+};
+
+BufferList.prototype.clear = function () {
+  this.head = this.tail = null;
+  this.length = 0;
+};
+
+BufferList.prototype.join = function (s) {
+  if (this.length === 0) return '';
+  var p = this.head;
+  var ret = '' + p.data;
+  while (p = p.next) {
+    ret += s + p.data;
+  }return ret;
+};
+
+BufferList.prototype.concat = function (n) {
+  if (this.length === 0) return bufferShim.alloc(0);
+  if (this.length === 1) return this.head.data;
+  var ret = bufferShim.allocUnsafe(n >>> 0);
+  var p = this.head;
+  var i = 0;
+  while (p) {
+    p.data.copy(ret, i);
+    i += p.data.length;
+    p = p.next;
+  }
+  return ret;
+};
+},{"buffer":205,"buffer-shims":204}],228:[function(require,module,exports){
+module.exports = require("./lib/_stream_transform.js")
+
+},{"./lib/_stream_transform.js":225}],229:[function(require,module,exports){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+var Buffer = require('buffer').Buffer;
+
+var isBufferEncoding = Buffer.isEncoding
+  || function(encoding) {
+       switch (encoding && encoding.toLowerCase()) {
+         case 'hex': case 'utf8': case 'utf-8': case 'ascii': case 'binary': case 'base64': case 'ucs2': case 'ucs-2': case 'utf16le': case 'utf-16le': case 'raw': return true;
+         default: return false;
+       }
+     }
+
+
+function assertEncoding(encoding) {
+  if (encoding && !isBufferEncoding(encoding)) {
+    throw new Error('Unknown encoding: ' + encoding);
+  }
+}
+
+// StringDecoder provides an interface for efficiently splitting a series of
+// buffers into a series of JS strings without breaking apart multi-byte
+// characters. CESU-8 is handled as part of the UTF-8 encoding.
+//
+// @TODO Handling all encodings inside a single object makes it very difficult
+// to reason about this code, so it should be split up in the future.
+// @TODO There should be a utf8-strict encoding that rejects invalid UTF-8 code
+// points as used by CESU-8.
+var StringDecoder = exports.StringDecoder = function(encoding) {
+  this.encoding = (encoding || 'utf8').toLowerCase().replace(/[-_]/, '');
+  assertEncoding(encoding);
+  switch (this.encoding) {
+    case 'utf8':
+      // CESU-8 represents each of Surrogate Pair by 3-bytes
+      this.surrogateSize = 3;
+      break;
+    case 'ucs2':
+    case 'utf16le':
+      // UTF-16 represents each of Surrogate Pair by 2-bytes
+      this.surrogateSize = 2;
+      this.detectIncompleteChar = utf16DetectIncompleteChar;
+      break;
+    case 'base64':
+      // Base-64 stores 3 bytes in 4 chars, and pads the remainder.
+      this.surrogateSize = 3;
+      this.detectIncompleteChar = base64DetectIncompleteChar;
+      break;
+    default:
+      this.write = passThroughWrite;
+      return;
+  }
+
+  // Enough space to store all bytes of a single character. UTF-8 needs 4
+  // bytes, but CESU-8 may require up to 6 (3 bytes per surrogate).
+  this.charBuffer = new Buffer(6);
+  // Number of bytes received for the current incomplete multi-byte character.
+  this.charReceived = 0;
+  // Number of bytes expected for the current incomplete multi-byte character.
+  this.charLength = 0;
 };
 
 
-function Item(fun,array){
-this.fun=fun;
-this.array=array;
+// write decodes the given buffer and returns it as JS string that is
+// guaranteed to not contain any partial multi-byte characters. Any partial
+// character found at the end of the buffer is buffered up, and will be
+// returned when calling write again with the remaining bytes.
+//
+// Note: Converting a Buffer containing an orphan surrogate to a String
+// currently works, but converting a String to a Buffer (via `new Buffer`, or
+// Buffer#write) will replace incomplete surrogates with the unicode
+// replacement character. See https://codereview.chromium.org/121173009/ .
+StringDecoder.prototype.write = function(buffer) {
+  var charStr = '';
+  // if our last write ended with an incomplete multibyte character
+  while (this.charLength) {
+    // determine how many remaining bytes this buffer has to offer for this char
+    var available = (buffer.length >= this.charLength - this.charReceived) ?
+        this.charLength - this.charReceived :
+        buffer.length;
+
+    // add the new bytes to the char buffer
+    buffer.copy(this.charBuffer, this.charReceived, 0, available);
+    this.charReceived += available;
+
+    if (this.charReceived < this.charLength) {
+      // still not enough chars in this buffer? wait for more ...
+      return '';
+    }
+
+    // remove bytes belonging to the current character from the buffer
+    buffer = buffer.slice(available, buffer.length);
+
+    // get the character that was split
+    charStr = this.charBuffer.slice(0, this.charLength).toString(this.encoding);
+
+    // CESU-8: lead surrogate (D800-DBFF) is also the incomplete character
+    var charCode = charStr.charCodeAt(charStr.length - 1);
+    if (charCode >= 0xD800 && charCode <= 0xDBFF) {
+      this.charLength += this.surrogateSize;
+      charStr = '';
+      continue;
+    }
+    this.charReceived = this.charLength = 0;
+
+    // if there are no more bytes in this buffer, just emit our char
+    if (buffer.length === 0) {
+      return charStr;
+    }
+    break;
+  }
+
+  // determine and set charLength / charReceived
+  this.detectIncompleteChar(buffer);
+
+  var end = buffer.length;
+  if (this.charLength) {
+    // buffer the incomplete character bytes we got
+    buffer.copy(this.charBuffer, 0, buffer.length - this.charReceived, end);
+    end -= this.charReceived;
+  }
+
+  charStr += buffer.toString(this.encoding, 0, end);
+
+  var end = charStr.length - 1;
+  var charCode = charStr.charCodeAt(end);
+  // CESU-8: lead surrogate (D800-DBFF) is also the incomplete character
+  if (charCode >= 0xD800 && charCode <= 0xDBFF) {
+    var size = this.surrogateSize;
+    this.charLength += size;
+    this.charReceived += size;
+    this.charBuffer.copy(this.charBuffer, size, 0, size);
+    buffer.copy(this.charBuffer, 0, 0, size);
+    return charStr.substring(0, end);
+  }
+
+  // or just emit the charStr
+  return charStr;
+};
+
+// detectIncompleteChar determines if there is an incomplete UTF-8 character at
+// the end of the given buffer. If so, it sets this.charLength to the byte
+// length that character, and sets this.charReceived to the number of bytes
+// that are available for this character.
+StringDecoder.prototype.detectIncompleteChar = function(buffer) {
+  // determine how many bytes we have to check at the end of this buffer
+  var i = (buffer.length >= 3) ? 3 : buffer.length;
+
+  // Figure out if one of the last i bytes of our buffer announces an
+  // incomplete char.
+  for (; i > 0; i--) {
+    var c = buffer[buffer.length - i];
+
+    // See http://en.wikipedia.org/wiki/UTF-8#Description
+
+    // 110XXXXX
+    if (i == 1 && c >> 5 == 0x06) {
+      this.charLength = 2;
+      break;
+    }
+
+    // 1110XXXX
+    if (i <= 2 && c >> 4 == 0x0E) {
+      this.charLength = 3;
+      break;
+    }
+
+    // 11110XXX
+    if (i <= 3 && c >> 3 == 0x1E) {
+      this.charLength = 4;
+      break;
+    }
+  }
+  this.charReceived = i;
+};
+
+StringDecoder.prototype.end = function(buffer) {
+  var res = '';
+  if (buffer && buffer.length)
+    res = this.write(buffer);
+
+  if (this.charReceived) {
+    var cr = this.charReceived;
+    var buf = this.charBuffer;
+    var enc = this.encoding;
+    res += buf.slice(0, cr).toString(enc);
+  }
+
+  return res;
+};
+
+function passThroughWrite(buffer) {
+  return buffer.toString(this.encoding);
 }
-Item.prototype.run=function(){
-this.fun.apply(null,this.array);
-};
-process.title='browser';
-process.browser=true;
-process.env={};
-process.argv=[];
-process.version='';
-process.versions={};
 
-function noop(){}
+function utf16DetectIncompleteChar(buffer) {
+  this.charReceived = buffer.length % 2;
+  this.charLength = this.charReceived ? 2 : 0;
+}
 
-process.on=noop;
-process.addListener=noop;
-process.once=noop;
-process.off=noop;
-process.removeListener=noop;
-process.removeAllListeners=noop;
-process.emit=noop;
+function base64DetectIncompleteChar(buffer) {
+  this.charReceived = buffer.length % 3;
+  this.charLength = this.charReceived ? 3 : 0;
+}
 
-process.binding=function(name){
-throw new Error('process.binding is not supported');
-};
+},{"buffer":205}],230:[function(require,module,exports){
+(function (global){
 
-process.cwd=function(){return'/';};
-process.chdir=function(dir){
-throw new Error('process.chdir is not supported');
-};
-process.umask=function(){return 0;};
+/**
+ * Module exports.
+ */
 
-},{}],205:[function(require,module,exports){
+module.exports = deprecate;
 
+/**
+ * Mark that a method should not be used.
+ * Returns a modified function which warns once by default.
+ *
+ * If `localStorage.noDeprecation = true` is set, then it is a no-op.
+ *
+ * If `localStorage.throwDeprecation = true` is set, then deprecated functions
+ * will throw an Error when invoked.
+ *
+ * If `localStorage.traceDeprecation = true` is set, then deprecated functions
+ * will invoke `console.trace()` instead of `console.error()`.
+ *
+ * @param {Function} fn - the function to deprecate
+ * @param {String} msg - the string to print to the console when `fn` is invoked
+ * @returns {Function} a new "deprecated" version of `fn`
+ * @api public
+ */
 
+function deprecate (fn, msg) {
+  if (config('noDeprecation')) {
+    return fn;
+  }
 
+  var warned = false;
+  function deprecated() {
+    if (!warned) {
+      if (config('throwDeprecation')) {
+        throw new Error(msg);
+      } else if (config('traceDeprecation')) {
+        console.trace(msg);
+      } else {
+        console.warn(msg);
+      }
+      warned = true;
+    }
+    return fn.apply(this, arguments);
+  }
 
+  return deprecated;
+}
 
+/**
+ * Checks `localStorage` for boolean values for the given `name`.
+ *
+ * @param {String} name
+ * @returns {Boolean}
+ * @api private
+ */
 
+function config (name) {
+  // accessing global.localStorage can trigger a DOMException in sandboxed iframes
+  try {
+    if (!global.localStorage) return false;
+  } catch (_) {
+    return false;
+  }
+  var val = global.localStorage[name];
+  if (null == val) return false;
+  return String(val).toLowerCase() === 'true';
+}
 
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],231:[function(require,module,exports){
+arguments[4][209][0].apply(exports,arguments)
+},{"dup":209}],232:[function(require,module,exports){
+module.exports = function isBuffer(arg) {
+  return arg && typeof arg === 'object'
+    && typeof arg.copy === 'function'
+    && typeof arg.fill === 'function'
+    && typeof arg.readUInt8 === 'function';
+}
+},{}],233:[function(require,module,exports){
+(function (process,global){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
 
+var formatRegExp = /%[sdj%]/g;
+exports.format = function(f) {
+  if (!isString(f)) {
+    var objects = [];
+    for (var i = 0; i < arguments.length; i++) {
+      objects.push(inspect(arguments[i]));
+    }
+    return objects.join(' ');
+  }
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-window.requestFileSystem=window.requestFileSystem||window.webkitRequestFileSystem;
-
-
-
-
-WebInspector.TempFile=function()
-{
-this._fileEntry=null;
-this._writer=null;
+  var i = 1;
+  var args = arguments;
+  var len = args.length;
+  var str = String(f).replace(formatRegExp, function(x) {
+    if (x === '%%') return '%';
+    if (i >= len) return x;
+    switch (x) {
+      case '%s': return String(args[i++]);
+      case '%d': return Number(args[i++]);
+      case '%j':
+        try {
+          return JSON.stringify(args[i++]);
+        } catch (_) {
+          return '[Circular]';
+        }
+      default:
+        return x;
+    }
+  });
+  for (var x = args[i]; i < len; x = args[++i]) {
+    if (isNull(x) || !isObject(x)) {
+      str += ' ' + x;
+    } else {
+      str += ' ' + inspect(x);
+    }
+  }
+  return str;
 };
 
 
+// Mark that a method should not be used.
+// Returns a modified function which warns once by default.
+// If --no-deprecation is set, then it is a no-op.
+exports.deprecate = function(fn, msg) {
+  // Allow for deprecating things in the process of starting up.
+  if (isUndefined(global.process)) {
+    return function() {
+      return exports.deprecate(fn, msg).apply(this, arguments);
+    };
+  }
 
+  if (process.noDeprecation === true) {
+    return fn;
+  }
 
+  var warned = false;
+  function deprecated() {
+    if (!warned) {
+      if (process.throwDeprecation) {
+        throw new Error(msg);
+      } else if (process.traceDeprecation) {
+        console.trace(msg);
+      } else {
+        console.error(msg);
+      }
+      warned = true;
+    }
+    return fn.apply(this, arguments);
+  }
 
-
-WebInspector.TempFile.create=function(dirPath,name)
-{
-var file=new WebInspector.TempFile();
-
-function requestTempFileSystem()
-{
-return new Promise(window.requestFileSystem.bind(window,window.TEMPORARY,10));
-}
-
-
-
-
-function getDirectoryEntry(fs)
-{
-return new Promise(fs.root.getDirectory.bind(fs.root,dirPath,{create:true}));
-}
-
-
-
-
-function getFileEntry(dir)
-{
-return new Promise(dir.getFile.bind(dir,name,{create:true}));
-}
-
-
-
-
-function createFileWriter(fileEntry)
-{
-file._fileEntry=fileEntry;
-return new Promise(fileEntry.createWriter.bind(fileEntry));
-}
-
-
-
-
-function truncateFile(writer)
-{
-if(!writer.length){
-file._writer=writer;
-return Promise.resolve(file);
-}
-
-
-
-
-
-function truncate(fulfill,reject)
-{
-writer.onwriteend=fulfill;
-writer.onerror=reject;
-writer.truncate(0);
-}
-
-function didTruncate()
-{
-file._writer=writer;
-writer.onwriteend=null;
-writer.onerror=null;
-return Promise.resolve(file);
-}
-
-function onTruncateError(e)
-{
-writer.onwriteend=null;
-writer.onerror=null;
-throw e;
-}
-
-return new Promise(truncate).then(didTruncate,onTruncateError);
-}
-
-return WebInspector.TempFile.ensureTempStorageCleared().
-then(requestTempFileSystem).
-then(getDirectoryEntry).
-then(getFileEntry).
-then(createFileWriter).
-then(truncateFile);
-};
-
-WebInspector.TempFile.prototype={
-
-
-
-
-write:function(strings,callback)
-{
-var blob=new Blob(strings,{type:"text/plain"});
-this._writer.onerror=function(e)
-{
-WebInspector.console.error("Failed to write into a temp file: "+e.target.error.message);
-callback(-1);
-};
-this._writer.onwriteend=function(e)
-{
-callback(e.target.length);
-};
-this._writer.write(blob);
-},
-
-finishWriting:function()
-{
-this._writer=null;
-},
-
-
-
-
-read:function(callback)
-{
-this.readRange(undefined,undefined,callback);
-},
-
-
-
-
-
-
-readRange:function(startOffset,endOffset,callback)
-{
-
-
-
-function didGetFile(file)
-{
-var reader=new FileReader();
-
-if(typeof startOffset==="number"||typeof endOffset==="number")
-file=file.slice(startOffset,endOffset);
-
-
-
-reader.onloadend=function(e)
-{
-callback(this.result);
-};
-reader.onerror=function(error)
-{
-WebInspector.console.error("Failed to read from temp file: "+error.message);
-};
-reader.readAsText(file);
-}
-function didFailToGetFile(error)
-{
-WebInspector.console.error("Failed to load temp file: "+error.message);
-callback(null);
-}
-this._fileEntry.file(didGetFile,didFailToGetFile);
-},
-
-
-
-
-
-copyToOutputStream:function(outputStream,delegate)
-{
-
-
-
-function didGetFile(file)
-{
-var reader=new WebInspector.ChunkedFileReader(file,10*1000*1000,delegate);
-reader.start(outputStream);
-}
-
-function didFailToGetFile(error)
-{
-WebInspector.console.error("Failed to load temp file: "+error.message);
-outputStream.close();
-}
-
-this._fileEntry.file(didGetFile,didFailToGetFile);
-},
-
-remove:function()
-{
-if(this._fileEntry)
-this._fileEntry.remove(function(){});
-}};
-
-
-
-
-
-
-
-WebInspector.DeferredTempFile=function(dirPath,name)
-{
-
-this._chunks=[];
-this._tempFile=null;
-this._isWriting=false;
-this._finishCallback=null;
-this._finishedWriting=false;
-this._callsPendingOpen=[];
-this._pendingReads=[];
-WebInspector.TempFile.create(dirPath,name).
-then(this._didCreateTempFile.bind(this),this._failedToCreateTempFile.bind(this));
-};
-
-WebInspector.DeferredTempFile.prototype={
-
-
-
-
-write:function(strings,callback)
-{
-if(this._finishCallback)
-throw new Error("No writes are allowed after close.");
-this._chunks.push({strings:strings,callback:callback||null});
-if(this._tempFile&&!this._isWriting)
-this._writeNextChunk();
-},
-
-
-
-
-finishWriting:function(callback)
-{
-this._finishCallback=callback;
-if(this._finishedWriting)
-callback(this._tempFile);else
-if(!this._isWriting&&!this._chunks.length)
-this._notifyFinished();
-},
-
-
-
-
-_failedToCreateTempFile:function(e)
-{
-WebInspector.console.error("Failed to create temp file "+e.code+" : "+e.message);
-this._notifyFinished();
-},
-
-
-
-
-_didCreateTempFile:function(tempFile)
-{
-this._tempFile=tempFile;
-var callsPendingOpen=this._callsPendingOpen;
-this._callsPendingOpen=null;
-for(var i=0;i<callsPendingOpen.length;++i)
-callsPendingOpen[i]();
-if(this._chunks.length)
-this._writeNextChunk();
-},
-
-_writeNextChunk:function()
-{
-
-if(!this._tempFile)
-return;
-var chunk=this._chunks.shift();
-this._isWriting=true;
-this._tempFile.write(chunk.strings,this._didWriteChunk.bind(this,chunk.callback));
-},
-
-
-
-
-
-_didWriteChunk:function(callback,size)
-{
-this._isWriting=false;
-if(size===-1){
-this._tempFile=null;
-this._notifyFinished();
-return;
-}
-if(callback)
-callback(size);
-if(this._chunks.length)
-this._writeNextChunk();else
-if(this._finishCallback)
-this._notifyFinished();
-},
-
-_notifyFinished:function()
-{
-this._finishedWriting=true;
-if(this._tempFile)
-this._tempFile.finishWriting();
-var chunks=this._chunks;
-this._chunks=[];
-for(var i=0;i<chunks.length;++i){
-if(chunks[i].callback)
-chunks[i].callback(-1);
-}
-if(this._finishCallback)
-this._finishCallback(this._tempFile);
-var pendingReads=this._pendingReads;
-this._pendingReads=[];
-for(var i=0;i<pendingReads.length;++i)
-pendingReads[i]();
-},
-
-
-
-
-
-
-readRange:function(startOffset,endOffset,callback)
-{
-if(!this._finishedWriting){
-this._pendingReads.push(this.readRange.bind(this,startOffset,endOffset,callback));
-return;
-}
-if(!this._tempFile){
-callback(null);
-return;
-}
-this._tempFile.readRange(startOffset,endOffset,callback);
-},
-
-
-
-
-
-copyToOutputStream:function(outputStream,delegate)
-{
-if(!this._finishedWriting){
-this._pendingReads.push(this.copyToOutputStream.bind(this,outputStream,delegate));
-return;
-}
-if(this._tempFile)
-this._tempFile.copyToOutputStream(outputStream,delegate);
-},
-
-remove:function()
-{
-if(this._callsPendingOpen){
-this._callsPendingOpen.push(this.remove.bind(this));
-return;
-}
-if(this._tempFile)
-this._tempFile.remove();
-this._tempFile=null;
-}};
-
-
-
-
-
-
-WebInspector.TempFile._clearTempStorage=function(fulfill,reject)
-{
-
-
-
-function handleError(event)
-{
-WebInspector.console.error(WebInspector.UIString("Failed to clear temp storage: %s",event.data));
-reject(event.data);
-}
-
-
-
-
-function handleMessage(event)
-{
-if(event.data.type==="tempStorageCleared"){
-if(event.data.error)
-WebInspector.console.error(event.data.error);else
-
-fulfill(undefined);
-return;
-}
-reject(event.data);
-}
-
-try{
-var worker=new WebInspector.Worker("temp_storage_shared_worker","TempStorageCleaner");
-worker.onerror=handleError;
-worker.onmessage=handleMessage;
-}catch(e){
-if(e.name==="URLMismatchError")
-console.log("Shared worker wasn't started due to url difference. "+e);else
-
-throw e;
-}
+  return deprecated;
 };
 
 
-
-
-WebInspector.TempFile.ensureTempStorageCleared=function()
-{
-if(!WebInspector.TempFile._storageCleanerPromise)
-WebInspector.TempFile._storageCleanerPromise=new Promise(WebInspector.TempFile._clearTempStorage);
-return WebInspector.TempFile._storageCleanerPromise;
+var debugs = {};
+var debugEnviron;
+exports.debuglog = function(set) {
+  if (isUndefined(debugEnviron))
+    debugEnviron = process.env.NODE_DEBUG || '';
+  set = set.toUpperCase();
+  if (!debugs[set]) {
+    if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) {
+      var pid = process.pid;
+      debugs[set] = function() {
+        var msg = exports.format.apply(exports, arguments);
+        console.error('%s %d: %s', set, pid, msg);
+      };
+    } else {
+      debugs[set] = function() {};
+    }
+  }
+  return debugs[set];
 };
 
 
+/**
+ * Echos the value of a value. Trys to print the value out
+ * in the best way possible given the different types.
+ *
+ * @param {Object} obj The object to print out.
+ * @param {Object} opts Optional options object that alters the output.
+ */
+/* legacy: obj, showHidden, depth, colors*/
+function inspect(obj, opts) {
+  // default options
+  var ctx = {
+    seen: [],
+    stylize: stylizeNoColor
+  };
+  // legacy...
+  if (arguments.length >= 3) ctx.depth = arguments[2];
+  if (arguments.length >= 4) ctx.colors = arguments[3];
+  if (isBoolean(opts)) {
+    // legacy...
+    ctx.showHidden = opts;
+  } else if (opts) {
+    // got an "options" object
+    exports._extend(ctx, opts);
+  }
+  // set default options
+  if (isUndefined(ctx.showHidden)) ctx.showHidden = false;
+  if (isUndefined(ctx.depth)) ctx.depth = 2;
+  if (isUndefined(ctx.colors)) ctx.colors = false;
+  if (isUndefined(ctx.customInspect)) ctx.customInspect = true;
+  if (ctx.colors) ctx.stylize = stylizeWithColor;
+  return formatValue(ctx, obj, ctx.depth);
+}
+exports.inspect = inspect;
 
 
+// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
+inspect.colors = {
+  'bold' : [1, 22],
+  'italic' : [3, 23],
+  'underline' : [4, 24],
+  'inverse' : [7, 27],
+  'white' : [37, 39],
+  'grey' : [90, 39],
+  'black' : [30, 39],
+  'blue' : [34, 39],
+  'cyan' : [36, 39],
+  'green' : [32, 39],
+  'magenta' : [35, 39],
+  'red' : [31, 39],
+  'yellow' : [33, 39]
+};
 
-
-WebInspector.TempFileBackingStorage=function(dirName)
-{
-this._dirName=dirName;
-this.reset();
+// Don't use 'blue' not visible on cmd.exe
+inspect.styles = {
+  'special': 'cyan',
+  'number': 'yellow',
+  'boolean': 'yellow',
+  'undefined': 'grey',
+  'null': 'bold',
+  'string': 'green',
+  'date': 'magenta',
+  // "name": intentionally not styling
+  'regexp': 'red'
 };
 
 
+function stylizeWithColor(str, styleType) {
+  var style = inspect.styles[styleType];
+
+  if (style) {
+    return '\u001b[' + inspect.colors[style][0] + 'm' + str +
+           '\u001b[' + inspect.colors[style][1] + 'm';
+  } else {
+    return str;
+  }
+}
 
 
+function stylizeNoColor(str, styleType) {
+  return str;
+}
 
 
+function arrayToHash(array) {
+  var hash = {};
+
+  array.forEach(function(val, idx) {
+    hash[val] = true;
+  });
+
+  return hash;
+}
 
 
+function formatValue(ctx, value, recurseTimes) {
+  // Provide a hook for user-specified inspect functions.
+  // Check that value is an object with an inspect function on it
+  if (ctx.customInspect &&
+      value &&
+      isFunction(value.inspect) &&
+      // Filter out the util module, it's inspect function is special
+      value.inspect !== exports.inspect &&
+      // Also filter out any prototype objects using the circular check.
+      !(value.constructor && value.constructor.prototype === value)) {
+    var ret = value.inspect(recurseTimes, ctx);
+    if (!isString(ret)) {
+      ret = formatValue(ctx, ret, recurseTimes);
+    }
+    return ret;
+  }
+
+  // Primitive types cannot have properties
+  var primitive = formatPrimitive(ctx, value);
+  if (primitive) {
+    return primitive;
+  }
+
+  // Look up the keys of the object.
+  var keys = Object.keys(value);
+  var visibleKeys = arrayToHash(keys);
+
+  if (ctx.showHidden) {
+    keys = Object.getOwnPropertyNames(value);
+  }
+
+  // IE doesn't make error fields non-enumerable
+  // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx
+  if (isError(value)
+      && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) {
+    return formatError(value);
+  }
+
+  // Some type of object without properties can be shortcutted.
+  if (keys.length === 0) {
+    if (isFunction(value)) {
+      var name = value.name ? ': ' + value.name : '';
+      return ctx.stylize('[Function' + name + ']', 'special');
+    }
+    if (isRegExp(value)) {
+      return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
+    }
+    if (isDate(value)) {
+      return ctx.stylize(Date.prototype.toString.call(value), 'date');
+    }
+    if (isError(value)) {
+      return formatError(value);
+    }
+  }
+
+  var base = '', array = false, braces = ['{', '}'];
+
+  // Make Array say that they are Array
+  if (isArray(value)) {
+    array = true;
+    braces = ['[', ']'];
+  }
+
+  // Make functions say that they are functions
+  if (isFunction(value)) {
+    var n = value.name ? ': ' + value.name : '';
+    base = ' [Function' + n + ']';
+  }
+
+  // Make RegExps say that they are RegExps
+  if (isRegExp(value)) {
+    base = ' ' + RegExp.prototype.toString.call(value);
+  }
+
+  // Make dates with properties first say the date
+  if (isDate(value)) {
+    base = ' ' + Date.prototype.toUTCString.call(value);
+  }
+
+  // Make error with message first say the error
+  if (isError(value)) {
+    base = ' ' + formatError(value);
+  }
+
+  if (keys.length === 0 && (!array || value.length == 0)) {
+    return braces[0] + base + braces[1];
+  }
+
+  if (recurseTimes < 0) {
+    if (isRegExp(value)) {
+      return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
+    } else {
+      return ctx.stylize('[Object]', 'special');
+    }
+  }
+
+  ctx.seen.push(value);
+
+  var output;
+  if (array) {
+    output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
+  } else {
+    output = keys.map(function(key) {
+      return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
+    });
+  }
+
+  ctx.seen.pop();
+
+  return reduceToSingleString(output, base, braces);
+}
+
+
+function formatPrimitive(ctx, value) {
+  if (isUndefined(value))
+    return ctx.stylize('undefined', 'undefined');
+  if (isString(value)) {
+    var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
+                                             .replace(/'/g, "\\'")
+                                             .replace(/\\"/g, '"') + '\'';
+    return ctx.stylize(simple, 'string');
+  }
+  if (isNumber(value))
+    return ctx.stylize('' + value, 'number');
+  if (isBoolean(value))
+    return ctx.stylize('' + value, 'boolean');
+  // For some reason typeof null is "object", so special case here.
+  if (isNull(value))
+    return ctx.stylize('null', 'null');
+}
+
+
+function formatError(value) {
+  return '[' + Error.prototype.toString.call(value) + ']';
+}
+
+
+function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
+  var output = [];
+  for (var i = 0, l = value.length; i < l; ++i) {
+    if (hasOwnProperty(value, String(i))) {
+      output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
+          String(i), true));
+    } else {
+      output.push('');
+    }
+  }
+  keys.forEach(function(key) {
+    if (!key.match(/^\d+$/)) {
+      output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
+          key, true));
+    }
+  });
+  return output;
+}
+
+
+function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
+  var name, str, desc;
+  desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] };
+  if (desc.get) {
+    if (desc.set) {
+      str = ctx.stylize('[Getter/Setter]', 'special');
+    } else {
+      str = ctx.stylize('[Getter]', 'special');
+    }
+  } else {
+    if (desc.set) {
+      str = ctx.stylize('[Setter]', 'special');
+    }
+  }
+  if (!hasOwnProperty(visibleKeys, key)) {
+    name = '[' + key + ']';
+  }
+  if (!str) {
+    if (ctx.seen.indexOf(desc.value) < 0) {
+      if (isNull(recurseTimes)) {
+        str = formatValue(ctx, desc.value, null);
+      } else {
+        str = formatValue(ctx, desc.value, recurseTimes - 1);
+      }
+      if (str.indexOf('\n') > -1) {
+        if (array) {
+          str = str.split('\n').map(function(line) {
+            return '  ' + line;
+          }).join('\n').substr(2);
+        } else {
+          str = '\n' + str.split('\n').map(function(line) {
+            return '   ' + line;
+          }).join('\n');
+        }
+      }
+    } else {
+      str = ctx.stylize('[Circular]', 'special');
+    }
+  }
+  if (isUndefined(name)) {
+    if (array && key.match(/^\d+$/)) {
+      return str;
+    }
+    name = JSON.stringify('' + key);
+    if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
+      name = name.substr(1, name.length - 2);
+      name = ctx.stylize(name, 'name');
+    } else {
+      name = name.replace(/'/g, "\\'")
+                 .replace(/\\"/g, '"')
+                 .replace(/(^"|"$)/g, "'");
+      name = ctx.stylize(name, 'string');
+    }
+  }
+
+  return name + ': ' + str;
+}
+
+
+function reduceToSingleString(output, base, braces) {
+  var numLinesEst = 0;
+  var length = output.reduce(function(prev, cur) {
+    numLinesEst++;
+    if (cur.indexOf('\n') >= 0) numLinesEst++;
+    return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1;
+  }, 0);
+
+  if (length > 60) {
+    return braces[0] +
+           (base === '' ? '' : base + '\n ') +
+           ' ' +
+           output.join(',\n  ') +
+           ' ' +
+           braces[1];
+  }
+
+  return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
+}
+
+
+// NOTE: These type checking functions intentionally don't use `instanceof`
+// because it is fragile and can be easily faked with `Object.create()`.
+function isArray(ar) {
+  return Array.isArray(ar);
+}
+exports.isArray = isArray;
+
+function isBoolean(arg) {
+  return typeof arg === 'boolean';
+}
+exports.isBoolean = isBoolean;
+
+function isNull(arg) {
+  return arg === null;
+}
+exports.isNull = isNull;
+
+function isNullOrUndefined(arg) {
+  return arg == null;
+}
+exports.isNullOrUndefined = isNullOrUndefined;
+
+function isNumber(arg) {
+  return typeof arg === 'number';
+}
+exports.isNumber = isNumber;
+
+function isString(arg) {
+  return typeof arg === 'string';
+}
+exports.isString = isString;
+
+function isSymbol(arg) {
+  return typeof arg === 'symbol';
+}
+exports.isSymbol = isSymbol;
+
+function isUndefined(arg) {
+  return arg === void 0;
+}
+exports.isUndefined = isUndefined;
+
+function isRegExp(re) {
+  return isObject(re) && objectToString(re) === '[object RegExp]';
+}
+exports.isRegExp = isRegExp;
+
+function isObject(arg) {
+  return typeof arg === 'object' && arg !== null;
+}
+exports.isObject = isObject;
+
+function isDate(d) {
+  return isObject(d) && objectToString(d) === '[object Date]';
+}
+exports.isDate = isDate;
+
+function isError(e) {
+  return isObject(e) &&
+      (objectToString(e) === '[object Error]' || e instanceof Error);
+}
+exports.isError = isError;
+
+function isFunction(arg) {
+  return typeof arg === 'function';
+}
+exports.isFunction = isFunction;
+
+function isPrimitive(arg) {
+  return arg === null ||
+         typeof arg === 'boolean' ||
+         typeof arg === 'number' ||
+         typeof arg === 'string' ||
+         typeof arg === 'symbol' ||  // ES6 symbol
+         typeof arg === 'undefined';
+}
+exports.isPrimitive = isPrimitive;
+
+exports.isBuffer = require('./support/isBuffer');
+
+function objectToString(o) {
+  return Object.prototype.toString.call(o);
+}
+
+
+function pad(n) {
+  return n < 10 ? '0' + n.toString(10) : n.toString(10);
+}
+
+
+var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
+              'Oct', 'Nov', 'Dec'];
+
+// 26 Feb 16:19:34
+function timestamp() {
+  var d = new Date();
+  var time = [pad(d.getHours()),
+              pad(d.getMinutes()),
+              pad(d.getSeconds())].join(':');
+  return [d.getDate(), months[d.getMonth()], time].join(' ');
+}
+
+
+// log is just a thin wrapper to console.log that prepends a timestamp
+exports.log = function() {
+  console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments));
+};
+
+
+/**
+ * Inherit the prototype methods from one constructor into another.
+ *
+ * The Function.prototype.inherits from lang.js rewritten as a standalone
+ * function (not on Function.prototype). NOTE: If this file is to be loaded
+ * during bootstrapping this function needs to be rewritten using some native
+ * functions as prototype setup using normal JavaScript does not work as
+ * expected during bootstrapping (see mirror.js in r114903).
+ *
+ * @param {function} ctor Constructor function which needs to inherit the
+ *     prototype.
+ * @param {function} superCtor Constructor function to inherit prototype from.
+ */
+exports.inherits = require('inherits');
+
+exports._extend = function(origin, add) {
+  // Don't do anything if add isn't an object
+  if (!add || !isObject(add)) return origin;
+
+  var keys = Object.keys(add);
+  var i = keys.length;
+  while (i--) {
+    origin[keys[i]] = add[keys[i]];
+  }
+  return origin;
+};
+
+function hasOwnProperty(obj, prop) {
+  return Object.prototype.hasOwnProperty.call(obj, prop);
+}
+
+}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./support/isBuffer":232,"_process":222,"inherits":231}],234:[function(require,module,exports){
+/*
+ * Copyright (C) 2013 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
+
+/**
+ * @constructor
+ */
+WebInspector.TempFile = function()
+{
+    this._fileEntry = null;
+    this._writer = null;
+}
+
+/**
+ * @param {string} dirPath
+ * @param {string} name
+ * @return {!Promise.<!WebInspector.TempFile>}
+ */
+WebInspector.TempFile.create = function(dirPath, name)
+{
+    var file = new WebInspector.TempFile();
+
+    function requestTempFileSystem()
+    {
+        return new Promise(window.requestFileSystem.bind(window, window.TEMPORARY, 10));
+    }
+
+    /**
+     * @param {!FileSystem} fs
+     */
+    function getDirectoryEntry(fs)
+    {
+        return new Promise(fs.root.getDirectory.bind(fs.root, dirPath, { create: true }));
+    }
+
+    /**
+     * @param {!DirectoryEntry} dir
+     */
+    function getFileEntry(dir)
+    {
+        return new Promise(dir.getFile.bind(dir, name, { create: true }));
+    }
+
+    /**
+     * @param {!FileEntry} fileEntry
+     */
+    function createFileWriter(fileEntry)
+    {
+        file._fileEntry = fileEntry;
+        return new Promise(fileEntry.createWriter.bind(fileEntry));
+    }
+
+    /**
+     * @param {!FileWriter} writer
+     */
+    function truncateFile(writer)
+    {
+        if (!writer.length) {
+            file._writer = writer;
+            return Promise.resolve(file);
+        }
+
+        /**
+         * @param {function(?)} fulfill
+         * @param {function(*)} reject
+         */
+        function truncate(fulfill, reject)
+        {
+            writer.onwriteend = fulfill;
+            writer.onerror = reject;
+            writer.truncate(0);
+        }
+
+        function didTruncate()
+        {
+            file._writer = writer;
+            writer.onwriteend = null;
+            writer.onerror = null;
+            return Promise.resolve(file);
+        }
+
+        function onTruncateError(e)
+        {
+            writer.onwriteend = null;
+            writer.onerror = null;
+            throw e;
+        }
+
+        return new Promise(truncate).then(didTruncate, onTruncateError);
+    }
+
+    return WebInspector.TempFile.ensureTempStorageCleared()
+        .then(requestTempFileSystem)
+        .then(getDirectoryEntry)
+        .then(getFileEntry)
+        .then(createFileWriter)
+        .then(truncateFile);
+}
+
+WebInspector.TempFile.prototype = {
+    /**
+     * @param {!Array.<string>} strings
+     * @param {function(number)} callback
+     */
+    write: function(strings, callback)
+    {
+        var blob = new Blob(strings, {type: "text/plain"});
+        this._writer.onerror = function(e)
+        {
+            WebInspector.console.error("Failed to write into a temp file: " + e.target.error.message);
+            callback(-1);
+        }
+        this._writer.onwriteend = function(e)
+        {
+            callback(e.target.length);
+        }
+        this._writer.write(blob);
+    },
+
+    finishWriting: function()
+    {
+        this._writer = null;
+    },
+
+    /**
+     * @param {function(?string)} callback
+     */
+    read: function(callback)
+    {
+        this.readRange(undefined, undefined, callback);
+    },
+
+    /**
+     * @param {number|undefined} startOffset
+     * @param {number|undefined} endOffset
+     * @param {function(?string)} callback
+     */
+    readRange: function(startOffset, endOffset, callback)
+    {
+        /**
+         * @param {!Blob} file
+         */
+        function didGetFile(file)
+        {
+            var reader = new FileReader();
+
+            if (typeof startOffset === "number" || typeof endOffset === "number")
+                file = file.slice(/** @type {number} */ (startOffset), /** @type {number} */ (endOffset));
+            /**
+             * @this {FileReader}
+             */
+            reader.onloadend = function(e)
+            {
+                callback(/** @type {?string} */ (this.result));
+            };
+            reader.onerror = function(error)
+            {
+                WebInspector.console.error("Failed to read from temp file: " + error.message);
+            };
+            reader.readAsText(file);
+        }
+        function didFailToGetFile(error)
+        {
+            WebInspector.console.error("Failed to load temp file: " + error.message);
+            callback(null);
+        }
+        this._fileEntry.file(didGetFile, didFailToGetFile);
+    },
+
+    /**
+     * @param {!WebInspector.OutputStream} outputStream
+     * @param {!WebInspector.OutputStreamDelegate} delegate
+     */
+    copyToOutputStream: function(outputStream, delegate)
+    {
+        /**
+         * @param {!File} file
+         */
+        function didGetFile(file)
+        {
+            var reader = new WebInspector.ChunkedFileReader(file, 10 * 1000 * 1000, delegate);
+            reader.start(outputStream);
+        }
+
+        function didFailToGetFile(error)
+        {
+            WebInspector.console.error("Failed to load temp file: " + error.message);
+            outputStream.close();
+        }
+
+        this._fileEntry.file(didGetFile, didFailToGetFile);
+    },
+
+    remove: function()
+    {
+        if (this._fileEntry)
+            this._fileEntry.remove(function() {});
+    }
+}
+
+/**
+ * @constructor
+ * @param {string} dirPath
+ * @param {string} name
+ */
+WebInspector.DeferredTempFile = function(dirPath, name)
+{
+    /** @type {!Array.<!{strings: !Array.<string>, callback: ?function(number)}>} */
+    this._chunks = [];
+    this._tempFile = null;
+    this._isWriting = false;
+    this._finishCallback = null;
+    this._finishedWriting = false;
+    this._callsPendingOpen = [];
+    this._pendingReads = [];
+    WebInspector.TempFile.create(dirPath, name)
+        .then(this._didCreateTempFile.bind(this), this._failedToCreateTempFile.bind(this));
+}
+
+WebInspector.DeferredTempFile.prototype = {
+    /**
+     * @param {!Array.<string>} strings
+     * @param {function(number)=} callback
+     */
+    write: function(strings, callback)
+    {
+        if (this._finishCallback)
+            throw new Error("No writes are allowed after close.");
+        this._chunks.push({strings: strings, callback: callback || null});
+        if (this._tempFile && !this._isWriting)
+            this._writeNextChunk();
+    },
+
+    /**
+     * @param {function(?WebInspector.TempFile)} callback
+     */
+    finishWriting: function(callback)
+    {
+        this._finishCallback = callback;
+        if (this._finishedWriting)
+            callback(this._tempFile);
+        else if (!this._isWriting && !this._chunks.length)
+            this._notifyFinished();
+    },
+
+    /**
+     * @param {*} e
+     */
+    _failedToCreateTempFile: function(e)
+    {
+        WebInspector.console.error("Failed to create temp file " + e.code + " : " + e.message);
+        this._notifyFinished();
+    },
+
+    /**
+     * @param {!WebInspector.TempFile} tempFile
+     */
+    _didCreateTempFile: function(tempFile)
+    {
+        this._tempFile = tempFile;
+        var callsPendingOpen = this._callsPendingOpen;
+        this._callsPendingOpen = null;
+        for (var i = 0; i < callsPendingOpen.length; ++i)
+            callsPendingOpen[i]();
+        if (this._chunks.length)
+            this._writeNextChunk();
+    },
+
+    _writeNextChunk: function()
+    {
+        // File was deleted while create or write was in-flight.
+        if (!this._tempFile)
+            return;
+        var chunk = this._chunks.shift();
+        this._isWriting = true;
+        this._tempFile.write(/** @type {!Array.<string>} */(chunk.strings), this._didWriteChunk.bind(this, chunk.callback));
+    },
+
+    /**
+     * @param {?function(number)} callback
+     * @param {number} size
+     */
+    _didWriteChunk: function(callback, size)
+    {
+        this._isWriting = false;
+        if (size === -1) {
+            this._tempFile = null;
+            this._notifyFinished();
+            return;
+        }
+        if (callback)
+            callback(size);
+        if (this._chunks.length)
+            this._writeNextChunk();
+        else if (this._finishCallback)
+            this._notifyFinished();
+    },
+
+    _notifyFinished: function()
+    {
+        this._finishedWriting = true;
+        if (this._tempFile)
+            this._tempFile.finishWriting();
+        var chunks = this._chunks;
+        this._chunks = [];
+        for (var i = 0; i < chunks.length; ++i) {
+            if (chunks[i].callback)
+                chunks[i].callback(-1);
+        }
+        if (this._finishCallback)
+            this._finishCallback(this._tempFile);
+        var pendingReads = this._pendingReads;
+        this._pendingReads = [];
+        for (var i = 0; i < pendingReads.length; ++i)
+            pendingReads[i]();
+    },
+
+    /**
+     * @param {number|undefined} startOffset
+     * @param {number|undefined} endOffset
+     * @param {function(string?)} callback
+     */
+    readRange: function(startOffset, endOffset, callback)
+    {
+        if (!this._finishedWriting) {
+            this._pendingReads.push(this.readRange.bind(this, startOffset, endOffset, callback));
+            return;
+        }
+        if (!this._tempFile) {
+            callback(null);
+            return;
+        }
+        this._tempFile.readRange(startOffset, endOffset, callback);
+    },
+
+    /**
+     * @param {!WebInspector.OutputStream} outputStream
+     * @param {!WebInspector.OutputStreamDelegate} delegate
+     */
+    copyToOutputStream: function(outputStream, delegate)
+    {
+        if (!this._finishedWriting) {
+            this._pendingReads.push(this.copyToOutputStream.bind(this, outputStream, delegate));
+            return;
+        }
+        if (this._tempFile)
+            this._tempFile.copyToOutputStream(outputStream, delegate);
+    },
+
+    remove: function()
+    {
+        if (this._callsPendingOpen) {
+            this._callsPendingOpen.push(this.remove.bind(this));
+            return;
+        }
+        if (this._tempFile)
+            this._tempFile.remove();
+        this._tempFile = null;
+    }
+}
+
+/**
+ * @param {function(?)} fulfill
+ * @param {function(*)} reject
+ */
+WebInspector.TempFile._clearTempStorage = function(fulfill, reject)
+{
+    /**
+     * @param {!Event} event
+     */
+    function handleError(event)
+    {
+        WebInspector.console.error(WebInspector.UIString("Failed to clear temp storage: %s", event.data));
+        reject(event.data);
+    }
+
+    /**
+     * @param {!Event} event
+     */
+    function handleMessage(event)
+    {
+        if (event.data.type === "tempStorageCleared") {
+            if (event.data.error)
+                WebInspector.console.error(event.data.error);
+            else
+                fulfill(undefined);
+            return;
+        }
+        reject(event.data);
+    }
+
+    try {
+        var worker = new WebInspector.Worker("temp_storage_shared_worker", "TempStorageCleaner");
+        worker.onerror = handleError;
+        worker.onmessage = handleMessage;
+    } catch (e) {
+        if (e.name === "URLMismatchError")
+            console.log("Shared worker wasn't started due to url difference. " + e);
+        else
+            throw e;
+    }
+}
+
+/**
+ * @return {!Promise.<undefined>}
+ */
+WebInspector.TempFile.ensureTempStorageCleared = function()
+{
+    if (!WebInspector.TempFile._storageCleanerPromise)
+        WebInspector.TempFile._storageCleanerPromise = new Promise(WebInspector.TempFile._clearTempStorage);
+    return WebInspector.TempFile._storageCleanerPromise;
+}
+
+/**
+ * @constructor
+ * @implements {WebInspector.BackingStorage}
+ * @param {string} dirName
+ */
+WebInspector.TempFileBackingStorage = function(dirName)
+{
+    this._dirName = dirName;
+    this.reset();
+}
+
+/**
+ * @typedef {{
+ *      string: ?string,
+ *      startOffset: number,
+ *      endOffset: number
+ * }}
+ */
 WebInspector.TempFileBackingStorage.Chunk;
 
-WebInspector.TempFileBackingStorage.prototype={
+WebInspector.TempFileBackingStorage.prototype = {
+    /**
+     * @override
+     * @param {string} string
+     */
+    appendString: function(string)
+    {
+        this._strings.push(string);
+        this._stringsLength += string.length;
+        var flushStringLength = 10 * 1024 * 1024;
+        if (this._stringsLength > flushStringLength)
+            this._flush(false);
+    },
 
+    /**
+     * @override
+     * @param {string} string
+     * @return {function():!Promise.<?string>}
+     */
+    appendAccessibleString: function(string)
+    {
+        this._flush(false);
+        this._strings.push(string);
+        var chunk = /** @type {!WebInspector.TempFileBackingStorage.Chunk} */ (this._flush(true));
 
+        /**
+         * @param {!WebInspector.TempFileBackingStorage.Chunk} chunk
+         * @param {!WebInspector.DeferredTempFile} file
+         * @return {!Promise.<?string>}
+         */
+        function readString(chunk, file)
+        {
+            if (chunk.string)
+                return /** @type {!Promise.<?string>} */ (Promise.resolve(chunk.string));
 
+            console.assert(chunk.endOffset);
+            if (!chunk.endOffset)
+                return Promise.reject("Nor string nor offset to the string in the file were found.");
 
-appendString:function(string)
-{
-this._strings.push(string);
-this._stringsLength+=string.length;
-var flushStringLength=10*1024*1024;
-if(this._stringsLength>flushStringLength)
-this._flush(false);
-},
+            /**
+             * @param {function(?string)} fulfill
+             * @param {function(*)} reject
+             */
+            function readRange(fulfill, reject)
+            {
+                // FIXME: call reject for null strings.
+                file.readRange(chunk.startOffset, chunk.endOffset, fulfill);
+            }
 
+            return new Promise(readRange);
+        }
 
+        return readString.bind(null, chunk, this._file);
+    },
 
+    /**
+     * @param {boolean} createChunk
+     * @return {?WebInspector.TempFileBackingStorage.Chunk}
+     */
+    _flush: function(createChunk)
+    {
+        if (!this._strings.length)
+            return null;
 
+        var chunk = null;
+        if (createChunk) {
+            console.assert(this._strings.length === 1);
+            chunk = {
+                string: this._strings[0],
+                startOffset: 0,
+                endOffset: 0
+            };
+        }
 
+        /**
+         * @this {WebInspector.TempFileBackingStorage}
+         * @param {?WebInspector.TempFileBackingStorage.Chunk} chunk
+         * @param {number} fileSize
+         */
+        function didWrite(chunk, fileSize)
+        {
+            if (fileSize === -1)
+                return;
+            if (chunk) {
+                chunk.startOffset = this._fileSize;
+                chunk.endOffset = fileSize;
+                chunk.string = null;
+            }
+            this._fileSize = fileSize;
+        }
 
-appendAccessibleString:function(string)
-{
-this._flush(false);
-this._strings.push(string);
-var chunk=this._flush(true);
+        this._file.write(this._strings, didWrite.bind(this, chunk));
+        this._strings = [];
+        this._stringsLength = 0;
+        return chunk;
+    },
 
+    /**
+     * @override
+     */
+    finishWriting: function()
+    {
+        this._flush(false);
+        this._file.finishWriting(function() {});
+    },
 
+    /**
+     * @override
+     */
+    reset: function()
+    {
+        if (this._file)
+            this._file.remove();
+        this._file = new WebInspector.DeferredTempFile(this._dirName, String(Date.now()));
+        /**
+         * @type {!Array.<string>}
+         */
+        this._strings = [];
+        this._stringsLength = 0;
+        this._fileSize = 0;
+    },
 
-
-
-
-function readString(chunk,file)
-{
-if(chunk.string)
-return Promise.resolve(chunk.string);
-
-console.assert(chunk.endOffset);
-if(!chunk.endOffset)
-return Promise.reject("Nor string nor offset to the string in the file were found.");
-
-
-
-
-
-function readRange(fulfill,reject)
-{
-
-file.readRange(chunk.startOffset,chunk.endOffset,fulfill);
-}
-
-return new Promise(readRange);
+    /**
+     * @param {!WebInspector.OutputStream} outputStream
+     * @param {!WebInspector.OutputStreamDelegate} delegate
+     */
+    writeToStream: function(outputStream, delegate)
+    {
+        this._file.copyToOutputStream(outputStream, delegate);
+    }
 }
 
-return readString.bind(null,chunk,this._file);
-},
-
-
-
-
+},{}],235:[function(require,module,exports){
+/*
+ * Copyright (C) 2009 Apple Inc.  All rights reserved.
+ * Copyright (C) 2009 Joseph Pecoraro
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ *     its contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
 
-_flush:function(createChunk)
+/**
+ * @param {!Array.<number>} rgba
+ * @param {!WebInspector.Color.Format} format
+ * @param {string=} originalText
+ * @constructor
+ */
+WebInspector.Color = function(rgba, format, originalText)
 {
-if(!this._strings.length)
-return null;
+    this._rgba = rgba;
+    this._originalText = originalText || null;
+    this._originalTextIsValid = !!this._originalText;
+    this._format = format;
+    if (typeof this._rgba[3] === "undefined")
+        this._rgba[3] = 1;
 
-var chunk=null;
-if(createChunk){
-console.assert(this._strings.length===1);
-chunk={
-string:this._strings[0],
-startOffset:0,
-endOffset:0};
-
+    for (var i = 0; i < 4; ++i) {
+        if (this._rgba[i] < 0) {
+            this._rgba[i] = 0;
+            this._originalTextIsValid = false;
+        }
+        if (this._rgba[i] > 1) {
+            this._rgba[i] = 1;
+            this._originalTextIsValid = false;
+        }
+    }
 }
 
+/** @type {!RegExp} */
+WebInspector.Color.Regex = /((?:rgb|hsl)a?\([^)]+\)|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|\b[a-zA-Z]+\b(?!-))/g;
 
-
-
-
-
-function didWrite(chunk,fileSize)
-{
-if(fileSize===-1)
-return;
-if(chunk){
-chunk.startOffset=this._fileSize;
-chunk.endOffset=fileSize;
-chunk.string=null;
-}
-this._fileSize=fileSize;
+/**
+ * @enum {string}
+ */
+WebInspector.Color.Format = {
+    Original: "original",
+    Nickname: "nickname",
+    HEX: "hex",
+    ShortHEX: "shorthex",
+    RGB: "rgb",
+    RGBA: "rgba",
+    HSL: "hsl",
+    HSLA: "hsla"
 }
 
-this._file.write(this._strings,didWrite.bind(this,chunk));
-this._strings=[];
-this._stringsLength=0;
-return chunk;
-},
-
-
-
-
-finishWriting:function()
+/**
+ * @param {string} text
+ * @return {?WebInspector.Color}
+ */
+WebInspector.Color.parse = function(text)
 {
-this._flush(false);
-this._file.finishWriting(function(){});
-},
-
-
-
-
-reset:function()
-{
-if(this._file)
-this._file.remove();
-this._file=new WebInspector.DeferredTempFile(this._dirName,String(Date.now()));
-
-
-
-this._strings=[];
-this._stringsLength=0;
-this._fileSize=0;
-},
-
-
-
-
-
-writeToStream:function(outputStream,delegate)
-{
-this._file.copyToOutputStream(outputStream,delegate);
-}};
-
-
-},{}],206:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+    // Simple - #hex, rgb(), nickname, hsl()
+    var value = text.toLowerCase().replace(/\s+/g, "");
+    var simple = /^(?:#([0-9a-f]{3}|[0-9a-f]{6})|rgb\(((?:-?\d+%?,){2}-?\d+%?)\)|(\w+)|hsl\((-?\d+\.?\d*(?:,-?\d+\.?\d*%){2})\))$/i;
+    var match = value.match(simple);
+    if (match) {
+        if (match[1]) { // hex
+            var hex = match[1].toLowerCase();
+            var format;
+            if (hex.length === 3) {
+                format = WebInspector.Color.Format.ShortHEX;
+                hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2);
+            } else
+                format = WebInspector.Color.Format.HEX;
+            var r = parseInt(hex.substring(0,2), 16);
+            var g = parseInt(hex.substring(2,4), 16);
+            var b = parseInt(hex.substring(4,6), 16);
+            return new WebInspector.Color([r / 255, g / 255, b / 255, 1], format, text);
+        }
 
+        if (match[2]) { // rgb
+            var rgbString = match[2].split(/\s*,\s*/);
+            var rgba = [ WebInspector.Color._parseRgbNumeric(rgbString[0]),
+                         WebInspector.Color._parseRgbNumeric(rgbString[1]),
+                         WebInspector.Color._parseRgbNumeric(rgbString[2]), 1 ];
+            return new WebInspector.Color(rgba, WebInspector.Color.Format.RGB, text);
+        }
 
+        if (match[3]) { // nickname
+            var nickname = match[3].toLowerCase();
+            if (nickname in WebInspector.Color.Nicknames) {
+                var rgba = WebInspector.Color.Nicknames[nickname];
+                var color = WebInspector.Color.fromRGBA(rgba);
+                color._format = WebInspector.Color.Format.Nickname;
+                color._originalText = text;
+                return color;
+            }
+            return null;
+        }
 
+        if (match[4]) { // hsl
+            var hslString = match[4].replace(/%/g, "").split(/\s*,\s*/);
+            var hsla = [ WebInspector.Color._parseHueNumeric(hslString[0]),
+                         WebInspector.Color._parseSatLightNumeric(hslString[1]),
+                         WebInspector.Color._parseSatLightNumeric(hslString[2]), 1 ];
+            var rgba = [];
+            WebInspector.Color.hsl2rgb(hsla, rgba);
+            return new WebInspector.Color(rgba, WebInspector.Color.Format.HSL, text);
+        }
 
+        return null;
+    }
 
+    // Advanced - rgba(), hsla()
+    var advanced = /^(?:rgba\(((?:-?\d+%?,){3}-?(?:\d+|\d*\.\d+))\)|hsla\((-?(?:\d+|\d*\.\d+)(?:,-?(?:\d+|\d*\.\d+)*%){2},-?(?:\d+|\d*\.\d+))\))$/;
+    match = value.match(advanced);
+    if (match) {
+        if (match[1]) { // rgba
+            var rgbaString = match[1].split(/\s*,\s*/);
+            var rgba = [ WebInspector.Color._parseRgbNumeric(rgbaString[0]),
+                         WebInspector.Color._parseRgbNumeric(rgbaString[1]),
+                         WebInspector.Color._parseRgbNumeric(rgbaString[2]),
+                         WebInspector.Color._parseAlphaNumeric(rgbaString[3]) ];
+            return new WebInspector.Color(rgba, WebInspector.Color.Format.RGBA, text);
+        }
 
+        if (match[2]) { // hsla
+            var hslaString = match[2].replace(/%/g, "").split(/\s*,\s*/);
+            var hsla = [ WebInspector.Color._parseHueNumeric(hslaString[0]),
+                         WebInspector.Color._parseSatLightNumeric(hslaString[1]),
+                         WebInspector.Color._parseSatLightNumeric(hslaString[2]),
+                         WebInspector.Color._parseAlphaNumeric(hslaString[3]) ];
+            var rgba = [];
+            WebInspector.Color.hsl2rgb(hsla, rgba);
+            return new WebInspector.Color(rgba, WebInspector.Color.Format.HSLA, text);
+        }
+    }
 
+    return null;
+}
 
-WebInspector.Color=function(rgba,format,originalText)
+/**
+ * @param {!Array.<number>} rgba
+ * @return {!WebInspector.Color}
+ */
+WebInspector.Color.fromRGBA = function(rgba)
 {
-this._rgba=rgba;
-this._originalText=originalText||null;
-this._originalTextIsValid=!!this._originalText;
-this._format=format;
-if(typeof this._rgba[3]==="undefined")
-this._rgba[3]=1;
-
-for(var i=0;i<4;++i){
-if(this._rgba[i]<0){
-this._rgba[i]=0;
-this._originalTextIsValid=false;
-}
-if(this._rgba[i]>1){
-this._rgba[i]=1;
-this._originalTextIsValid=false;
-}
+    return new WebInspector.Color([rgba[0] / 255, rgba[1] / 255, rgba[2] / 255, rgba[3]], WebInspector.Color.Format.RGBA);
 }
-};
 
-
-WebInspector.Color.Regex=/((?:rgb|hsl)a?\([^)]+\)|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|\b[a-zA-Z]+\b(?!-))/g;
-
-
-
-
-WebInspector.Color.Format={
-Original:"original",
-Nickname:"nickname",
-HEX:"hex",
-ShortHEX:"shorthex",
-RGB:"rgb",
-RGBA:"rgba",
-HSL:"hsl",
-HSLA:"hsla"};
-
-
-
-
-
-
-WebInspector.Color.parse=function(text)
+/**
+ * @param {!Array.<number>} hsva
+ * @return {!WebInspector.Color}
+ */
+WebInspector.Color.fromHSVA = function(hsva)
 {
-
-var value=text.toLowerCase().replace(/\s+/g,"");
-var simple=/^(?:#([0-9a-f]{3}|[0-9a-f]{6})|rgb\(((?:-?\d+%?,){2}-?\d+%?)\)|(\w+)|hsl\((-?\d+\.?\d*(?:,-?\d+\.?\d*%){2})\))$/i;
-var match=value.match(simple);
-if(match){
-if(match[1]){
-var hex=match[1].toLowerCase();
-var format;
-if(hex.length===3){
-format=WebInspector.Color.Format.ShortHEX;
-hex=hex.charAt(0)+hex.charAt(0)+hex.charAt(1)+hex.charAt(1)+hex.charAt(2)+hex.charAt(2);
-}else
-format=WebInspector.Color.Format.HEX;
-var r=parseInt(hex.substring(0,2),16);
-var g=parseInt(hex.substring(2,4),16);
-var b=parseInt(hex.substring(4,6),16);
-return new WebInspector.Color([r/255,g/255,b/255,1],format,text);
+    var rgba = [];
+    WebInspector.Color.hsva2rgba(hsva, rgba);
+    return new WebInspector.Color(rgba, WebInspector.Color.Format.HSLA);
 }
 
-if(match[2]){
-var rgbString=match[2].split(/\s*,\s*/);
-var rgba=[WebInspector.Color._parseRgbNumeric(rgbString[0]),
-WebInspector.Color._parseRgbNumeric(rgbString[1]),
-WebInspector.Color._parseRgbNumeric(rgbString[2]),1];
-return new WebInspector.Color(rgba,WebInspector.Color.Format.RGB,text);
-}
+WebInspector.Color.prototype = {
+    /**
+     * @return {!WebInspector.Color.Format}
+     */
+    format: function()
+    {
+        return this._format;
+    },
 
-if(match[3]){
-var nickname=match[3].toLowerCase();
-if(nickname in WebInspector.Color.Nicknames){
-var rgba=WebInspector.Color.Nicknames[nickname];
-var color=WebInspector.Color.fromRGBA(rgba);
-color._format=WebInspector.Color.Format.Nickname;
-color._originalText=text;
-return color;
-}
-return null;
-}
+    /**
+     * @return {!Array.<number>} HSLA with components within [0..1]
+     */
+    hsla: function()
+    {
+        if (this._hsla)
+            return this._hsla;
+        var r = this._rgba[0];
+        var g = this._rgba[1];
+        var b = this._rgba[2];
+        var max = Math.max(r, g, b);
+        var min = Math.min(r, g, b);
+        var diff = max - min;
+        var add = max + min;
 
-if(match[4]){
-var hslString=match[4].replace(/%/g,"").split(/\s*,\s*/);
-var hsla=[WebInspector.Color._parseHueNumeric(hslString[0]),
-WebInspector.Color._parseSatLightNumeric(hslString[1]),
-WebInspector.Color._parseSatLightNumeric(hslString[2]),1];
-var rgba=[];
-WebInspector.Color.hsl2rgb(hsla,rgba);
-return new WebInspector.Color(rgba,WebInspector.Color.Format.HSL,text);
-}
+        if (min === max)
+            var h = 0;
+        else if (r === max)
+            var h = ((1 / 6 * (g - b) / diff) + 1) % 1;
+        else if (g === max)
+            var h = (1 / 6 * (b - r) / diff) + 1 / 3;
+        else
+            var h = (1 / 6 * (r - g) / diff) + 2 / 3;
 
-return null;
-}
+        var l = 0.5 * add;
 
+        if (l === 0)
+            var s = 0;
+        else if (l === 1)
+            var s = 0;
+        else if (l <= 0.5)
+            var s = diff / add;
+        else
+            var s = diff / (2 - add);
 
-var advanced=/^(?:rgba\(((?:-?\d+%?,){3}-?(?:\d+|\d*\.\d+))\)|hsla\((-?(?:\d+|\d*\.\d+)(?:,-?(?:\d+|\d*\.\d+)*%){2},-?(?:\d+|\d*\.\d+))\))$/;
-match=value.match(advanced);
-if(match){
-if(match[1]){
-var rgbaString=match[1].split(/\s*,\s*/);
-var rgba=[WebInspector.Color._parseRgbNumeric(rgbaString[0]),
-WebInspector.Color._parseRgbNumeric(rgbaString[1]),
-WebInspector.Color._parseRgbNumeric(rgbaString[2]),
-WebInspector.Color._parseAlphaNumeric(rgbaString[3])];
-return new WebInspector.Color(rgba,WebInspector.Color.Format.RGBA,text);
-}
-
-if(match[2]){
-var hslaString=match[2].replace(/%/g,"").split(/\s*,\s*/);
-var hsla=[WebInspector.Color._parseHueNumeric(hslaString[0]),
-WebInspector.Color._parseSatLightNumeric(hslaString[1]),
-WebInspector.Color._parseSatLightNumeric(hslaString[2]),
-WebInspector.Color._parseAlphaNumeric(hslaString[3])];
-var rgba=[];
-WebInspector.Color.hsl2rgb(hsla,rgba);
-return new WebInspector.Color(rgba,WebInspector.Color.Format.HSLA,text);
-}
-}
-
-return null;
-};
-
-
-
-
-
-WebInspector.Color.fromRGBA=function(rgba)
-{
-return new WebInspector.Color([rgba[0]/255,rgba[1]/255,rgba[2]/255,rgba[3]],WebInspector.Color.Format.RGBA);
-};
-
-
-
-
-
-WebInspector.Color.fromHSVA=function(hsva)
-{
-var rgba=[];
-WebInspector.Color.hsva2rgba(hsva,rgba);
-return new WebInspector.Color(rgba,WebInspector.Color.Format.HSLA);
-};
-
-WebInspector.Color.prototype={
-
-
-
-format:function()
-{
-return this._format;
-},
-
-
-
-
-hsla:function()
-{
-if(this._hsla)
-return this._hsla;
-var r=this._rgba[0];
-var g=this._rgba[1];
-var b=this._rgba[2];
-var max=Math.max(r,g,b);
-var min=Math.min(r,g,b);
-var diff=max-min;
-var add=max+min;
-
-if(min===max)
-var h=0;else
-if(r===max)
-var h=(1/6*(g-b)/diff+1)%1;else
-if(g===max)
-var h=1/6*(b-r)/diff+1/3;else
-
-var h=1/6*(r-g)/diff+2/3;
-
-var l=0.5*add;
+        this._hsla = [h, s, l, this._rgba[3]];
+        return this._hsla;
+    },
 
-if(l===0)
-var s=0;else
-if(l===1)
-var s=0;else
-if(l<=0.5)
-var s=diff/add;else
+    /**
+     * @return {!Array.<number>}
+     */
+    canonicalHSLA: function()
+    {
+        var hsla = this.hsla();
+        return [Math.round(hsla[0] * 360), Math.round(hsla[1] * 100), Math.round(hsla[2] * 100), hsla[3]];
+    },
 
-var s=diff/(2-add);
+    /**
+     * @return {!Array.<number>} HSVA with components within [0..1]
+     */
+    hsva: function()
+    {
+        var hsla = this.hsla();
+        var h = hsla[0];
+        var s = hsla[1];
+        var l = hsla[2];
 
-this._hsla=[h,s,l,this._rgba[3]];
-return this._hsla;
-},
+        s *= l < 0.5 ? l : 1 - l;
+        return [h, s !== 0 ? 2 * s / (l + s) : 0, (l + s), hsla[3]];
+    },
 
+    /**
+     * @return {boolean}
+     */
+    hasAlpha: function()
+    {
+        return this._rgba[3] !== 1;
+    },
 
+    /**
+     * @return {boolean}
+     */
+    canBeShortHex: function()
+    {
+        if (this.hasAlpha())
+            return false;
+        for (var i = 0; i < 3; ++i) {
+            var c = Math.round(this._rgba[i] * 255);
+            if (c % 17)
+                return false;
+        }
+        return true;
+    },
 
+    /**
+     * @return {?string}
+     */
+    asString: function(format)
+    {
+        if (format === this._format && this._originalTextIsValid)
+            return this._originalText;
 
-canonicalHSLA:function()
-{
-var hsla=this.hsla();
-return[Math.round(hsla[0]*360),Math.round(hsla[1]*100),Math.round(hsla[2]*100),hsla[3]];
-},
+        if (!format)
+            format = this._format;
 
+        /**
+         * @param {number} value
+         * @return {number}
+         */
+        function toRgbValue(value)
+        {
+            return Math.round(value * 255);
+        }
 
+        /**
+         * @param {number} value
+         * @return {string}
+         */
+        function toHexValue(value)
+        {
+            var hex = Math.round(value * 255).toString(16);
+            return hex.length === 1 ? "0" + hex : hex;
+        }
 
+        /**
+         * @param {number} value
+         * @return {string}
+         */
+        function toShortHexValue(value)
+        {
+            return (Math.round(value * 255) / 17).toString(16);
+        }
 
-hsva:function()
-{
-var hsla=this.hsla();
-var h=hsla[0];
-var s=hsla[1];
-var l=hsla[2];
+        switch (format) {
+        case WebInspector.Color.Format.Original:
+            return this._originalText;
+        case WebInspector.Color.Format.RGB:
+            if (this.hasAlpha())
+                return null;
+            return String.sprintf("rgb(%d, %d, %d)", toRgbValue(this._rgba[0]), toRgbValue(this._rgba[1]), toRgbValue(this._rgba[2]));
+        case WebInspector.Color.Format.RGBA:
+            return String.sprintf("rgba(%d, %d, %d, %f)", toRgbValue(this._rgba[0]), toRgbValue(this._rgba[1]), toRgbValue(this._rgba[2]), this._rgba[3]);
+        case WebInspector.Color.Format.HSL:
+            if (this.hasAlpha())
+                return null;
+            var hsl = this.hsla();
+            return String.sprintf("hsl(%d, %d%, %d%)", Math.round(hsl[0] * 360), Math.round(hsl[1] * 100), Math.round(hsl[2] * 100));
+        case WebInspector.Color.Format.HSLA:
+            var hsla = this.hsla();
+            return String.sprintf("hsla(%d, %d%, %d%, %f)", Math.round(hsla[0] * 360), Math.round(hsla[1] * 100), Math.round(hsla[2] * 100), hsla[3]);
+        case WebInspector.Color.Format.HEX:
+            if (this.hasAlpha())
+                return null;
+            return String.sprintf("#%s%s%s", toHexValue(this._rgba[0]), toHexValue(this._rgba[1]), toHexValue(this._rgba[2])).toLowerCase();
+        case WebInspector.Color.Format.ShortHEX:
+            if (!this.canBeShortHex())
+                return null;
+            return String.sprintf("#%s%s%s", toShortHexValue(this._rgba[0]), toShortHexValue(this._rgba[1]), toShortHexValue(this._rgba[2])).toLowerCase();
+        case WebInspector.Color.Format.Nickname:
+            return this.nickname();
+        }
 
-s*=l<0.5?l:1-l;
-return[h,s!==0?2*s/(l+s):0,l+s,hsla[3]];
-},
+        return this._originalText;
+    },
 
 
+    /**
+     * @return {!Array<number>}
+     */
+    rgba: function()
+    {
+        return this._rgba.slice();
+    },
 
+    /**
+     * @return {!Array.<number>}
+     */
+    canonicalRGBA: function()
+    {
+        var rgba = new Array(4);
+        for (var i = 0; i < 3; ++i)
+            rgba[i] = Math.round(this._rgba[i] * 255);
+        rgba[3] = this._rgba[3];
+        return rgba;
+    },
 
-hasAlpha:function()
-{
-return this._rgba[3]!==1;
-},
+    /**
+     * @return {?string} nickname
+     */
+    nickname: function()
+    {
+        if (!WebInspector.Color._rgbaToNickname) {
+            WebInspector.Color._rgbaToNickname = {};
+            for (var nickname in WebInspector.Color.Nicknames) {
+                var rgba = WebInspector.Color.Nicknames[nickname];
+                if (rgba.length !== 4)
+                    rgba = rgba.concat(1);
+                WebInspector.Color._rgbaToNickname[rgba] = nickname;
+            }
+        }
 
+        return WebInspector.Color._rgbaToNickname[this.canonicalRGBA()] || null;
+    },
 
+    /**
+     * @return {!DOMAgent.RGBA}
+     */
+    toProtocolRGBA: function()
+    {
+        var rgba = this.canonicalRGBA();
+        var result = { r: rgba[0], g: rgba[1], b: rgba[2] };
+        if (rgba[3] !== 1)
+            result.a = rgba[3];
+        return result;
+    },
 
+    /**
+     * @return {!WebInspector.Color}
+     */
+    invert: function()
+    {
+        var rgba = [];
+        rgba[0] = 1 - this._rgba[0];
+        rgba[1] = 1 - this._rgba[1];
+        rgba[2] = 1 - this._rgba[2];
+        rgba[3] = this._rgba[3];
+        return new WebInspector.Color(rgba, WebInspector.Color.Format.RGBA);
+    },
 
-canBeShortHex:function()
-{
-if(this.hasAlpha())
-return false;
-for(var i=0;i<3;++i){
-var c=Math.round(this._rgba[i]*255);
-if(c%17)
-return false;
+    /**
+     * @param {number} alpha
+     * @return {!WebInspector.Color}
+     */
+    setAlpha: function(alpha)
+    {
+        var rgba = this._rgba.slice();
+        rgba[3] = alpha;
+        return new WebInspector.Color(rgba, WebInspector.Color.Format.RGBA);
+    }
 }
-return true;
-},
 
-
-
-
-asString:function(format)
+/**
+ * @param {string} value
+ * return {number}
+ */
+WebInspector.Color._parseRgbNumeric = function(value)
 {
-if(format===this._format&&this._originalTextIsValid)
-return this._originalText;
+    var parsed = parseInt(value, 10);
+    if (value.indexOf("%") !== -1)
+        parsed /= 100;
+    else
+        parsed /= 255;
+    return parsed;
+}
 
-if(!format)
-format=this._format;
-
-
-
-
-
-function toRgbValue(value)
+/**
+ * @param {string} value
+ * return {number}
+ */
+WebInspector.Color._parseHueNumeric = function(value)
 {
-return Math.round(value*255);
+    return isNaN(value) ? 0 : (parseFloat(value) / 360) % 1;
 }
 
-
-
-
-
-function toHexValue(value)
+/**
+ * @param {string} value
+ * return {number}
+ */
+WebInspector.Color._parseSatLightNumeric = function(value)
 {
-var hex=Math.round(value*255).toString(16);
-return hex.length===1?"0"+hex:hex;
+    return Math.min(1, parseFloat(value) / 100);
 }
 
-
-
-
-
-function toShortHexValue(value)
+/**
+ * @param {string} value
+ * return {number}
+ */
+WebInspector.Color._parseAlphaNumeric = function(value)
 {
-return(Math.round(value*255)/17).toString(16);
+    return isNaN(value) ? 0 : parseFloat(value);
 }
 
-switch(format){
-case WebInspector.Color.Format.Original:
-return this._originalText;
-case WebInspector.Color.Format.RGB:
-if(this.hasAlpha())
-return null;
-return String.sprintf("rgb(%d, %d, %d)",toRgbValue(this._rgba[0]),toRgbValue(this._rgba[1]),toRgbValue(this._rgba[2]));
-case WebInspector.Color.Format.RGBA:
-return String.sprintf("rgba(%d, %d, %d, %f)",toRgbValue(this._rgba[0]),toRgbValue(this._rgba[1]),toRgbValue(this._rgba[2]),this._rgba[3]);
-case WebInspector.Color.Format.HSL:
-if(this.hasAlpha())
-return null;
-var hsl=this.hsla();
-return String.sprintf("hsl(%d, %d%, %d%)",Math.round(hsl[0]*360),Math.round(hsl[1]*100),Math.round(hsl[2]*100));
-case WebInspector.Color.Format.HSLA:
-var hsla=this.hsla();
-return String.sprintf("hsla(%d, %d%, %d%, %f)",Math.round(hsla[0]*360),Math.round(hsla[1]*100),Math.round(hsla[2]*100),hsla[3]);
-case WebInspector.Color.Format.HEX:
-if(this.hasAlpha())
-return null;
-return String.sprintf("#%s%s%s",toHexValue(this._rgba[0]),toHexValue(this._rgba[1]),toHexValue(this._rgba[2])).toLowerCase();
-case WebInspector.Color.Format.ShortHEX:
-if(!this.canBeShortHex())
-return null;
-return String.sprintf("#%s%s%s",toShortHexValue(this._rgba[0]),toShortHexValue(this._rgba[1]),toShortHexValue(this._rgba[2])).toLowerCase();
-case WebInspector.Color.Format.Nickname:
-return this.nickname();}
-
-
-return this._originalText;
-},
-
-
-
-
-
-rgba:function()
-{
-return this._rgba.slice();
-},
-
-
-
-
-canonicalRGBA:function()
+/**
+ * @param {!Array.<number>} hsva
+ * @param {!Array.<number>} out_hsla
+ */
+WebInspector.Color._hsva2hsla = function(hsva, out_hsla)
 {
-var rgba=new Array(4);
-for(var i=0;i<3;++i)
-rgba[i]=Math.round(this._rgba[i]*255);
-rgba[3]=this._rgba[3];
-return rgba;
-},
+    var h = hsva[0];
+    var s = hsva[1];
+    var v = hsva[2];
 
+    var t = (2 - s) * v;
+    if (v === 0 || s === 0)
+        s = 0;
+    else
+        s *= v / (t < 1 ? t : 2 - t);
 
-
-
-nickname:function()
-{
-if(!WebInspector.Color._rgbaToNickname){
-WebInspector.Color._rgbaToNickname={};
-for(var nickname in WebInspector.Color.Nicknames){
-var rgba=WebInspector.Color.Nicknames[nickname];
-if(rgba.length!==4)
-rgba=rgba.concat(1);
-WebInspector.Color._rgbaToNickname[rgba]=nickname;
-}
+    out_hsla[0] = h;
+    out_hsla[1] = s;
+    out_hsla[2] = t / 2;
+    out_hsla[3] = hsva[3];
 }
 
-return WebInspector.Color._rgbaToNickname[this.canonicalRGBA()]||null;
-},
-
-
-
-
-toProtocolRGBA:function()
-{
-var rgba=this.canonicalRGBA();
-var result={r:rgba[0],g:rgba[1],b:rgba[2]};
-if(rgba[3]!==1)
-result.a=rgba[3];
-return result;
-},
-
-
-
-
-invert:function()
+/**
+ * @param {!Array.<number>} hsl
+ * @param {!Array.<number>} out_rgb
+ */
+WebInspector.Color.hsl2rgb = function(hsl, out_rgb)
 {
-var rgba=[];
-rgba[0]=1-this._rgba[0];
-rgba[1]=1-this._rgba[1];
-rgba[2]=1-this._rgba[2];
-rgba[3]=this._rgba[3];
-return new WebInspector.Color(rgba,WebInspector.Color.Format.RGBA);
-},
+    var h = hsl[0];
+    var s = hsl[1];
+    var l = hsl[2];
 
+    function hue2rgb(p, q, h)
+    {
+        if (h < 0)
+            h += 1;
+        else if (h > 1)
+            h -= 1;
 
+        if ((h * 6) < 1)
+            return p + (q - p) * h * 6;
+        else if ((h * 2) < 1)
+            return q;
+        else if ((h * 3) < 2)
+            return p + (q - p) * ((2 / 3) - h) * 6;
+        else
+            return p;
+    }
 
+    if (s < 0)
+        s = 0;
 
+    if (l <= 0.5)
+        var q = l * (1 + s);
+    else
+        var q = l + s - (l * s);
 
-setAlpha:function(alpha)
-{
-var rgba=this._rgba.slice();
-rgba[3]=alpha;
-return new WebInspector.Color(rgba,WebInspector.Color.Format.RGBA);
-}};
+    var p = 2 * l - q;
 
+    var tr = h + (1 / 3);
+    var tg = h;
+    var tb = h - (1 / 3);
 
+    out_rgb[0] = hue2rgb(p, q, tr);
+    out_rgb[1] = hue2rgb(p, q, tg);
+    out_rgb[2] = hue2rgb(p, q, tb);
+    out_rgb[3] = hsl[3];
+}
 
-
-
-
-WebInspector.Color._parseRgbNumeric=function(value)
+/**
+ * @param {!Array<number>} hsva
+ * @param {!Array<number>} out_rgba
+ */
+WebInspector.Color.hsva2rgba = function(hsva, out_rgba)
 {
-var parsed=parseInt(value,10);
-if(value.indexOf("%")!==-1)
-parsed/=100;else
+    WebInspector.Color._hsva2hsla(hsva, WebInspector.Color.hsva2rgba._tmpHSLA);
+    WebInspector.Color.hsl2rgb(WebInspector.Color.hsva2rgba._tmpHSLA, out_rgba);
 
-parsed/=255;
-return parsed;
+    for (var i = 0; i < WebInspector.Color.hsva2rgba._tmpHSLA.length; i++)
+        WebInspector.Color.hsva2rgba._tmpHSLA[i] = 0;
 };
 
-
-
+/** @type {!Array<number>} */
+WebInspector.Color.hsva2rgba._tmpHSLA = [0, 0, 0, 0];
 
 
-WebInspector.Color._parseHueNumeric=function(value)
+/**
+ * Calculate the luminance of this color using the WCAG algorithm.
+ * See http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
+ * @param {!Array<number>} rgba
+ * @return {number}
+ */
+WebInspector.Color.luminance = function(rgba)
 {
-return isNaN(value)?0:parseFloat(value)/360%1;
-};
-
-
+    var rSRGB = rgba[0];
+    var gSRGB = rgba[1];
+    var bSRGB = rgba[2];
 
+    var r = rSRGB <= 0.03928 ? rSRGB / 12.92 : Math.pow(((rSRGB + 0.055) / 1.055), 2.4);
+    var g = gSRGB <= 0.03928 ? gSRGB / 12.92 : Math.pow(((gSRGB + 0.055) / 1.055), 2.4);
+    var b = bSRGB <= 0.03928 ? bSRGB / 12.92 : Math.pow(((bSRGB + 0.055) / 1.055), 2.4);
 
+    return 0.2126 * r + 0.7152 * g + 0.0722 * b;
+}
 
-WebInspector.Color._parseSatLightNumeric=function(value)
+/**
+ * Combine the two given color according to alpha blending.
+ * @param {!Array<number>} fgRGBA
+ * @param {!Array<number>} bgRGBA
+ * @param {!Array<number>} out_blended
+ */
+WebInspector.Color.blendColors = function(fgRGBA, bgRGBA, out_blended)
 {
-return Math.min(1,parseFloat(value)/100);
-};
+    var alpha = fgRGBA[3];
 
+    out_blended[0] = ((1 - alpha) * bgRGBA[0]) + (alpha * fgRGBA[0]);
+    out_blended[1] = ((1 - alpha) * bgRGBA[1]) + (alpha * fgRGBA[1]);
+    out_blended[2] = ((1 - alpha) * bgRGBA[2]) + (alpha * fgRGBA[2]);
+    out_blended[3] = alpha + (bgRGBA[3] * (1 - alpha));
+}
 
-
-
-
-WebInspector.Color._parseAlphaNumeric=function(value)
-{
-return isNaN(value)?0:parseFloat(value);
-};
-
-
-
-
-
-WebInspector.Color._hsva2hsla=function(hsva,out_hsla)
-{
-var h=hsva[0];
-var s=hsva[1];
-var v=hsva[2];
-
-var t=(2-s)*v;
-if(v===0||s===0)
-s=0;else
-
-s*=v/(t<1?t:2-t);
-
-out_hsla[0]=h;
-out_hsla[1]=s;
-out_hsla[2]=t/2;
-out_hsla[3]=hsva[3];
-};
-
-
-
-
-
-WebInspector.Color.hsl2rgb=function(hsl,out_rgb)
+/**
+ * Calculate the contrast ratio between a foreground and a background color.
+ * Returns the ratio to 1, for example for two two colors with a contrast ratio of 21:1, this function will return 21.
+ * See http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
+ * @param {!Array<number>} fgRGBA
+ * @param {!Array<number>} bgRGBA
+ * @return {number}
+ */
+WebInspector.Color.calculateContrastRatio = function(fgRGBA, bgRGBA)
 {
-var h=hsl[0];
-var s=hsl[1];
-var l=hsl[2];
+    WebInspector.Color.blendColors(fgRGBA, bgRGBA, WebInspector.Color.calculateContrastRatio._blendedFg);
 
-function hue2rgb(p,q,h)
-{
-if(h<0)
-h+=1;else
-if(h>1)
-h-=1;
+    var fgLuminance = WebInspector.Color.luminance(WebInspector.Color.calculateContrastRatio._blendedFg);
+    var bgLuminance = WebInspector.Color.luminance(bgRGBA);
+    var contrastRatio = (Math.max(fgLuminance, bgLuminance) + 0.05) /
+        (Math.min(fgLuminance, bgLuminance) + 0.05);
 
-if(h*6<1)
-return p+(q-p)*h*6;else
-if(h*2<1)
-return q;else
-if(h*3<2)
-return p+(q-p)*(2/3-h)*6;else
+    for (var i = 0; i < WebInspector.Color.calculateContrastRatio._blendedFg.length; i++)
+        WebInspector.Color.calculateContrastRatio._blendedFg[i] = 0;
 
-return p;
+    return contrastRatio;
 }
 
-if(s<0)
-s=0;
+WebInspector.Color.calculateContrastRatio._blendedFg = [0, 0, 0, 0];
 
-if(l<=0.5)
-var q=l*(1+s);else
-
-var q=l+s-l*s;
-
-var p=2*l-q;
-
-var tr=h+1/3;
-var tg=h;
-var tb=h-1/3;
-
-out_rgb[0]=hue2rgb(p,q,tr);
-out_rgb[1]=hue2rgb(p,q,tg);
-out_rgb[2]=hue2rgb(p,q,tb);
-out_rgb[3]=hsl[3];
-};
-
-
-
-
-
-WebInspector.Color.hsva2rgba=function(hsva,out_rgba)
+/**
+ * Compute a desired luminance given a given luminance and a desired contrast
+ * ratio.
+ * @param {number} luminance The given luminance.
+ * @param {number} contrast The desired contrast ratio.
+ * @param {boolean} lighter Whether the desired luminance is lighter or darker
+ * than the given luminance. If no luminance can be found which meets this
+ * requirement, a luminance which meets the inverse requirement will be
+ * returned.
+ * @return {number} The desired luminance.
+ */
+WebInspector.Color.desiredLuminance = function(luminance, contrast, lighter)
 {
-WebInspector.Color._hsva2hsla(hsva,WebInspector.Color.hsva2rgba._tmpHSLA);
-WebInspector.Color.hsl2rgb(WebInspector.Color.hsva2rgba._tmpHSLA,out_rgba);
-
-for(var i=0;i<WebInspector.Color.hsva2rgba._tmpHSLA.length;i++)
-WebInspector.Color.hsva2rgba._tmpHSLA[i]=0;
+    function computeLuminance()
+    {
+        if (lighter)
+            return (luminance + 0.05) * contrast - 0.05;
+        else
+            return (luminance + 0.05) / contrast - 0.05;
+    }
+    var desiredLuminance = computeLuminance();
+    if (desiredLuminance < 0 || desiredLuminance > 1) {
+        lighter = !lighter;
+        desiredLuminance = computeLuminance();
+    }
+    return desiredLuminance;
 };
 
 
-WebInspector.Color.hsva2rgba._tmpHSLA=[0,0,0,0];
-
-
-
-
-
-
-
-
-WebInspector.Color.luminance=function(rgba)
-{
-var rSRGB=rgba[0];
-var gSRGB=rgba[1];
-var bSRGB=rgba[2];
-
-var r=rSRGB<=0.03928?rSRGB/12.92:Math.pow((rSRGB+0.055)/1.055,2.4);
-var g=gSRGB<=0.03928?gSRGB/12.92:Math.pow((gSRGB+0.055)/1.055,2.4);
-var b=bSRGB<=0.03928?bSRGB/12.92:Math.pow((bSRGB+0.055)/1.055,2.4);
-
-return 0.2126*r+0.7152*g+0.0722*b;
-};
-
-
-
-
-
-
-
-WebInspector.Color.blendColors=function(fgRGBA,bgRGBA,out_blended)
-{
-var alpha=fgRGBA[3];
-
-out_blended[0]=(1-alpha)*bgRGBA[0]+alpha*fgRGBA[0];
-out_blended[1]=(1-alpha)*bgRGBA[1]+alpha*fgRGBA[1];
-out_blended[2]=(1-alpha)*bgRGBA[2]+alpha*fgRGBA[2];
-out_blended[3]=alpha+bgRGBA[3]*(1-alpha);
+WebInspector.Color.Nicknames = {
+    "aliceblue":          [240,248,255],
+    "antiquewhite":       [250,235,215],
+    "aqua":               [0,255,255],
+    "aquamarine":         [127,255,212],
+    "azure":              [240,255,255],
+    "beige":              [245,245,220],
+    "bisque":             [255,228,196],
+    "black":              [0,0,0],
+    "blanchedalmond":     [255,235,205],
+    "blue":               [0,0,255],
+    "blueviolet":         [138,43,226],
+    "brown":              [165,42,42],
+    "burlywood":          [222,184,135],
+    "cadetblue":          [95,158,160],
+    "chartreuse":         [127,255,0],
+    "chocolate":          [210,105,30],
+    "coral":              [255,127,80],
+    "cornflowerblue":     [100,149,237],
+    "cornsilk":           [255,248,220],
+    "crimson":            [237,20,61],
+    "cyan":               [0,255,255],
+    "darkblue":           [0,0,139],
+    "darkcyan":           [0,139,139],
+    "darkgoldenrod":      [184,134,11],
+    "darkgray":           [169,169,169],
+    "darkgrey":           [169,169,169],
+    "darkgreen":          [0,100,0],
+    "darkkhaki":          [189,183,107],
+    "darkmagenta":        [139,0,139],
+    "darkolivegreen":     [85,107,47],
+    "darkorange":         [255,140,0],
+    "darkorchid":         [153,50,204],
+    "darkred":            [139,0,0],
+    "darksalmon":         [233,150,122],
+    "darkseagreen":       [143,188,143],
+    "darkslateblue":      [72,61,139],
+    "darkslategray":      [47,79,79],
+    "darkslategrey":      [47,79,79],
+    "darkturquoise":      [0,206,209],
+    "darkviolet":         [148,0,211],
+    "deeppink":           [255,20,147],
+    "deepskyblue":        [0,191,255],
+    "dimgray":            [105,105,105],
+    "dimgrey":            [105,105,105],
+    "dodgerblue":         [30,144,255],
+    "firebrick":          [178,34,34],
+    "floralwhite":        [255,250,240],
+    "forestgreen":        [34,139,34],
+    "fuchsia":            [255,0,255],
+    "gainsboro":          [220,220,220],
+    "ghostwhite":         [248,248,255],
+    "gold":               [255,215,0],
+    "goldenrod":          [218,165,32],
+    "gray":               [128,128,128],
+    "grey":               [128,128,128],
+    "green":              [0,128,0],
+    "greenyellow":        [173,255,47],
+    "honeydew":           [240,255,240],
+    "hotpink":            [255,105,180],
+    "indianred":          [205,92,92],
+    "indigo":             [75,0,130],
+    "ivory":              [255,255,240],
+    "khaki":              [240,230,140],
+    "lavender":           [230,230,250],
+    "lavenderblush":      [255,240,245],
+    "lawngreen":          [124,252,0],
+    "lemonchiffon":       [255,250,205],
+    "lightblue":          [173,216,230],
+    "lightcoral":         [240,128,128],
+    "lightcyan":          [224,255,255],
+    "lightgoldenrodyellow":[250,250,210],
+    "lightgreen":         [144,238,144],
+    "lightgray":          [211,211,211],
+    "lightgrey":          [211,211,211],
+    "lightpink":          [255,182,193],
+    "lightsalmon":        [255,160,122],
+    "lightseagreen":      [32,178,170],
+    "lightskyblue":       [135,206,250],
+    "lightslategray":     [119,136,153],
+    "lightslategrey":     [119,136,153],
+    "lightsteelblue":     [176,196,222],
+    "lightyellow":        [255,255,224],
+    "lime":               [0,255,0],
+    "limegreen":          [50,205,50],
+    "linen":              [250,240,230],
+    "magenta":            [255,0,255],
+    "maroon":             [128,0,0],
+    "mediumaquamarine":   [102,205,170],
+    "mediumblue":         [0,0,205],
+    "mediumorchid":       [186,85,211],
+    "mediumpurple":       [147,112,219],
+    "mediumseagreen":     [60,179,113],
+    "mediumslateblue":    [123,104,238],
+    "mediumspringgreen":  [0,250,154],
+    "mediumturquoise":    [72,209,204],
+    "mediumvioletred":    [199,21,133],
+    "midnightblue":       [25,25,112],
+    "mintcream":          [245,255,250],
+    "mistyrose":          [255,228,225],
+    "moccasin":           [255,228,181],
+    "navajowhite":        [255,222,173],
+    "navy":               [0,0,128],
+    "oldlace":            [253,245,230],
+    "olive":              [128,128,0],
+    "olivedrab":          [107,142,35],
+    "orange":             [255,165,0],
+    "orangered":          [255,69,0],
+    "orchid":             [218,112,214],
+    "palegoldenrod":      [238,232,170],
+    "palegreen":          [152,251,152],
+    "paleturquoise":      [175,238,238],
+    "palevioletred":      [219,112,147],
+    "papayawhip":         [255,239,213],
+    "peachpuff":          [255,218,185],
+    "peru":               [205,133,63],
+    "pink":               [255,192,203],
+    "plum":               [221,160,221],
+    "powderblue":         [176,224,230],
+    "purple":             [128,0,128],
+    "rebeccapurple":      [102,51,153],
+    "red":                [255,0,0],
+    "rosybrown":          [188,143,143],
+    "royalblue":          [65,105,225],
+    "saddlebrown":        [139,69,19],
+    "salmon":             [250,128,114],
+    "sandybrown":         [244,164,96],
+    "seagreen":           [46,139,87],
+    "seashell":           [255,245,238],
+    "sienna":             [160,82,45],
+    "silver":             [192,192,192],
+    "skyblue":            [135,206,235],
+    "slateblue":          [106,90,205],
+    "slategray":          [112,128,144],
+    "slategrey":          [112,128,144],
+    "snow":               [255,250,250],
+    "springgreen":        [0,255,127],
+    "steelblue":          [70,130,180],
+    "tan":                [210,180,140],
+    "teal":               [0,128,128],
+    "thistle":            [216,191,216],
+    "tomato":             [255,99,71],
+    "turquoise":          [64,224,208],
+    "violet":             [238,130,238],
+    "wheat":              [245,222,179],
+    "white":              [255,255,255],
+    "whitesmoke":         [245,245,245],
+    "yellow":             [255,255,0],
+    "yellowgreen":        [154,205,50],
+    "transparent":        [0, 0, 0, 0],
 };
 
+WebInspector.Color.PageHighlight = {
+    Content: WebInspector.Color.fromRGBA([111, 168, 220, .66]),
+    ContentLight: WebInspector.Color.fromRGBA([111, 168, 220, .5]),
+    ContentOutline: WebInspector.Color.fromRGBA([9, 83, 148]),
+    Padding: WebInspector.Color.fromRGBA([147, 196, 125, .55]),
+    PaddingLight: WebInspector.Color.fromRGBA([147, 196, 125, .4]),
+    Border: WebInspector.Color.fromRGBA([255, 229, 153, .66]),
+    BorderLight: WebInspector.Color.fromRGBA([255, 229, 153, .5]),
+    Margin: WebInspector.Color.fromRGBA([246, 178, 107, .66]),
+    MarginLight: WebInspector.Color.fromRGBA([246, 178, 107, .5]),
+    EventTarget: WebInspector.Color.fromRGBA([255, 196, 196, .66]),
+    Shape: WebInspector.Color.fromRGBA([96, 82, 177, 0.8]),
+    ShapeMargin: WebInspector.Color.fromRGBA([96, 82, 127, .6])
+}
 
-
-
-
-
-
-
-
-WebInspector.Color.calculateContrastRatio=function(fgRGBA,bgRGBA)
+/**
+ * @param {!WebInspector.Color} color
+ * @return {!WebInspector.Color.Format}
+ */
+WebInspector.Color.detectColorFormat = function(color)
 {
-WebInspector.Color.blendColors(fgRGBA,bgRGBA,WebInspector.Color.calculateContrastRatio._blendedFg);
+    const cf = WebInspector.Color.Format;
+    var format;
+    var formatSetting = WebInspector.moduleSetting("colorFormat").get();
+    if (formatSetting === cf.Original)
+        format = cf.Original;
+    else if (formatSetting === cf.RGB)
+        format = (color.hasAlpha() ? cf.RGBA : cf.RGB);
+    else if (formatSetting === cf.HSL)
+        format = (color.hasAlpha() ? cf.HSLA : cf.HSL);
+    else if (!color.hasAlpha())
+        format = (color.canBeShortHex() ? cf.ShortHEX : cf.HEX);
+    else
+        format = cf.RGBA;
 
-var fgLuminance=WebInspector.Color.luminance(WebInspector.Color.calculateContrastRatio._blendedFg);
-var bgLuminance=WebInspector.Color.luminance(bgRGBA);
-var contrastRatio=(Math.max(fgLuminance,bgLuminance)+0.05)/(
-Math.min(fgLuminance,bgLuminance)+0.05);
+    return format;
+}
 
-for(var i=0;i<WebInspector.Color.calculateContrastRatio._blendedFg.length;i++)
-WebInspector.Color.calculateContrastRatio._blendedFg[i]=0;
+},{}],236:[function(require,module,exports){
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
 
-return contrastRatio;
-};
+/**
+ * @constructor
+ * @implements {WebInspector.EventTarget}
+ */
+WebInspector.Object = function() {
+}
 
-WebInspector.Color.calculateContrastRatio._blendedFg=[0,0,0,0];
-
-
+WebInspector.Object.prototype = {
+    /**
+     * @override
+     * @param {string|symbol} eventType
+     * @param {function(!WebInspector.Event)} listener
+     * @param {!Object=} thisObject
+     * @return {!WebInspector.EventTarget.EventDescriptor}
+     */
+    addEventListener: function(eventType, listener, thisObject)
+    {
+        if (!listener)
+            console.assert(false);
 
+        if (!this._listeners)
+            this._listeners = new Map();
+        if (!this._listeners.has(eventType))
+            this._listeners.set(eventType, []);
+        this._listeners.get(eventType).push({ thisObject: thisObject, listener: listener });
+        return new WebInspector.EventTarget.EventDescriptor(this, eventType, thisObject, listener);
+    },
 
+    /**
+     * @override
+     * @param {string|symbol} eventType
+     * @param {function(!WebInspector.Event)} listener
+     * @param {!Object=} thisObject
+     */
+    removeEventListener: function(eventType, listener, thisObject)
+    {
+        console.assert(listener);
 
+        if (!this._listeners || !this._listeners.has(eventType))
+            return;
+        var listeners = this._listeners.get(eventType);
+        for (var i = 0; i < listeners.length; ++i) {
+            if (listeners[i].listener === listener && listeners[i].thisObject === thisObject)
+                listeners.splice(i--, 1);
+        }
 
+        if (!listeners.length)
+            this._listeners.delete(eventType);
+    },
 
+    /**
+     * @override
+     */
+    removeAllListeners: function()
+    {
+        delete this._listeners;
+    },
 
+    /**
+     * @override
+     * @param {string|symbol} eventType
+     * @return {boolean}
+     */
+    hasEventListeners: function(eventType)
+    {
+        return this._listeners && this._listeners.has(eventType);
+    },
 
+    /**
+     * @override
+     * @param {string|symbol} eventType
+     * @param {*=} eventData
+     * @return {boolean}
+     */
+    dispatchEventToListeners: function(eventType, eventData)
+    {
+        if (!this._listeners || !this._listeners.has(eventType))
+            return false;
 
+        var event = new WebInspector.Event(this, eventType, eventData);
+        var listeners = this._listeners.get(eventType).slice(0);
+        for (var i = 0; i < listeners.length; ++i) {
+            listeners[i].listener.call(listeners[i].thisObject, event);
+            if (event._stoppedPropagation)
+                break;
+        }
 
+        return event.defaultPrevented;
+    }
+}
 
-WebInspector.Color.desiredLuminance=function(luminance,contrast,lighter)
+/**
+ * @constructor
+ * @param {!WebInspector.EventTarget} target
+ * @param {string|symbol} type
+ * @param {*=} data
+ */
+WebInspector.Event = function(target, type, data)
 {
-function computeLuminance()
-{
-if(lighter)
-return(luminance+0.05)*contrast-0.05;else
-
-return(luminance+0.05)/contrast-0.05;
-}
-var desiredLuminance=computeLuminance();
-if(desiredLuminance<0||desiredLuminance>1){
-lighter=!lighter;
-desiredLuminance=computeLuminance();
+    this.target = target;
+    this.type = type;
+    this.data = data;
+    this.defaultPrevented = false;
+    this._stoppedPropagation = false;
 }
-return desiredLuminance;
-};
 
+WebInspector.Event.prototype = {
+    stopPropagation: function()
+    {
+        this._stoppedPropagation = true;
+    },
 
-WebInspector.Color.Nicknames={
-"aliceblue":[240,248,255],
-"antiquewhite":[250,235,215],
-"aqua":[0,255,255],
-"aquamarine":[127,255,212],
-"azure":[240,255,255],
-"beige":[245,245,220],
-"bisque":[255,228,196],
-"black":[0,0,0],
-"blanchedalmond":[255,235,205],
-"blue":[0,0,255],
-"blueviolet":[138,43,226],
-"brown":[165,42,42],
-"burlywood":[222,184,135],
-"cadetblue":[95,158,160],
-"chartreuse":[127,255,0],
-"chocolate":[210,105,30],
-"coral":[255,127,80],
-"cornflowerblue":[100,149,237],
-"cornsilk":[255,248,220],
-"crimson":[237,20,61],
-"cyan":[0,255,255],
-"darkblue":[0,0,139],
-"darkcyan":[0,139,139],
-"darkgoldenrod":[184,134,11],
-"darkgray":[169,169,169],
-"darkgrey":[169,169,169],
-"darkgreen":[0,100,0],
-"darkkhaki":[189,183,107],
-"darkmagenta":[139,0,139],
-"darkolivegreen":[85,107,47],
-"darkorange":[255,140,0],
-"darkorchid":[153,50,204],
-"darkred":[139,0,0],
-"darksalmon":[233,150,122],
-"darkseagreen":[143,188,143],
-"darkslateblue":[72,61,139],
-"darkslategray":[47,79,79],
-"darkslategrey":[47,79,79],
-"darkturquoise":[0,206,209],
-"darkviolet":[148,0,211],
-"deeppink":[255,20,147],
-"deepskyblue":[0,191,255],
-"dimgray":[105,105,105],
-"dimgrey":[105,105,105],
-"dodgerblue":[30,144,255],
-"firebrick":[178,34,34],
-"floralwhite":[255,250,240],
-"forestgreen":[34,139,34],
-"fuchsia":[255,0,255],
-"gainsboro":[220,220,220],
-"ghostwhite":[248,248,255],
-"gold":[255,215,0],
-"goldenrod":[218,165,32],
-"gray":[128,128,128],
-"grey":[128,128,128],
-"green":[0,128,0],
-"greenyellow":[173,255,47],
-"honeydew":[240,255,240],
-"hotpink":[255,105,180],
-"indianred":[205,92,92],
-"indigo":[75,0,130],
-"ivory":[255,255,240],
-"khaki":[240,230,140],
-"lavender":[230,230,250],
-"lavenderblush":[255,240,245],
-"lawngreen":[124,252,0],
-"lemonchiffon":[255,250,205],
-"lightblue":[173,216,230],
-"lightcoral":[240,128,128],
-"lightcyan":[224,255,255],
-"lightgoldenrodyellow":[250,250,210],
-"lightgreen":[144,238,144],
-"lightgray":[211,211,211],
-"lightgrey":[211,211,211],
-"lightpink":[255,182,193],
-"lightsalmon":[255,160,122],
-"lightseagreen":[32,178,170],
-"lightskyblue":[135,206,250],
-"lightslategray":[119,136,153],
-"lightslategrey":[119,136,153],
-"lightsteelblue":[176,196,222],
-"lightyellow":[255,255,224],
-"lime":[0,255,0],
-"limegreen":[50,205,50],
-"linen":[250,240,230],
-"magenta":[255,0,255],
-"maroon":[128,0,0],
-"mediumaquamarine":[102,205,170],
-"mediumblue":[0,0,205],
-"mediumorchid":[186,85,211],
-"mediumpurple":[147,112,219],
-"mediumseagreen":[60,179,113],
-"mediumslateblue":[123,104,238],
-"mediumspringgreen":[0,250,154],
-"mediumturquoise":[72,209,204],
-"mediumvioletred":[199,21,133],
-"midnightblue":[25,25,112],
-"mintcream":[245,255,250],
-"mistyrose":[255,228,225],
-"moccasin":[255,228,181],
-"navajowhite":[255,222,173],
-"navy":[0,0,128],
-"oldlace":[253,245,230],
-"olive":[128,128,0],
-"olivedrab":[107,142,35],
-"orange":[255,165,0],
-"orangered":[255,69,0],
-"orchid":[218,112,214],
-"palegoldenrod":[238,232,170],
-"palegreen":[152,251,152],
-"paleturquoise":[175,238,238],
-"palevioletred":[219,112,147],
-"papayawhip":[255,239,213],
-"peachpuff":[255,218,185],
-"peru":[205,133,63],
-"pink":[255,192,203],
-"plum":[221,160,221],
-"powderblue":[176,224,230],
-"purple":[128,0,128],
-"rebeccapurple":[102,51,153],
-"red":[255,0,0],
-"rosybrown":[188,143,143],
-"royalblue":[65,105,225],
-"saddlebrown":[139,69,19],
-"salmon":[250,128,114],
-"sandybrown":[244,164,96],
-"seagreen":[46,139,87],
-"seashell":[255,245,238],
-"sienna":[160,82,45],
-"silver":[192,192,192],
-"skyblue":[135,206,235],
-"slateblue":[106,90,205],
-"slategray":[112,128,144],
-"slategrey":[112,128,144],
-"snow":[255,250,250],
-"springgreen":[0,255,127],
-"steelblue":[70,130,180],
-"tan":[210,180,140],
-"teal":[0,128,128],
-"thistle":[216,191,216],
-"tomato":[255,99,71],
-"turquoise":[64,224,208],
-"violet":[238,130,238],
-"wheat":[245,222,179],
-"white":[255,255,255],
-"whitesmoke":[245,245,245],
-"yellow":[255,255,0],
-"yellowgreen":[154,205,50],
-"transparent":[0,0,0,0]};
-
-
-WebInspector.Color.PageHighlight={
-Content:WebInspector.Color.fromRGBA([111,168,220,.66]),
-ContentLight:WebInspector.Color.fromRGBA([111,168,220,.5]),
-ContentOutline:WebInspector.Color.fromRGBA([9,83,148]),
-Padding:WebInspector.Color.fromRGBA([147,196,125,.55]),
-PaddingLight:WebInspector.Color.fromRGBA([147,196,125,.4]),
-Border:WebInspector.Color.fromRGBA([255,229,153,.66]),
-BorderLight:WebInspector.Color.fromRGBA([255,229,153,.5]),
-Margin:WebInspector.Color.fromRGBA([246,178,107,.66]),
-MarginLight:WebInspector.Color.fromRGBA([246,178,107,.5]),
-EventTarget:WebInspector.Color.fromRGBA([255,196,196,.66]),
-Shape:WebInspector.Color.fromRGBA([96,82,177,0.8]),
-ShapeMargin:WebInspector.Color.fromRGBA([96,82,127,.6])};
-
-
-
-
+    preventDefault: function()
+    {
+        this.defaultPrevented = true;
+    },
 
+    /**
+     * @param {boolean=} preventDefault
+     */
+    consume: function(preventDefault)
+    {
+        this.stopPropagation();
+        if (preventDefault)
+            this.preventDefault();
+    }
+}
 
-WebInspector.Color.detectColorFormat=function(color)
+/**
+ * @interface
+ */
+WebInspector.EventTarget = function()
 {
-const cf=WebInspector.Color.Format;
-var format;
-var formatSetting=WebInspector.moduleSetting("colorFormat").get();
-if(formatSetting===cf.Original)
-format=cf.Original;else
-if(formatSetting===cf.RGB)
-format=color.hasAlpha()?cf.RGBA:cf.RGB;else
-if(formatSetting===cf.HSL)
-format=color.hasAlpha()?cf.HSLA:cf.HSL;else
-if(!color.hasAlpha())
-format=color.canBeShortHex()?cf.ShortHEX:cf.HEX;else
+}
 
-format=cf.RGBA;
-
-return format;
-};
-
-},{}],207:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-WebInspector.Object=function(){
-};
-
-WebInspector.Object.prototype={
-
-
-
-
-
-
-
-addEventListener:function(eventType,listener,thisObject)
+/**
+ * @param {!Array<!WebInspector.EventTarget.EventDescriptor>} eventList
+ */
+WebInspector.EventTarget.removeEventListeners = function(eventList)
 {
-if(!listener)
-console.assert(false);
+    for (var i = 0; i < eventList.length; ++i) {
+        var eventInfo = eventList[i];
+        eventInfo.eventTarget.removeEventListener(eventInfo.eventType, eventInfo.method, eventInfo.receiver);
+    }
+    // Do not hold references on unused event descriptors.
+    eventList.splice(0, eventList.length);
+}
 
-if(!this._listeners)
-this._listeners=new Map();
-if(!this._listeners.has(eventType))
-this._listeners.set(eventType,[]);
-this._listeners.get(eventType).push({thisObject:thisObject,listener:listener});
-return new WebInspector.EventTarget.EventDescriptor(this,eventType,thisObject,listener);
-},
-
-
+WebInspector.EventTarget.prototype = {
+    /**
+     * @param {string|symbol} eventType
+     * @param {function(!WebInspector.Event)} listener
+     * @param {!Object=} thisObject
+     * @return {!WebInspector.EventTarget.EventDescriptor}
+     */
+    addEventListener: function(eventType, listener, thisObject) { },
 
+    /**
+     * @param {string|symbol} eventType
+     * @param {function(!WebInspector.Event)} listener
+     * @param {!Object=} thisObject
+     */
+    removeEventListener: function(eventType, listener, thisObject) { },
 
+    removeAllListeners: function() { },
 
+    /**
+     * @param {string|symbol} eventType
+     * @return {boolean}
+     */
+    hasEventListeners: function(eventType) { },
 
+    /**
+     * @param {string|symbol} eventType
+     * @param {*=} eventData
+     * @return {boolean}
+     */
+    dispatchEventToListeners: function(eventType, eventData) { },
+}
 
-removeEventListener:function(eventType,listener,thisObject)
+/**
+ * @constructor
+ * @param {!WebInspector.EventTarget} eventTarget
+ * @param {string|symbol} eventType
+ * @param {(!Object|undefined)} receiver
+ * @param {function(?):?} method
+ */
+WebInspector.EventTarget.EventDescriptor = function(eventTarget, eventType, receiver, method)
 {
-console.assert(listener);
-
-if(!this._listeners||!this._listeners.has(eventType))
-return;
-var listeners=this._listeners.get(eventType);
-for(var i=0;i<listeners.length;++i){
-if(listeners[i].listener===listener&&listeners[i].thisObject===thisObject)
-listeners.splice(i--,1);
+    this.eventTarget = eventTarget;
+    this.eventType = eventType;
+    this.receiver = receiver;
+    this.method = method;
 }
 
-if(!listeners.length)
-this._listeners.delete(eventType);
-},
-
-
-
+},{}],237:[function(require,module,exports){
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC.
+ * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
 
-removeAllListeners:function()
+/**
+ * @constructor
+ * @param {string} url
+ */
+WebInspector.ParsedURL = function(url)
 {
-delete this._listeners;
-},
+    this.isValid = false;
+    this.url = url;
+    this.scheme = "";
+    this.host = "";
+    this.port = "";
+    this.path = "";
+    this.queryParams = "";
+    this.fragment = "";
+    this.folderPathComponents = "";
+    this.lastPathComponent = "";
 
-
-
-
-
-
-hasEventListeners:function(eventType)
-{
-return this._listeners&&this._listeners.has(eventType);
-},
-
-
-
-
+    // RegExp groups:
+    // 1 - scheme (using the RFC3986 grammar)
+    // 2 - hostname
+    // 3 - ?port
+    // 4 - ?path
+    // 5 - ?fragment
+    var match = url.match(/^([A-Za-z][A-Za-z0-9+.-]*):\/\/([^\s\/:]*)(?::([\d]+))?(?:(\/[^#]*)(?:#(.*))?)?$/i);
+    if (match) {
+        this.isValid = true;
+        this.scheme = match[1].toLowerCase();
+        this.host = match[2];
+        this.port = match[3];
+        this.path = match[4] || "/";
+        this.fragment = match[5];
+    } else {
+        if (this.url.startsWith("data:")) {
+            this.scheme = "data";
+            return;
+        }
+        if (this.url === "about:blank") {
+            this.scheme = "about";
+            return;
+        }
+        this.path = this.url;
+    }
 
+    // First cut the query params.
+    var path = this.path;
+    var indexOfQuery = path.indexOf("?");
+    if (indexOfQuery !== -1) {
+        this.queryParams = path.substring(indexOfQuery + 1);
+        path = path.substring(0, indexOfQuery);
+    }
 
+    // Then take last path component.
+    var lastSlashIndex = path.lastIndexOf("/");
+    if (lastSlashIndex !== -1) {
+        this.folderPathComponents = path.substring(0, lastSlashIndex);
+        this.lastPathComponent = path.substring(lastSlashIndex + 1);
+    } else
+        this.lastPathComponent = path;
+}
 
-dispatchEventToListeners:function(eventType,eventData)
+/**
+ * @param {string} url
+ * @return {!Array.<string>}
+ */
+WebInspector.ParsedURL.splitURLIntoPathComponents = function(url)
 {
-if(!this._listeners||!this._listeners.has(eventType))
-return false;
-
-var event=new WebInspector.Event(this,eventType,eventData);
-var listeners=this._listeners.get(eventType).slice(0);
-for(var i=0;i<listeners.length;++i){
-listeners[i].listener.call(listeners[i].thisObject,event);
-if(event._stoppedPropagation)
-break;
+    if (url.startsWith("/"))
+        url = "file://" + url;
+    var parsedURL = new WebInspector.ParsedURL(url);
+    var origin;
+    var folderPath;
+    var name;
+    if (parsedURL.isValid) {
+        origin = parsedURL.scheme + "://" + parsedURL.host;
+        if (parsedURL.port)
+            origin += ":" + parsedURL.port;
+        folderPath = parsedURL.folderPathComponents;
+        name = parsedURL.lastPathComponent;
+        if (parsedURL.queryParams)
+            name += "?" + parsedURL.queryParams;
+    } else {
+        origin = "";
+        folderPath = "";
+        name = url;
+    }
+    var result = [origin];
+    var splittedPath = folderPath.split("/");
+    for (var i = 1; i < splittedPath.length; ++i) {
+        if (!splittedPath[i])
+            continue;
+        result.push(splittedPath[i]);
+    }
+    result.push(name);
+    return result;
 }
 
-return event.defaultPrevented;
-}};
-
-
-
-
-
-
-
-
-WebInspector.Event=function(target,type,data)
+/**
+ * @param {string} url
+ * @return {string}
+ */
+WebInspector.ParsedURL.extractOrigin = function(url)
 {
-this.target=target;
-this.type=type;
-this.data=data;
-this.defaultPrevented=false;
-this._stoppedPropagation=false;
-};
+    var parsedURL = new WebInspector.ParsedURL(url);
+    if (!parsedURL.isValid)
+        return "";
 
-WebInspector.Event.prototype={
-stopPropagation:function()
-{
-this._stoppedPropagation=true;
-},
+    var origin = parsedURL.scheme + "://" + parsedURL.host;
+    if (parsedURL.port)
+        origin += ":" + parsedURL.port;
+    return origin;
+}
 
-preventDefault:function()
+/**
+ * @param {string} url
+ * @return {string}
+ */
+WebInspector.ParsedURL.extractExtension = function(url)
 {
-this.defaultPrevented=true;
-},
-
-
-
+    var lastIndexOfDot = url.lastIndexOf(".");
+    var extension = lastIndexOfDot !== -1 ? url.substr(lastIndexOfDot + 1) : "";
+    var indexOfQuestionMark = extension.indexOf("?");
+    if (indexOfQuestionMark !== -1)
+        extension = extension.substr(0, indexOfQuestionMark);
+    return extension;
+}
 
-consume:function(preventDefault)
+/**
+ * @param {string} url
+ * @return {string}
+ */
+WebInspector.ParsedURL.extractName = function(url)
 {
-this.stopPropagation();
-if(preventDefault)
-this.preventDefault();
-}};
+    var index = url.lastIndexOf("/");
+    return index !== -1 ? url.substr(index + 1) : url;
+}
 
-
-
-
-
-WebInspector.EventTarget=function()
+/**
+ * @param {string} baseURL
+ * @param {string} href
+ * @return {?string}
+ */
+WebInspector.ParsedURL.completeURL = function(baseURL, href)
 {
-};
+    if (href) {
+        // Return special URLs as-is.
+        var trimmedHref = href.trim();
+        if (trimmedHref.startsWith("data:") || trimmedHref.startsWith("blob:") || trimmedHref.startsWith("javascript:"))
+            return href;
 
+        // Return absolute URLs as-is.
+        var parsedHref = trimmedHref.asParsedURL();
+        if (parsedHref && parsedHref.scheme)
+            return trimmedHref;
+    } else {
+        return baseURL;
+    }
 
+    var parsedURL = baseURL.asParsedURL();
+    if (parsedURL) {
+        if (parsedURL.isDataURL())
+            return href;
+        var path = href;
 
+        var query = path.indexOf("?");
+        var postfix = "";
+        if (query !== -1) {
+            postfix = path.substring(query);
+            path = path.substring(0, query);
+        } else {
+            var fragment = path.indexOf("#");
+            if (fragment !== -1) {
+                postfix = path.substring(fragment);
+                path = path.substring(0, fragment);
+            }
+        }
 
-WebInspector.EventTarget.removeEventListeners=function(eventList)
-{
-for(var i=0;i<eventList.length;++i){
-var eventInfo=eventList[i];
-eventInfo.eventTarget.removeEventListener(eventInfo.eventType,eventInfo.method,eventInfo.receiver);
+        if (!path) {  // empty path, must be postfix
+            var basePath = parsedURL.path;
+            if (postfix.charAt(0) === "?") {
+                // A href of "?foo=bar" implies "basePath?foo=bar".
+                // With "basePath?a=b" and "?foo=bar" we should get "basePath?foo=bar".
+                var baseQuery = parsedURL.path.indexOf("?");
+                if (baseQuery !== -1)
+                    basePath = basePath.substring(0, baseQuery);
+            } // else it must be a fragment
+            return parsedURL.scheme + "://" + parsedURL.host + (parsedURL.port ? (":" + parsedURL.port) : "") + basePath + postfix;
+        } else if (path.charAt(0) !== "/") {  // relative path
+            var prefix = parsedURL.path;
+            var prefixQuery = prefix.indexOf("?");
+            if (prefixQuery !== -1)
+                prefix = prefix.substring(0, prefixQuery);
+            prefix = prefix.substring(0, prefix.lastIndexOf("/")) + "/";
+            path = prefix + path;
+        } else if (path.length > 1 && path.charAt(1) === "/") {
+            // href starts with "//" which is a full URL with the protocol dropped (use the baseURL protocol).
+            return parsedURL.scheme + ":" + path + postfix;
+        }  // else absolute path
+        return parsedURL.scheme + "://" + parsedURL.host + (parsedURL.port ? (":" + parsedURL.port) : "") + Runtime.normalizePath(path) + postfix;
+    }
+    return null;
 }
 
-eventList.splice(0,eventList.length);
-};
+WebInspector.ParsedURL.prototype = {
+    get displayName()
+    {
+        if (this._displayName)
+            return this._displayName;
 
-WebInspector.EventTarget.prototype={
-
-
-
-
-
-
-addEventListener:function(eventType,listener,thisObject){},
-
-
-
-
-
-
-removeEventListener:function(eventType,listener,thisObject){},
-
-removeAllListeners:function(){},
-
-
-
-
-
-hasEventListeners:function(eventType){},
-
-
-
-
-
-
-dispatchEventToListeners:function(eventType,eventData){}};
-
-
-
-
-
-
-
-
-
-WebInspector.EventTarget.EventDescriptor=function(eventTarget,eventType,receiver,method)
-{
-this.eventTarget=eventTarget;
-this.eventType=eventType;
-this.receiver=receiver;
-this.method=method;
-};
-
-},{}],208:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+        if (this.isDataURL())
+            return this.dataURLDisplayName();
+        if (this.isAboutBlank())
+            return this.url;
 
+        this._displayName = this.lastPathComponent;
+        if (!this._displayName)
+            this._displayName = (this.host || "") + "/";
+        if (this._displayName === "/")
+            this._displayName = this.url;
+        return this._displayName;
+    },
 
+    /**
+     * @return {string}
+     */
+    dataURLDisplayName: function()
+    {
+        if (this._dataURLDisplayName)
+            return this._dataURLDisplayName;
+        if (!this.isDataURL())
+            return "";
+        this._dataURLDisplayName = this.url.trimEnd(20);
+        return this._dataURLDisplayName;
+    },
 
+    /**
+     * @return {boolean}
+     */
+    isAboutBlank: function()
+    {
+        return this.url === "about:blank";
+    },
 
+    /**
+     * @return {boolean}
+     */
+    isDataURL: function()
+    {
+        return this.scheme === "data";
+    },
 
+    /**
+     * @return {string}
+     */
+    lastPathComponentWithFragment: function()
+    {
+        return this.lastPathComponent + (this.fragment ? "#" + this.fragment : "");
+    },
 
+    /**
+     * @return {string}
+     */
+    domain: function()
+    {
+        if (this.isDataURL())
+            return "data:";
+        return this.host + (this.port ? ":" + this.port : "");
+    },
 
+    /**
+     * @return {string}
+     */
+    securityOrigin: function()
+    {
+        if (this.isDataURL())
+            return "data:";
+        return this.scheme + "://" + this.domain();
+    },
 
+    /**
+     * @return {string}
+     */
+    urlWithoutScheme: function()
+    {
+        if (this.scheme && this.url.startsWith(this.scheme + "://"))
+            return this.url.substring(this.scheme.length + 3);
+        return this.url;
+    },
+}
 
-WebInspector.ParsedURL=function(url)
+/**
+ * @param {string} string
+ * @return {!{url: string, lineNumber: (number|undefined), columnNumber: (number|undefined)}}
+ */
+WebInspector.ParsedURL.splitLineAndColumn = function(string)
 {
-this.isValid=false;
-this.url=url;
-this.scheme="";
-this.host="";
-this.port="";
-this.path="";
-this.queryParams="";
-this.fragment="";
-this.folderPathComponents="";
-this.lastPathComponent="";
+    var lineColumnRegEx = /(?::(\d+))?(?::(\d+))?$/;
+    var lineColumnMatch = lineColumnRegEx.exec(string);
+    var lineNumber;
+    var columnNumber;
+    console.assert(lineColumnMatch);
 
-
-
-
-
-
+    if (typeof(lineColumnMatch[1]) === "string") {
+        lineNumber = parseInt(lineColumnMatch[1], 10);
+        // Immediately convert line and column to 0-based numbers.
+        lineNumber = isNaN(lineNumber) ? undefined : lineNumber - 1;
+    }
+    if (typeof(lineColumnMatch[2]) === "string") {
+        columnNumber = parseInt(lineColumnMatch[2], 10);
+        columnNumber = isNaN(columnNumber) ? undefined : columnNumber - 1;
+    }
 
-var match=url.match(/^([A-Za-z][A-Za-z0-9+.-]*):\/\/([^\s\/:]*)(?::([\d]+))?(?:(\/[^#]*)(?:#(.*))?)?$/i);
-if(match){
-this.isValid=true;
-this.scheme=match[1].toLowerCase();
-this.host=match[2];
-this.port=match[3];
-this.path=match[4]||"/";
-this.fragment=match[5];
-}else{
-if(this.url.startsWith("data:")){
-this.scheme="data";
-return;
-}
-if(this.url==="about:blank"){
-this.scheme="about";
-return;
-}
-this.path=this.url;
+    return {url: string.substring(0, string.length - lineColumnMatch[0].length), lineNumber: lineNumber, columnNumber: columnNumber};
 }
 
-
-var path=this.path;
-var indexOfQuery=path.indexOf("?");
-if(indexOfQuery!==-1){
-this.queryParams=path.substring(indexOfQuery+1);
-path=path.substring(0,indexOfQuery);
+/**
+ * @param {string} url
+ * @return {boolean}
+ */
+WebInspector.ParsedURL.isRelativeURL = function(url)
+{
+    return !(/^[A-Za-z][A-Za-z0-9+.-]*:/.test(url));
 }
 
-
-var lastSlashIndex=path.lastIndexOf("/");
-if(lastSlashIndex!==-1){
-this.folderPathComponents=path.substring(0,lastSlashIndex);
-this.lastPathComponent=path.substring(lastSlashIndex+1);
-}else
-this.lastPathComponent=path;
-};
-
-
-
-
-
-WebInspector.ParsedURL.splitURLIntoPathComponents=function(url)
+/**
+ * @return {?WebInspector.ParsedURL}
+ */
+String.prototype.asParsedURL = function()
 {
-if(url.startsWith("/"))
-url="file://"+url;
-var parsedURL=new WebInspector.ParsedURL(url);
-var origin;
-var folderPath;
-var name;
-if(parsedURL.isValid){
-origin=parsedURL.scheme+"://"+parsedURL.host;
-if(parsedURL.port)
-origin+=":"+parsedURL.port;
-folderPath=parsedURL.folderPathComponents;
-name=parsedURL.lastPathComponent;
-if(parsedURL.queryParams)
-name+="?"+parsedURL.queryParams;
-}else{
-origin="";
-folderPath="";
-name=url;
-}
-var result=[origin];
-var splittedPath=folderPath.split("/");
-for(var i=1;i<splittedPath.length;++i){
-if(!splittedPath[i])
-continue;
-result.push(splittedPath[i]);
+    var parsedURL = new WebInspector.ParsedURL(this.toString());
+    if (parsedURL.isValid)
+        return parsedURL;
+    return null;
 }
-result.push(name);
-return result;
-};
 
-
-
-
+},{}],238:[function(require,module,exports){
+/*
+ * Copyright (C) 2012 Google Inc.  All rights reserved.
+ * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ *     its contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
 
-WebInspector.ParsedURL.extractOrigin=function(url)
+/**
+ * @constructor
+ * @param {string} name
+ * @param {string} title
+ * @param {!WebInspector.ResourceCategory} category
+ * @param {boolean} isTextType
+ */
+WebInspector.ResourceType = function(name, title, category, isTextType)
 {
-var parsedURL=new WebInspector.ParsedURL(url);
-if(!parsedURL.isValid)
-return"";
+    this._name = name;
+    this._title = title;
+    this._category = category;
+    this._isTextType = isTextType;
+}
 
-var origin=parsedURL.scheme+"://"+parsedURL.host;
-if(parsedURL.port)
-origin+=":"+parsedURL.port;
-return origin;
-};
+WebInspector.ResourceType.prototype = {
+    /**
+     * @return {string}
+     */
+    name: function()
+    {
+        return this._name;
+    },
 
+    /**
+     * @return {string}
+     */
+    title: function()
+    {
+        return this._title;
+    },
 
+    /**
+     * @return {!WebInspector.ResourceCategory}
+     */
+    category: function()
+    {
+        return this._category;
+    },
 
-
-
-WebInspector.ParsedURL.extractExtension=function(url)
-{
-var lastIndexOfDot=url.lastIndexOf(".");
-var extension=lastIndexOfDot!==-1?url.substr(lastIndexOfDot+1):"";
-var indexOfQuestionMark=extension.indexOf("?");
-if(indexOfQuestionMark!==-1)
-extension=extension.substr(0,indexOfQuestionMark);
-return extension;
-};
-
-
+    /**
+     * @return {boolean}
+     */
+    isTextType: function()
+    {
+        return this._isTextType;
+    },
 
+    /**
+     * @return {boolean}
+     */
+    isScript: function()
+    {
+        return this._name === "script" || this._name === "sm-script";
+    },
 
+    /**
+     * @return {boolean}
+     */
+    hasScripts: function()
+    {
+        return this.isScript() || this.isDocument();
+    },
 
-WebInspector.ParsedURL.extractName=function(url)
-{
-var index=url.lastIndexOf("/");
-return index!==-1?url.substr(index+1):url;
-};
+    /**
+     * @return {boolean}
+     */
+    isStyleSheet: function()
+    {
+        return this._name === "stylesheet" || this._name === "sm-stylesheet";
+    },
 
+    /**
+     * @return {boolean}
+     */
+    isDocument: function()
+    {
+        return this._name === "document";
+    },
 
+    /**
+     * @return {boolean}
+     */
+    isDocumentOrScriptOrStyleSheet: function()
+    {
+        return this.isDocument() || this.isScript() || this.isStyleSheet();
+    },
 
+    /**
+     * @return {boolean}
+     */
+    isFromSourceMap: function()
+    {
+        return this._name.startsWith("sm-");
+    },
 
+    /**
+     * @override
+     * @return {string}
+     */
+    toString: function()
+    {
+        return this._name;
+    },
 
+    /**
+     * @return {string}
+     */
+    canonicalMimeType: function()
+    {
+        if (this.isDocument())
+            return "text/html";
+        if (this.isScript())
+            return "text/javascript";
+        if (this.isStyleSheet())
+            return "text/css";
+        return "";
+    }
+}
 
-WebInspector.ParsedURL.completeURL=function(baseURL,href)
+/**
+ * @constructor
+ * @param {string} title
+ * @param {string} shortTitle
+ */
+WebInspector.ResourceCategory = function(title, shortTitle)
 {
-if(href){
-
-var trimmedHref=href.trim();
-if(trimmedHref.startsWith("data:")||trimmedHref.startsWith("blob:")||trimmedHref.startsWith("javascript:"))
-return href;
-
-
-var parsedHref=trimmedHref.asParsedURL();
-if(parsedHref&&parsedHref.scheme)
-return trimmedHref;
-}else{
-return baseURL;
+    this.title = title;
+    this.shortTitle = shortTitle;
 }
 
-var parsedURL=baseURL.asParsedURL();
-if(parsedURL){
-if(parsedURL.isDataURL())
-return href;
-var path=href;
-
-var query=path.indexOf("?");
-var postfix="";
-if(query!==-1){
-postfix=path.substring(query);
-path=path.substring(0,query);
-}else{
-var fragment=path.indexOf("#");
-if(fragment!==-1){
-postfix=path.substring(fragment);
-path=path.substring(0,fragment);
-}
+WebInspector.resourceCategories = {
+    XHR: new WebInspector.ResourceCategory("XHR and Fetch", "XHR"),
+    Script: new WebInspector.ResourceCategory("Scripts", "JS"),
+    Stylesheet: new WebInspector.ResourceCategory("Stylesheets", "CSS"),
+    Image: new WebInspector.ResourceCategory("Images", "Img"),
+    Media: new WebInspector.ResourceCategory("Media", "Media"),
+    Font: new WebInspector.ResourceCategory("Fonts", "Font"),
+    Document: new WebInspector.ResourceCategory("Documents", "Doc"),
+    WebSocket: new WebInspector.ResourceCategory("WebSockets", "WS"),
+    Manifest: new WebInspector.ResourceCategory("Manifest", "Manifest"),
+    Other: new WebInspector.ResourceCategory("Other", "Other")
 }
 
-if(!path){
-var basePath=parsedURL.path;
-if(postfix.charAt(0)==="?"){
-
-
-var baseQuery=parsedURL.path.indexOf("?");
-if(baseQuery!==-1)
-basePath=basePath.substring(0,baseQuery);
+/**
+ * Keep these in sync with WebCore::InspectorPageAgent::resourceTypeJson
+ * @enum {!WebInspector.ResourceType}
+ */
+WebInspector.resourceTypes = {
+    XHR: new WebInspector.ResourceType("xhr", "XHR", WebInspector.resourceCategories.XHR, true),
+    Fetch: new WebInspector.ResourceType("fetch", "Fetch", WebInspector.resourceCategories.XHR, true),
+    EventSource: new WebInspector.ResourceType("eventsource", "EventSource", WebInspector.resourceCategories.XHR, true),
+    Script: new WebInspector.ResourceType("script", "Script", WebInspector.resourceCategories.Script, true),
+    Stylesheet: new WebInspector.ResourceType("stylesheet", "Stylesheet", WebInspector.resourceCategories.Stylesheet, true),
+    Image: new WebInspector.ResourceType("image", "Image", WebInspector.resourceCategories.Image, false),
+    Media: new WebInspector.ResourceType("media", "Media", WebInspector.resourceCategories.Media, false),
+    Font: new WebInspector.ResourceType("font", "Font", WebInspector.resourceCategories.Font, false),
+    Document: new WebInspector.ResourceType("document", "Document", WebInspector.resourceCategories.Document, true),
+    TextTrack: new WebInspector.ResourceType("texttrack", "TextTrack", WebInspector.resourceCategories.Other, true),
+    WebSocket: new WebInspector.ResourceType("websocket", "WebSocket", WebInspector.resourceCategories.WebSocket, false),
+    Other: new WebInspector.ResourceType("other", "Other", WebInspector.resourceCategories.Other, false),
+    SourceMapScript: new WebInspector.ResourceType("sm-script", "Script", WebInspector.resourceCategories.Script, false),
+    SourceMapStyleSheet: new WebInspector.ResourceType("sm-stylesheet", "Stylesheet", WebInspector.resourceCategories.Stylesheet, false),
+    Manifest: new WebInspector.ResourceType("manifest", "Manifest", WebInspector.resourceCategories.Manifest, true),
 }
-return parsedURL.scheme+"://"+parsedURL.host+(parsedURL.port?":"+parsedURL.port:"")+basePath+postfix;
-}else if(path.charAt(0)!=="/"){
-var prefix=parsedURL.path;
-var prefixQuery=prefix.indexOf("?");
-if(prefixQuery!==-1)
-prefix=prefix.substring(0,prefixQuery);
-prefix=prefix.substring(0,prefix.lastIndexOf("/"))+"/";
-path=prefix+path;
-}else if(path.length>1&&path.charAt(1)==="/"){
 
-return parsedURL.scheme+":"+path+postfix;
+/**
+ * @param {string} url
+ * @return {string|undefined}
+ */
+WebInspector.ResourceType.mimeFromURL = function(url)
+{
+    var name = WebInspector.ParsedURL.extractName(url);
+    if (WebInspector.ResourceType._mimeTypeByName.has(name)) {
+        return WebInspector.ResourceType._mimeTypeByName.get(name);
+    }
+    var ext = WebInspector.ParsedURL.extractExtension(url).toLowerCase();
+    return WebInspector.ResourceType._mimeTypeByExtension.get(ext);
 }
-return parsedURL.scheme+"://"+parsedURL.host+(parsedURL.port?":"+parsedURL.port:"")+Runtime.normalizePath(path)+postfix;
-}
-return null;
-};
 
-WebInspector.ParsedURL.prototype={
-get displayName()
-{
-if(this._displayName)
-return this._displayName;
+WebInspector.ResourceType._mimeTypeByName = new Map([
+    // CoffeeScript
+    ["Cakefile", "text/x-coffeescript"]
+]);
 
-if(this.isDataURL())
-return this.dataURLDisplayName();
-if(this.isAboutBlank())
-return this.url;
+WebInspector.ResourceType._mimeTypeByExtension = new Map([
+    // Web extensions
+    ["js", "text/javascript"],
+    ["css", "text/css"],
+    ["html", "text/html"],
+    ["htm", "text/html"],
+    ["xml", "application/xml"],
+    ["xsl", "application/xml"],
 
-this._displayName=this.lastPathComponent;
-if(!this._displayName)
-this._displayName=(this.host||"")+"/";
-if(this._displayName==="/")
-this._displayName=this.url;
-return this._displayName;
-},
+    // HTML Embedded Scripts, ASP], JSP
+    ["asp", "application/x-aspx"],
+    ["aspx", "application/x-aspx"],
+    ["jsp", "application/x-jsp"],
 
+    // C/C++
+    ["c", "text/x-c++src"],
+    ["cc", "text/x-c++src"],
+    ["cpp", "text/x-c++src"],
+    ["h", "text/x-c++src"],
+    ["m", "text/x-c++src"],
+    ["mm", "text/x-c++src"],
 
+    // CoffeeScript
+    ["coffee", "text/x-coffeescript"],
 
+    // Dart
+    ["dart", "text/javascript"],
 
-dataURLDisplayName:function()
-{
-if(this._dataURLDisplayName)
-return this._dataURLDisplayName;
-if(!this.isDataURL())
-return"";
-this._dataURLDisplayName=this.url.trimEnd(20);
-return this._dataURLDisplayName;
-},
+    // TypeScript
+    ["ts", "text/typescript"],
+    ["tsx", "text/typescript"],
 
+    // JSON
+    ["json", "application/json"],
+    ["gyp", "application/json"],
+    ["gypi", "application/json"],
 
+    // C#
+    ["cs", "text/x-csharp"],
 
+    // Java
+    ["java", "text/x-java"],
 
-isAboutBlank:function()
-{
-return this.url==="about:blank";
-},
+    // Less
+    ["less", "text/x-less"],
 
+    // PHP
+    ["php", "text/x-php"],
+    ["phtml", "application/x-httpd-php"],
 
+    // Python
+    ["py", "text/x-python"],
 
+    // Shell
+    ["sh", "text/x-sh"],
 
-isDataURL:function()
-{
-return this.scheme==="data";
-},
-
-
-
-
-lastPathComponentWithFragment:function()
-{
-return this.lastPathComponent+(this.fragment?"#"+this.fragment:"");
-},
-
-
-
-
-domain:function()
-{
-if(this.isDataURL())
-return"data:";
-return this.host+(this.port?":"+this.port:"");
-},
-
-
-
-
-securityOrigin:function()
-{
-if(this.isDataURL())
-return"data:";
-return this.scheme+"://"+this.domain();
-},
-
+    // SCSS
+    ["scss", "text/x-scss"],
 
+    // Video Text Tracks.
+    ["vtt", "text/vtt"],
 
+    // LiveScript
+    ["ls", "text/x-livescript"],
 
-urlWithoutScheme:function()
-{
-if(this.scheme&&this.url.startsWith(this.scheme+"://"))
-return this.url.substring(this.scheme.length+3);
-return this.url;
-}};
+    // ClojureScript
+    ["cljs", "text/x-clojure"],
+    ["cljc", "text/x-clojure"],
+    ["cljx", "text/x-clojure"],
 
+    // Stylus
+    ["styl", "text/x-styl"],
 
+    // JSX
+    ["jsx", "text/jsx"],
 
+    // Image
+    ["jpeg", "image/jpeg"],
+    ["jpg", "image/jpeg"],
+    ["svg", "image/svg"],
+    ["gif", "image/gif"],
+    ["webp", "image/webp"],
+    ["png", "image/png"],
+    ["ico", "image/ico"],
+    ["tiff", "image/tiff"],
+    ["tif", "image/tif"],
+    ["bmp", "image/bmp"],
 
+    // Font
+    ["ttf", "font/opentype"],
+    ["otf", "font/opentype"],
+    ["ttc", "font/opentype"],
+    ["woff", "application/font-woff"]
+]);
 
+},{}],239:[function(require,module,exports){
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
 
-WebInspector.ParsedURL.splitLineAndColumn=function(string)
+/**
+ * @constructor
+ * @param {number} begin
+ * @param {number} end
+ * @param {*} data
+ */
+WebInspector.Segment = function(begin, end, data)
 {
-var lineColumnRegEx=/(?::(\d+))?(?::(\d+))?$/;
-var lineColumnMatch=lineColumnRegEx.exec(string);
-var lineNumber;
-var columnNumber;
-console.assert(lineColumnMatch);
-
-if(typeof lineColumnMatch[1]==="string"){
-lineNumber=parseInt(lineColumnMatch[1],10);
-
-lineNumber=isNaN(lineNumber)?undefined:lineNumber-1;
-}
-if(typeof lineColumnMatch[2]==="string"){
-columnNumber=parseInt(lineColumnMatch[2],10);
-columnNumber=isNaN(columnNumber)?undefined:columnNumber-1;
+    if (begin > end)
+        console.assert(false, "Invalid segment");
+    this.begin = begin;
+    this.end = end;
+    this.data = data;
 }
 
-return{url:string.substring(0,string.length-lineColumnMatch[0].length),lineNumber:lineNumber,columnNumber:columnNumber};
+WebInspector.Segment.prototype = {
+    /**
+     * @param {!WebInspector.Segment} that
+     * @return {boolean}
+     */
+    intersects: function(that)
+    {
+        return this.begin < that.end && that.begin < this.end;
+    }
 };
 
-
-
-
-
-WebInspector.ParsedURL.isRelativeURL=function(url)
+/**
+ * @constructor
+ * @param {(function(!WebInspector.Segment, !WebInspector.Segment): ?WebInspector.Segment)=} mergeCallback
+ */
+WebInspector.SegmentedRange = function(mergeCallback)
 {
-return!/^[A-Za-z][A-Za-z0-9+.-]*:/.test(url);
-};
+    /** @type {!Array<!WebInspector.Segment>} */
+    this._segments = [];
+    this._mergeCallback = mergeCallback;
+}
 
-
-
-
-String.prototype.asParsedURL=function()
-{
-var parsedURL=new WebInspector.ParsedURL(this.toString());
-if(parsedURL.isValid)
-return parsedURL;
-return null;
-};
-
-},{}],209:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+WebInspector.SegmentedRange.prototype = {
+    /**
+     * @param {!WebInspector.Segment} newSegment
+     */
+    append: function(newSegment)
+    {
+        // 1. Find the proper insertion point for new segment
+        var startIndex = this._segments.lowerBound(newSegment, (a, b) => a.begin - b.begin);
+        var endIndex = startIndex;
+        var merged = null;
+        if (startIndex > 0) {
+            // 2. Try mering the preceding segment
+            var precedingSegment = this._segments[startIndex - 1];
+            merged = this._tryMerge(precedingSegment, newSegment);
+            if (merged) {
+                --startIndex;
+                newSegment = merged;
+            } else if (this._segments[startIndex - 1].end >= newSegment.begin) {
+                // 2a. If merge failed and segments overlap, adjust preceding segment.
+                // If an old segment entirely contains new one, split it in two.
+                if (newSegment.end < precedingSegment.end)
+                    this._segments.splice(startIndex, 0, new WebInspector.Segment(newSegment.end, precedingSegment.end, precedingSegment.data));
+                precedingSegment.end = newSegment.begin;
+            }
+        }
+        // 3. Consume all segments that are entirely covered by the new one.
+        while (endIndex < this._segments.length && this._segments[endIndex].end <= newSegment.end)
+            ++endIndex;
+        // 4. Merge or adjust the succeeding segment if it overlaps.
+        if (endIndex < this._segments.length) {
+            merged = this._tryMerge(newSegment, this._segments[endIndex]);
+            if (merged) {
+                endIndex++;
+                newSegment = merged;
+            } else if (newSegment.intersects(this._segments[endIndex]))
+                this._segments[endIndex].begin = newSegment.end;
+        }
+        this._segments.splice(startIndex, endIndex - startIndex, newSegment);
+    },
 
+    /**
+     * @param {!WebInspector.SegmentedRange} that
+     */
+    appendRange: function(that)
+    {
+        that.segments().forEach(segment => this.append(segment));
+    },
 
+    /**
+     * @return {!Array<!WebInspector.Segment>}
+     */
+    segments: function()
+    {
+        return this._segments;
+    },
 
+    /**
+     * @param {!WebInspector.Segment} first
+     * @param {!WebInspector.Segment} second
+     * @return {?WebInspector.Segment}
+     */
+    _tryMerge: function(first, second)
+    {
+        var merged = this._mergeCallback && this._mergeCallback(first, second);
+        if (!merged)
+            return null;
+        merged.begin = first.begin;
+        merged.end = Math.max(first.end, second.end);
+        return merged;
+    }
+}
 
+},{}],240:[function(require,module,exports){
+/*
+ * Copyright (C) 2013 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
 
-WebInspector.ResourceType=function(name,title,category,isTextType)
+/**
+ * @constructor
+ * @param {number} startLine
+ * @param {number} startColumn
+ * @param {number} endLine
+ * @param {number} endColumn
+ */
+WebInspector.TextRange = function(startLine, startColumn, endLine, endColumn)
 {
-this._name=name;
-this._title=title;
-this._category=category;
-this._isTextType=isTextType;
-};
-
-WebInspector.ResourceType.prototype={
-
-
+    this.startLine = startLine;
+    this.startColumn = startColumn;
+    this.endLine = endLine;
+    this.endColumn = endColumn;
+}
 
-name:function()
+/**
+ * @param {number} line
+ * @param {number} column
+ * @return {!WebInspector.TextRange}
+ */
+WebInspector.TextRange.createFromLocation = function(line, column)
 {
-return this._name;
-},
+    return new WebInspector.TextRange(line, column, line, column);
+}
 
-
-
-
-title:function()
+/**
+ * @param {!Object} serializedTextRange
+ * @return {!WebInspector.TextRange}
+ */
+WebInspector.TextRange.fromObject = function(serializedTextRange)
 {
-return this._title;
-},
+    return new WebInspector.TextRange(serializedTextRange.startLine, serializedTextRange.startColumn, serializedTextRange.endLine, serializedTextRange.endColumn);
+}
 
-
-
-
-category:function()
+/**
+ * @param {!WebInspector.TextRange} range1
+ * @param {!WebInspector.TextRange} range2
+ * @return {number}
+ */
+WebInspector.TextRange.comparator = function(range1, range2)
 {
-return this._category;
-},
+    return range1.compareTo(range2);
+}
 
+WebInspector.TextRange.prototype = {
+    /**
+     * @return {boolean}
+     */
+    isEmpty: function()
+    {
+        return this.startLine === this.endLine && this.startColumn === this.endColumn;
+    },
 
+    /**
+     * @param {!WebInspector.TextRange} range
+     * @return {boolean}
+     */
+    immediatelyPrecedes: function(range)
+    {
+        if (!range)
+            return false;
+        return this.endLine === range.startLine && this.endColumn === range.startColumn;
+    },
 
+    /**
+     * @param {!WebInspector.TextRange} range
+     * @return {boolean}
+     */
+    immediatelyFollows: function(range)
+    {
+        if (!range)
+            return false;
+        return range.immediatelyPrecedes(this);
+    },
 
-isTextType:function()
-{
-return this._isTextType;
-},
+    /**
+     * @param {!WebInspector.TextRange} range
+     * @return {boolean}
+     */
+    follows: function(range)
+    {
+        return (range.endLine === this.startLine && range.endColumn <= this.startColumn)
+            || range.endLine < this.startLine;
+    },
 
+    /**
+     * @return {number}
+     */
+    get linesCount()
+    {
+        return this.endLine - this.startLine;
+    },
 
+    /**
+     * @return {!WebInspector.TextRange}
+     */
+    collapseToEnd: function()
+    {
+        return new WebInspector.TextRange(this.endLine, this.endColumn, this.endLine, this.endColumn);
+    },
 
+    /**
+     * @return {!WebInspector.TextRange}
+     */
+    collapseToStart: function()
+    {
+        return new WebInspector.TextRange(this.startLine, this.startColumn, this.startLine, this.startColumn);
+    },
 
-isScript:function()
-{
-return this._name==="script"||this._name==="sm-script";
-},
+    /**
+     * @return {!WebInspector.TextRange}
+     */
+    normalize: function()
+    {
+        if (this.startLine > this.endLine || (this.startLine === this.endLine && this.startColumn > this.endColumn))
+            return new WebInspector.TextRange(this.endLine, this.endColumn, this.startLine, this.startColumn);
+        else
+            return this.clone();
+    },
 
+    /**
+     * @return {!WebInspector.TextRange}
+     */
+    clone: function()
+    {
+        return new WebInspector.TextRange(this.startLine, this.startColumn, this.endLine, this.endColumn);
+    },
 
+    /**
+     * @return {!{startLine: number, startColumn: number, endLine: number, endColumn: number}}
+     */
+    serializeToObject: function()
+    {
+        var serializedTextRange = {};
+        serializedTextRange.startLine = this.startLine;
+        serializedTextRange.startColumn = this.startColumn;
+        serializedTextRange.endLine = this.endLine;
+        serializedTextRange.endColumn = this.endColumn;
+        return serializedTextRange;
+    },
 
+    /**
+     * @param {!WebInspector.TextRange} other
+     * @return {number}
+     */
+    compareTo: function(other)
+    {
+        if (this.startLine > other.startLine)
+            return 1;
+        if (this.startLine < other.startLine)
+            return -1;
+        if (this.startColumn > other.startColumn)
+            return 1;
+        if (this.startColumn < other.startColumn)
+            return -1;
+        return 0;
+    },
 
-hasScripts:function()
-{
-return this.isScript()||this.isDocument();
-},
+    /**
+     * @param {number} lineNumber
+     * @param {number} columnNumber
+     * @return {number}
+     */
+    compareToPosition: function(lineNumber, columnNumber)
+    {
+        if (lineNumber < this.startLine || (lineNumber === this.startLine && columnNumber < this.startColumn))
+            return -1;
+        if (lineNumber > this.endLine || (lineNumber === this.endLine && columnNumber > this.endColumn))
+            return 1;
+        return 0;
+    },
 
+    /**
+     * @param {!WebInspector.TextRange} other
+     * @return {boolean}
+     */
+    equal: function(other)
+    {
+        return this.startLine === other.startLine && this.endLine === other.endLine &&
+            this.startColumn === other.startColumn && this.endColumn === other.endColumn;
+    },
 
+    /**
+     * @param {number} line
+     * @param {number} column
+     * @return {!WebInspector.TextRange}
+     */
+    relativeTo: function(line, column)
+    {
+        var relative = this.clone();
 
+        if (this.startLine === line)
+            relative.startColumn -= column;
+        if (this.endLine === line)
+            relative.endColumn -= column;
 
-isStyleSheet:function()
-{
-return this._name==="stylesheet"||this._name==="sm-stylesheet";
-},
+        relative.startLine -= line;
+        relative.endLine -= line;
+        return relative;
+    },
 
+    /**
+     * @param {!WebInspector.TextRange} originalRange
+     * @param {!WebInspector.TextRange} editedRange
+     * @return {!WebInspector.TextRange}
+     */
+    rebaseAfterTextEdit: function(originalRange, editedRange)
+    {
+        console.assert(originalRange.startLine === editedRange.startLine);
+        console.assert(originalRange.startColumn === editedRange.startColumn);
+        var rebase = this.clone();
+        if (!this.follows(originalRange))
+            return rebase;
+        var lineDelta = editedRange.endLine - originalRange.endLine;
+        var columnDelta = editedRange.endColumn - originalRange.endColumn;
+        rebase.startLine += lineDelta;
+        rebase.endLine += lineDelta;
+        if (rebase.startLine === editedRange.endLine)
+            rebase.startColumn += columnDelta;
+        if (rebase.endLine === editedRange.endLine)
+            rebase.endColumn += columnDelta;
+        return rebase;
+    },
 
+    /**
+     * @override
+     * @return {string}
+     */
+    toString: function()
+    {
+        return JSON.stringify(this);
+    },
 
+    /**
+     * @param {number} lineNumber
+     * @param {number} columnNumber
+     * @return {boolean}
+     */
+    containsLocation: function(lineNumber, columnNumber)
+    {
+        if (this.startLine === this.endLine)
+            return this.startLine === lineNumber && this.startColumn <= columnNumber && columnNumber <= this.endColumn;
+        if (this.startLine === lineNumber)
+            return this.startColumn <= columnNumber;
+        if (this.endLine === lineNumber)
+            return columnNumber <= this.endColumn;
+        return this.startLine < lineNumber && lineNumber < this.endLine;
+    }
+}
 
-isDocument:function()
-{
-return this._name==="document";
-},
-
-
-
-
-isDocumentOrScriptOrStyleSheet:function()
-{
-return this.isDocument()||this.isScript()||this.isStyleSheet();
-},
-
-
-
-
-isFromSourceMap:function()
-{
-return this._name.startsWith("sm-");
-},
-
-
-
-
-
-toString:function()
-{
-return this._name;
-},
-
-
-
-
-canonicalMimeType:function()
-{
-if(this.isDocument())
-return"text/html";
-if(this.isScript())
-return"text/javascript";
-if(this.isStyleSheet())
-return"text/css";
-return"";
-}};
-
-
-
-
-
-
-
-WebInspector.ResourceCategory=function(title,shortTitle)
-{
-this.title=title;
-this.shortTitle=shortTitle;
-};
-
-WebInspector.resourceCategories={
-XHR:new WebInspector.ResourceCategory("XHR and Fetch","XHR"),
-Script:new WebInspector.ResourceCategory("Scripts","JS"),
-Stylesheet:new WebInspector.ResourceCategory("Stylesheets","CSS"),
-Image:new WebInspector.ResourceCategory("Images","Img"),
-Media:new WebInspector.ResourceCategory("Media","Media"),
-Font:new WebInspector.ResourceCategory("Fonts","Font"),
-Document:new WebInspector.ResourceCategory("Documents","Doc"),
-WebSocket:new WebInspector.ResourceCategory("WebSockets","WS"),
-Manifest:new WebInspector.ResourceCategory("Manifest","Manifest"),
-Other:new WebInspector.ResourceCategory("Other","Other")};
-
-
-
-
-
-
-WebInspector.resourceTypes={
-XHR:new WebInspector.ResourceType("xhr","XHR",WebInspector.resourceCategories.XHR,true),
-Fetch:new WebInspector.ResourceType("fetch","Fetch",WebInspector.resourceCategories.XHR,true),
-EventSource:new WebInspector.ResourceType("eventsource","EventSource",WebInspector.resourceCategories.XHR,true),
-Script:new WebInspector.ResourceType("script","Script",WebInspector.resourceCategories.Script,true),
-Stylesheet:new WebInspector.ResourceType("stylesheet","Stylesheet",WebInspector.resourceCategories.Stylesheet,true),
-Image:new WebInspector.ResourceType("image","Image",WebInspector.resourceCategories.Image,false),
-Media:new WebInspector.ResourceType("media","Media",WebInspector.resourceCategories.Media,false),
-Font:new WebInspector.ResourceType("font","Font",WebInspector.resourceCategories.Font,false),
-Document:new WebInspector.ResourceType("document","Document",WebInspector.resourceCategories.Document,true),
-TextTrack:new WebInspector.ResourceType("texttrack","TextTrack",WebInspector.resourceCategories.Other,true),
-WebSocket:new WebInspector.ResourceType("websocket","WebSocket",WebInspector.resourceCategories.WebSocket,false),
-Other:new WebInspector.ResourceType("other","Other",WebInspector.resourceCategories.Other,false),
-SourceMapScript:new WebInspector.ResourceType("sm-script","Script",WebInspector.resourceCategories.Script,false),
-SourceMapStyleSheet:new WebInspector.ResourceType("sm-stylesheet","Stylesheet",WebInspector.resourceCategories.Stylesheet,false),
-Manifest:new WebInspector.ResourceType("manifest","Manifest",WebInspector.resourceCategories.Manifest,true)};
-
-
-
-
-
-
-WebInspector.ResourceType.mimeFromURL=function(url)
+/**
+ * @param {!WebInspector.TextRange} oldRange
+ * @param {string} newText
+ * @return {!WebInspector.TextRange}
+ */
+WebInspector.TextRange.fromEdit = function(oldRange, newText)
 {
-var name=WebInspector.ParsedURL.extractName(url);
-if(WebInspector.ResourceType._mimeTypeByName.has(name)){
-return WebInspector.ResourceType._mimeTypeByName.get(name);
+    var endLine = oldRange.startLine;
+    var endColumn = oldRange.startColumn + newText.length;
+    var lineEndings = newText.computeLineEndings();
+    if (lineEndings.length > 1) {
+        endLine = oldRange.startLine + lineEndings.length - 1;
+        var len = lineEndings.length;
+        endColumn = lineEndings[len - 1] - lineEndings[len - 2] - 1;
+    }
+    return new WebInspector.TextRange(
+        oldRange.startLine,
+        oldRange.startColumn,
+        endLine,
+        endColumn);
 }
-var ext=WebInspector.ParsedURL.extractExtension(url).toLowerCase();
-return WebInspector.ResourceType._mimeTypeByExtension.get(ext);
-};
 
-WebInspector.ResourceType._mimeTypeByName=new Map([
-
-["Cakefile","text/x-coffeescript"]]);
-
-
-WebInspector.ResourceType._mimeTypeByExtension=new Map([
-
-["js","text/javascript"],
-["css","text/css"],
-["html","text/html"],
-["htm","text/html"],
-["xml","application/xml"],
-["xsl","application/xml"],
-
-
-["asp","application/x-aspx"],
-["aspx","application/x-aspx"],
-["jsp","application/x-jsp"],
-
-
-["c","text/x-c++src"],
-["cc","text/x-c++src"],
-["cpp","text/x-c++src"],
-["h","text/x-c++src"],
-["m","text/x-c++src"],
-["mm","text/x-c++src"],
-
-
-["coffee","text/x-coffeescript"],
-
-
-["dart","text/javascript"],
-
-
-["ts","text/typescript"],
-["tsx","text/typescript"],
-
-
-["json","application/json"],
-["gyp","application/json"],
-["gypi","application/json"],
-
-
-["cs","text/x-csharp"],
-
-
-["java","text/x-java"],
-
-
-["less","text/x-less"],
-
-
-["php","text/x-php"],
-["phtml","application/x-httpd-php"],
-
-
-["py","text/x-python"],
-
-
-["sh","text/x-sh"],
-
-
-["scss","text/x-scss"],
-
-
-["vtt","text/vtt"],
-
-
-["ls","text/x-livescript"],
-
-
-["cljs","text/x-clojure"],
-["cljc","text/x-clojure"],
-["cljx","text/x-clojure"],
-
-
-["styl","text/x-styl"],
-
-
-["jsx","text/jsx"],
-
-
-["jpeg","image/jpeg"],
-["jpg","image/jpeg"],
-["svg","image/svg"],
-["gif","image/gif"],
-["webp","image/webp"],
-["png","image/png"],
-["ico","image/ico"],
-["tiff","image/tiff"],
-["tif","image/tif"],
-["bmp","image/bmp"],
-
-
-["ttf","font/opentype"],
-["otf","font/opentype"],
-["ttc","font/opentype"],
-["woff","application/font-woff"]]);
-
-
-},{}],210:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-WebInspector.Segment=function(begin,end,data)
-{
-if(begin>end)
-console.assert(false,"Invalid segment");
-this.begin=begin;
-this.end=end;
-this.data=data;
-};
-
-WebInspector.Segment.prototype={
-
-
-
-
-intersects:function(that)
-{
-return this.begin<that.end&&that.begin<this.end;
-}};
-
-
-
-
-
-
-WebInspector.SegmentedRange=function(mergeCallback)
+/**
+ * @constructor
+ * @param {number} offset
+ * @param {number} length
+ */
+WebInspector.SourceRange = function(offset, length)
 {
+    this.offset = offset;
+    this.length = length;
+}
 
-this._segments=[];
-this._mergeCallback=mergeCallback;
-};
-
-WebInspector.SegmentedRange.prototype={
-
-
-
-append:function(newSegment)
+/**
+ * @constructor
+ * @param {string} sourceURL
+ * @param {!WebInspector.TextRange} oldRange
+ * @param {string} newText
+ */
+WebInspector.SourceEdit = function(sourceURL, oldRange, newText)
 {
-
-var startIndex=this._segments.lowerBound(newSegment,(a,b)=>a.begin-b.begin);
-var endIndex=startIndex;
-var merged=null;
-if(startIndex>0){
-
-var precedingSegment=this._segments[startIndex-1];
-merged=this._tryMerge(precedingSegment,newSegment);
-if(merged){
---startIndex;
-newSegment=merged;
-}else if(this._segments[startIndex-1].end>=newSegment.begin){
-
-
-if(newSegment.end<precedingSegment.end)
-this._segments.splice(startIndex,0,new WebInspector.Segment(newSegment.end,precedingSegment.end,precedingSegment.data));
-precedingSegment.end=newSegment.begin;
-}
+    this.sourceURL = sourceURL;
+    this.oldRange = oldRange;
+    this.newText = newText;
 }
 
-while(endIndex<this._segments.length&&this._segments[endIndex].end<=newSegment.end)
-++endIndex;
-
-if(endIndex<this._segments.length){
-merged=this._tryMerge(newSegment,this._segments[endIndex]);
-if(merged){
-endIndex++;
-newSegment=merged;
-}else if(newSegment.intersects(this._segments[endIndex]))
-this._segments[endIndex].begin=newSegment.end;
+WebInspector.SourceEdit.prototype = {
+    /**
+     * @return {!WebInspector.TextRange}
+     */
+    newRange: function()
+    {
+        return WebInspector.TextRange.fromEdit(this.oldRange, this.newText);
+    },
 }
-this._segments.splice(startIndex,endIndex-startIndex,newSegment);
-},
 
-
-
-
-appendRange:function(that)
+/**
+ * @param {!WebInspector.SourceEdit} edit1
+ * @param {!WebInspector.SourceEdit} edit2
+ * @return {number}
+ */
+WebInspector.SourceEdit.comparator = function(edit1, edit2)
 {
-that.segments().forEach(segment=>this.append(segment));
-},
-
-
-
-
-segments:function()
-{
-return this._segments;
-},
-
-
-
-
-
-
-_tryMerge:function(first,second)
-{
-var merged=this._mergeCallback&&this._mergeCallback(first,second);
-if(!merged)
-return null;
-merged.begin=first.begin;
-merged.end=Math.max(first.end,second.end);
-return merged;
-}};
-
-
-},{}],211:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-WebInspector.TextRange=function(startLine,startColumn,endLine,endColumn)
-{
-this.startLine=startLine;
-this.startColumn=startColumn;
-this.endLine=endLine;
-this.endColumn=endColumn;
-};
-
-
-
-
-
-
-WebInspector.TextRange.createFromLocation=function(line,column)
-{
-return new WebInspector.TextRange(line,column,line,column);
-};
-
-
-
-
-
-WebInspector.TextRange.fromObject=function(serializedTextRange)
-{
-return new WebInspector.TextRange(serializedTextRange.startLine,serializedTextRange.startColumn,serializedTextRange.endLine,serializedTextRange.endColumn);
-};
-
-
-
-
-
-
-WebInspector.TextRange.comparator=function(range1,range2)
-{
-return range1.compareTo(range2);
-};
-
-WebInspector.TextRange.prototype={
-
-
-
-isEmpty:function()
-{
-return this.startLine===this.endLine&&this.startColumn===this.endColumn;
-},
-
-
-
-
-
-immediatelyPrecedes:function(range)
-{
-if(!range)
-return false;
-return this.endLine===range.startLine&&this.endColumn===range.startColumn;
-},
-
-
-
-
-
-immediatelyFollows:function(range)
-{
-if(!range)
-return false;
-return range.immediatelyPrecedes(this);
-},
-
-
-
-
-
-follows:function(range)
-{
-return range.endLine===this.startLine&&range.endColumn<=this.startColumn||
-range.endLine<this.startLine;
-},
-
-
-
-
-get linesCount()
-{
-return this.endLine-this.startLine;
-},
-
-
-
-
-collapseToEnd:function()
-{
-return new WebInspector.TextRange(this.endLine,this.endColumn,this.endLine,this.endColumn);
-},
-
-
-
-
-collapseToStart:function()
-{
-return new WebInspector.TextRange(this.startLine,this.startColumn,this.startLine,this.startColumn);
-},
-
-
-
-
-normalize:function()
-{
-if(this.startLine>this.endLine||this.startLine===this.endLine&&this.startColumn>this.endColumn)
-return new WebInspector.TextRange(this.endLine,this.endColumn,this.startLine,this.startColumn);else
-
-return this.clone();
-},
-
-
-
-
-clone:function()
-{
-return new WebInspector.TextRange(this.startLine,this.startColumn,this.endLine,this.endColumn);
-},
-
-
-
-
-serializeToObject:function()
-{
-var serializedTextRange={};
-serializedTextRange.startLine=this.startLine;
-serializedTextRange.startColumn=this.startColumn;
-serializedTextRange.endLine=this.endLine;
-serializedTextRange.endColumn=this.endColumn;
-return serializedTextRange;
-},
-
-
-
-
-
-compareTo:function(other)
-{
-if(this.startLine>other.startLine)
-return 1;
-if(this.startLine<other.startLine)
-return-1;
-if(this.startColumn>other.startColumn)
-return 1;
-if(this.startColumn<other.startColumn)
-return-1;
-return 0;
-},
-
-
-
-
-
-
-compareToPosition:function(lineNumber,columnNumber)
-{
-if(lineNumber<this.startLine||lineNumber===this.startLine&&columnNumber<this.startColumn)
-return-1;
-if(lineNumber>this.endLine||lineNumber===this.endLine&&columnNumber>this.endColumn)
-return 1;
-return 0;
-},
-
-
-
-
-
-equal:function(other)
-{
-return this.startLine===other.startLine&&this.endLine===other.endLine&&
-this.startColumn===other.startColumn&&this.endColumn===other.endColumn;
-},
-
-
-
-
-
-
-relativeTo:function(line,column)
-{
-var relative=this.clone();
-
-if(this.startLine===line)
-relative.startColumn-=column;
-if(this.endLine===line)
-relative.endColumn-=column;
-
-relative.startLine-=line;
-relative.endLine-=line;
-return relative;
-},
-
-
-
-
-
-
-rebaseAfterTextEdit:function(originalRange,editedRange)
-{
-console.assert(originalRange.startLine===editedRange.startLine);
-console.assert(originalRange.startColumn===editedRange.startColumn);
-var rebase=this.clone();
-if(!this.follows(originalRange))
-return rebase;
-var lineDelta=editedRange.endLine-originalRange.endLine;
-var columnDelta=editedRange.endColumn-originalRange.endColumn;
-rebase.startLine+=lineDelta;
-rebase.endLine+=lineDelta;
-if(rebase.startLine===editedRange.endLine)
-rebase.startColumn+=columnDelta;
-if(rebase.endLine===editedRange.endLine)
-rebase.endColumn+=columnDelta;
-return rebase;
-},
-
-
-
-
-
-toString:function()
-{
-return JSON.stringify(this);
-},
-
-
-
-
-
-
-containsLocation:function(lineNumber,columnNumber)
-{
-if(this.startLine===this.endLine)
-return this.startLine===lineNumber&&this.startColumn<=columnNumber&&columnNumber<=this.endColumn;
-if(this.startLine===lineNumber)
-return this.startColumn<=columnNumber;
-if(this.endLine===lineNumber)
-return columnNumber<=this.endColumn;
-return this.startLine<lineNumber&&lineNumber<this.endLine;
-}};
-
-
-
-
-
-
-
-WebInspector.TextRange.fromEdit=function(oldRange,newText)
-{
-var endLine=oldRange.startLine;
-var endColumn=oldRange.startColumn+newText.length;
-var lineEndings=newText.computeLineEndings();
-if(lineEndings.length>1){
-endLine=oldRange.startLine+lineEndings.length-1;
-var len=lineEndings.length;
-endColumn=lineEndings[len-1]-lineEndings[len-2]-1;
+    return WebInspector.TextRange.comparator(edit1.oldRange, edit2.oldRange);
 }
-return new WebInspector.TextRange(
-oldRange.startLine,
-oldRange.startColumn,
-endLine,
-endColumn);
-};
 
-
-
-
-
+},{}],241:[function(require,module,exports){
+/*
+ * Copyright (C) 2011 Google Inc.  All rights reserved.
+ * Copyright (C) 2006, 2007, 2008 Apple Inc.  All rights reserved.
+ * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com).
+ * Copyright (C) 2009 Joseph Pecoraro
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ *     its contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
 
-WebInspector.SourceRange=function(offset,length)
+/**
+ * @param {string} string
+ * @param {...*} vararg
+ * @return {string}
+ */
+WebInspector.UIString = function(string, vararg)
 {
-this.offset=offset;
-this.length=length;
-};
-
-
-
-
-
-
-
-WebInspector.SourceEdit=function(sourceURL,oldRange,newText)
-{
-this.sourceURL=sourceURL;
-this.oldRange=oldRange;
-this.newText=newText;
-};
-
-WebInspector.SourceEdit.prototype={
-
-
-
-newRange:function()
-{
-return WebInspector.TextRange.fromEdit(this.oldRange,this.newText);
-}};
-
-
-
-
-
-
-
-WebInspector.SourceEdit.comparator=function(edit1,edit2)
-{
-return WebInspector.TextRange.comparator(edit1.oldRange,edit2.oldRange);
-};
-
-},{}],212:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-WebInspector.UIString=function(string,vararg)
-{
-return String.vsprintf(WebInspector.localize(string),Array.prototype.slice.call(arguments,1));
-};
-
-
-
-
-
-
-WebInspector.UIString.capitalize=function(string,vararg)
-{
-if(WebInspector._useLowerCaseMenuTitles===undefined)
-throw"WebInspector.setLocalizationPlatform() has not been called";
-
-var localized=WebInspector.localize(string);
-var capitalized;
-if(WebInspector._useLowerCaseMenuTitles)
-capitalized=localized.replace(/\^(.)/g,"$1");else
-
-capitalized=localized.replace(/\^(.)/g,function(str,char){return char.toUpperCase();});
-return String.vsprintf(capitalized,Array.prototype.slice.call(arguments,1));
-};
-
-
-
-
-WebInspector.setLocalizationPlatform=function(platform)
-{
-WebInspector._useLowerCaseMenuTitles=platform==="windows";
-};
-
-
-
-
-
-WebInspector.localize=function(string)
-{
-return string;
-};
-
-
-
-
-
-WebInspector.UIStringFormat=function(format)
-{
-
-this._localizedFormat=WebInspector.localize(format);
-
-this._tokenizedFormat=String.tokenizeFormatString(this._localizedFormat,String.standardFormatters);
-};
-
-
-
-
-
-
-WebInspector.UIStringFormat._append=function(a,b)
-{
-return a+b;
-};
-
-WebInspector.UIStringFormat.prototype={
-
-
-
-
-format:function(vararg)
-{
-return String.format(this._localizedFormat,arguments,
-String.standardFormatters,"",WebInspector.UIStringFormat._append,this._tokenizedFormat).formattedResult;
-}};
-
-
-},{}],213:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-WebInspector.FilmStripModel=function(tracingModel,zeroTime)
-{
-this.reset(tracingModel,zeroTime);
-};
-
-WebInspector.FilmStripModel._category="disabled-by-default-devtools.screenshot";
-
-WebInspector.FilmStripModel.TraceEvents={
-CaptureFrame:"CaptureFrame",
-Screenshot:"Screenshot"};
-
-
-WebInspector.FilmStripModel.prototype={
-
-
-
-
-reset:function(tracingModel,zeroTime)
-{
-this._zeroTime=zeroTime||tracingModel.minimumRecordTime();
-this._spanTime=tracingModel.maximumRecordTime()-this._zeroTime;
-
-
-this._frames=[];
-var browserMain=WebInspector.TracingModel.browserMainThread(tracingModel);
-if(!browserMain)
-return;
-
-var events=browserMain.events();
-for(var i=0;i<events.length;++i){
-var event=events[i];
-if(event.startTime<this._zeroTime)
-continue;
-if(!event.hasCategory(WebInspector.FilmStripModel._category))
-continue;
-if(event.name===WebInspector.FilmStripModel.TraceEvents.CaptureFrame){
-var data=event.args["data"];
-if(data)
-this._frames.push(WebInspector.FilmStripModel.Frame._fromEvent(this,event,this._frames.length));
-}else if(event.name===WebInspector.FilmStripModel.TraceEvents.Screenshot){
-this._frames.push(WebInspector.FilmStripModel.Frame._fromSnapshot(this,event,this._frames.length));
-}
+    return String.vsprintf(WebInspector.localize(string), Array.prototype.slice.call(arguments, 1));
 }
-},
 
-
-
-
-frames:function()
+/**
+ * @param {string} string
+ * @param {...*} vararg
+ * @return {string}
+ */
+WebInspector.UIString.capitalize = function(string, vararg)
 {
-return this._frames;
-},
+    if (WebInspector._useLowerCaseMenuTitles === undefined)
+        throw "WebInspector.setLocalizationPlatform() has not been called";
 
-
-
-
-zeroTime:function()
-{
-return this._zeroTime;
-},
-
-
-
-
-spanTime:function()
-{
-return this._spanTime;
-},
-
-
-
-
-
-frameByTimestamp:function(timestamp)
-{
-var index=this._frames.upperBound(timestamp,(timestamp,frame)=>timestamp-frame.timestamp)-1;
-return index>=0?this._frames[index]:null;
-}};
-
-
-
-
-
-
-
-
-WebInspector.FilmStripModel.Frame=function(model,timestamp,index)
-{
-this._model=model;
-this.timestamp=timestamp;
-this.index=index;
-
-this._imageData=null;
-
-this._snapshot=null;
-};
-
-
-
-
-
-
-
-WebInspector.FilmStripModel.Frame._fromEvent=function(model,event,index)
-{
-var frame=new WebInspector.FilmStripModel.Frame(model,event.startTime,index);
-frame._imageData=event.args["data"];
-return frame;
-};
-
-
-
-
-
-
-
-WebInspector.FilmStripModel.Frame._fromSnapshot=function(model,snapshot,index)
-{
-var frame=new WebInspector.FilmStripModel.Frame(model,snapshot.startTime,index);
-frame._snapshot=snapshot;
-return frame;
-};
-
-WebInspector.FilmStripModel.Frame.prototype={
-
-
-
-model:function()
-{
-return this._model;
-},
-
-
-
-
-imageDataPromise:function()
-{
-if(this._imageData||!this._snapshot)
-return Promise.resolve(this._imageData);
-
-return this._snapshot.objectPromise();
-}};
-
-
-},{}],214:[function(require,module,exports){
-
-
-
-
-
-
-
-
-WebInspector.SCSSParser=function()
-{
-};
-
-WebInspector.SCSSParser.prototype={
-
-
-
-
-
-parse:function(content)
-{
-var ast=null;
-try{
-ast=gonzales.parse(content,{syntax:"scss"});
-}catch(e){
-return[];
+    var localized = WebInspector.localize(string);
+    var capitalized;
+    if (WebInspector._useLowerCaseMenuTitles)
+        capitalized = localized.replace(/\^(.)/g, "$1");
+    else
+        capitalized = localized.replace(/\^(.)/g, function(str, char) { return char.toUpperCase(); });
+    return String.vsprintf(capitalized, Array.prototype.slice.call(arguments, 1));
 }
 
-
-var rootBlock={
-properties:[],
-node:ast};
-
-
-var blocks=[rootBlock];
-ast.selectors=[];
-WebInspector.SCSSParser.extractNodes(ast,blocks,rootBlock);
-
-var rules=[];
-for(var block of blocks)
-this._handleBlock(block,rules);
-return rules;
-},
-
-
-
-
-
-_handleBlock:function(block,output)
+/**
+ * @param {string} platform
+ */
+WebInspector.setLocalizationPlatform = function(platform)
 {
-var selectors=block.node.selectors.map(WebInspector.SCSSParser.rangeFromNode);
-var properties=[];
-var styleRange=WebInspector.SCSSParser.rangeFromNode(block.node);
-styleRange.startColumn+=1;
-styleRange.endColumn-=1;
-for(var node of block.properties){
-if(node.type==="declaration")
-this._handleDeclaration(node,properties);else
-if(node.type==="include")
-this._handleInclude(node,properties);else
-if(node.type==="multilineComment"&&node.start.line===node.end.line)
-this._handleComment(node,properties);
+    WebInspector._useLowerCaseMenuTitles = platform === "windows";
 }
-if(!selectors.length&&!properties.length)
-return;
-var rule=new WebInspector.SCSSParser.Rule(selectors,properties,styleRange);
-output.push(rule);
-},
 
-
-
-
-
-_handleDeclaration:function(node,output)
+/**
+ * @param {string} string
+ * @return {string}
+ */
+WebInspector.localize = function(string)
 {
-var propertyNode=node.content.find(node=>node.type==="property");
-var valueNode=node.content.find(node=>node.type==="value");
-if(!propertyNode||!valueNode)
-return;
-
-var nameRange=WebInspector.SCSSParser.rangeFromNode(propertyNode);
-var valueRange=WebInspector.SCSSParser.rangeFromNode(valueNode);
-var range=node.declarationRange;
-
-var property=new WebInspector.SCSSParser.Property(range,nameRange,valueRange,false);
-output.push(property);
-},
-
-
-
-
-
-_handleInclude:function(node,output)
-{
-var mixinName=node.content.find(node=>node.type==="ident");
-if(!mixinName)
-return;
-var nameRange=WebInspector.SCSSParser.rangeFromNode(mixinName);
-var mixinArguments=node.content.find(node=>node.type==="arguments");
-if(!mixinArguments)
-return;
-var parameters=mixinArguments.content.filter(node=>node.type!=="delimiter"&&node.type!=="space");
-for(var parameter of parameters){
-var range=WebInspector.SCSSParser.rangeFromNode(node);
-var valueRange=WebInspector.SCSSParser.rangeFromNode(parameter);
-var property=new WebInspector.SCSSParser.Property(range,nameRange,valueRange,false);
-output.push(property);
+    return string;
 }
-},
 
-
-
-
-
-_handleComment:function(node,output)
+/**
+ * @constructor
+ * @param {string} format
+ */
+WebInspector.UIStringFormat = function(format)
 {
-if(node.start.line!==node.end.line)
-return;
-var innerText=node.content;
-var innerResult=this.parse(innerText);
-if(innerResult.length!==1||innerResult[0].properties.length!==1)
-return;
-var property=innerResult[0].properties[0];
-var disabledProperty=property.rebaseInsideOneLineComment(node);
-output.push(disabledProperty);
-}};
-
-
-
-
-
-
-WebInspector.SCSSParser.rangeFromNode=function(node)
-{
-return new WebInspector.TextRange(node.start.line-1,node.start.column-1,node.end.line-1,node.end.column);
-};
-
-
-
-
-
-
-
-
-WebInspector.SCSSParser.Property=function(range,nameRange,valueRange,disabled)
-{
-this.range=range;
-this.name=nameRange;
-this.value=valueRange;
-this.disabled=disabled;
-};
-
-WebInspector.SCSSParser.Property.prototype={
-
-
-
-
-rebaseInsideOneLineComment:function(commentNode)
-{
-var lineOffset=commentNode.start.line-1;
-
-var columnOffset=commentNode.start.column-1+2;
-var range=WebInspector.SCSSParser.rangeFromNode(commentNode);
-var name=rebaseRange(this.name,lineOffset,columnOffset);
-var value=rebaseRange(this.value,lineOffset,columnOffset);
-return new WebInspector.SCSSParser.Property(range,name,value,true);
-
-
-
-
-
-
-
-function rebaseRange(range,lineOffset,columnOffset)
-{
-return new WebInspector.TextRange(range.startLine+lineOffset,range.startColumn+columnOffset,range.endLine+lineOffset,range.endColumn+columnOffset);
+    /** @type {string} */
+    this._localizedFormat = WebInspector.localize(format);
+    /** @type {!Array.<!Object>} */
+    this._tokenizedFormat = String.tokenizeFormatString(this._localizedFormat, String.standardFormatters);
 }
-}};
 
-
-
-
-
-
-
-
-WebInspector.SCSSParser.Rule=function(selectors,properties,styleRange)
+/**
+ * @param {string} a
+ * @param {string} b
+ * @return {string}
+ */
+WebInspector.UIStringFormat._append = function(a, b)
 {
-this.selectors=selectors;
-this.properties=properties;
-this.styleRange=styleRange;
-};
-
-
-
-
-
-
-WebInspector.SCSSParser.extractNodes=function(node,blocks,lastBlock)
-{
-if(!Array.isArray(node.content))
-return;
-if(node.type==="block"){
-lastBlock={
-node:node,
-properties:[]};
-
-blocks.push(lastBlock);
-}
-var lastDeclaration=null;
-var selectors=[];
-for(var i=0;i<node.content.length;++i){
-var child=node.content[i];
-if(child.type==="declarationDelimiter"&&lastDeclaration){
-lastDeclaration.declarationRange.endLine=child.end.line-1;
-lastDeclaration.declarationRange.endColumn=child.end.column;
-lastDeclaration=null;
-}else if(child.type==="selector"){
-selectors.push(child);
-}else if(child.type==="block"){
-child.selectors=selectors;
-selectors=[];
-}
-if(child.type==="include"||child.type==="declaration"||child.type==="multilineComment")
-lastBlock.properties.push(child);
-if(child.type==="declaration"){
-lastDeclaration=child;
-lastDeclaration.declarationRange=WebInspector.TextRange.createFromLocation(child.start.line-1,child.start.column-1);
-}
-WebInspector.SCSSParser.extractNodes(child,blocks,lastBlock);
-}
-if(lastDeclaration){
-lastDeclaration.declarationRange.endLine=node.end.line-1;
-lastDeclaration.declarationRange.endColumn=node.end.column-1;
+    return a + b;
 }
-};
 
-},{}],215:[function(require,module,exports){
-(function webpackUniversalModuleDefinition(root,factory){
-if(typeof exports==='object'&&typeof module==='object')
-module.exports=factory();else
-if(typeof define==='function'&&define.amd)
-define([],factory);else
-if(typeof exports==='object')
-exports["gonzales"]=factory();else
-
-root["gonzales"]=factory();
-})(this,function(){
-return function(modules){
-
-var installedModules={};
-
-
-function __webpack_require__(moduleId){
-
-
-if(installedModules[moduleId])
-return installedModules[moduleId].exports;
-
-
-var module=installedModules[moduleId]={
-exports:{},
-id:moduleId,
-loaded:false};
-
-
-
-modules[moduleId].call(module.exports,module,module.exports,__webpack_require__);
-
-
-module.loaded=true;
-
-
-return module.exports;
-}
-
-
-
-__webpack_require__.m=modules;
-
-
-__webpack_require__.c=installedModules;
-
-
-__webpack_require__.p="";
-
-
-return __webpack_require__(0);
-}(
-
-[
-
-function(module,exports,__webpack_require__){
-
-'use strict';
-
-var Node=__webpack_require__(1);
-var parse=__webpack_require__(7);
-
-module.exports={
-createNode:function(options){
-return new Node(options);
-},
-parse:parse};
-
-
-},
-
-function(module,exports,__webpack_require__){
-
-'use strict';
-
-
-
-
-
-
-
-
-
-var _createClass=function(){function defineProperties(target,props){for(var i=0;i<props.length;i++){var descriptor=props[i];descriptor.enumerable=descriptor.enumerable||false;descriptor.configurable=true;if('value'in descriptor)descriptor.writable=true;Object.defineProperty(target,descriptor.key,descriptor);}}return function(Constructor,protoProps,staticProps){if(protoProps)defineProperties(Constructor.prototype,protoProps);if(staticProps)defineProperties(Constructor,staticProps);return Constructor;};}();
-
-function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor)){throw new TypeError('Cannot call a class as a function');}}
-
-var Node=function(){
-function Node(options){
-_classCallCheck(this,Node);
-
-this.type=options.type;
-this.content=options.content;
-this.syntax=options.syntax;
-
-if(options.start)this.start=options.start;
-if(options.end)this.end=options.end;
+WebInspector.UIStringFormat.prototype = {
+    /**
+     * @param {...*} vararg
+     * @return {string}
+     */
+    format: function(vararg)
+    {
+        return String.format(this._localizedFormat, arguments,
+            String.standardFormatters, "", WebInspector.UIStringFormat._append, this._tokenizedFormat).formattedResult;
+    }
 }
 
+},{}],242:[function(require,module,exports){
+/*
+ * Copyright 2015 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
 
-
-
-
-
-Node.prototype.contains=function contains(type){
-return this.content.some(function(node){
-return node.type===type;
-});
-};
-
-
-
-
-
-
-Node.prototype.eachFor=function eachFor(type,callback){
-if(!Array.isArray(this.content))return;
-
-if(typeof type!=='string'){
-callback=type;
-type=null;
+/**
+ * @constructor
+ * @param {!WebInspector.TracingModel} tracingModel
+ * @param {number=} zeroTime
+ */
+WebInspector.FilmStripModel = function(tracingModel, zeroTime)
+{
+    this.reset(tracingModel, zeroTime);
 }
 
-var l=this.content.length;
-var breakLoop;
+WebInspector.FilmStripModel._category = "disabled-by-default-devtools.screenshot";
 
-for(var i=l;i--;){
-if(breakLoop===null)break;
-
-if(!type||this.content[i]&&this.content[i].type===type)breakLoop=callback(this.content[i],i,this);
+WebInspector.FilmStripModel.TraceEvents = {
+    CaptureFrame: "CaptureFrame",
+    Screenshot: "Screenshot"
 }
 
-if(breakLoop===null)return null;
-};
+WebInspector.FilmStripModel.prototype = {
+    /**
+     * @param {!WebInspector.TracingModel} tracingModel
+     * @param {number=} zeroTime
+     */
+    reset: function(tracingModel, zeroTime)
+    {
+        this._zeroTime = zeroTime || tracingModel.minimumRecordTime();
+        this._spanTime = tracingModel.maximumRecordTime() - this._zeroTime;
 
+        /** @type {!Array<!WebInspector.FilmStripModel.Frame>} */
+        this._frames = [];
+        var browserMain = WebInspector.TracingModel.browserMainThread(tracingModel);
+        if (!browserMain)
+            return;
 
+        var events = browserMain.events();
+        for (var i = 0; i < events.length; ++i) {
+            var event = events[i];
+            if (event.startTime < this._zeroTime)
+                continue;
+            if (!event.hasCategory(WebInspector.FilmStripModel._category))
+                continue;
+            if (event.name === WebInspector.FilmStripModel.TraceEvents.CaptureFrame) {
+                var data = event.args["data"];
+                if (data)
+                    this._frames.push(WebInspector.FilmStripModel.Frame._fromEvent(this, event, this._frames.length));
+            } else if (event.name === WebInspector.FilmStripModel.TraceEvents.Screenshot) {
+                this._frames.push(WebInspector.FilmStripModel.Frame._fromSnapshot(this, /** @type {!WebInspector.TracingModel.ObjectSnapshot} */ (event), this._frames.length));
+            }
+        }
+    },
 
-
-
-
-Node.prototype.first=function first(type){
-if(!Array.isArray(this.content))return null;
+    /**
+     * @return {!Array<!WebInspector.FilmStripModel.Frame>}
+     */
+    frames: function()
+    {
+        return this._frames;
+    },
 
-if(!type)return this.content[0];
+    /**
+     * @return {number}
+     */
+    zeroTime: function()
+    {
+        return this._zeroTime;
+    },
 
-var i=0;
-var l=this.content.length;
+    /**
+     * @return {number}
+     */
+    spanTime: function()
+    {
+        return this._spanTime;
+    },
 
-for(;i<l;i++){
-if(this.content[i].type===type)return this.content[i];
+    /**
+     * @param {number} timestamp
+     * @return {?WebInspector.FilmStripModel.Frame}
+     */
+    frameByTimestamp: function(timestamp)
+    {
+        var index = this._frames.upperBound(timestamp, (timestamp, frame) => timestamp - frame.timestamp) - 1;
+        return index >= 0 ? this._frames[index] : null;
+    }
 }
 
-return null;
-};
-
-
-
-
-
-
-Node.prototype.forEach=function forEach(type,callback){
-if(!Array.isArray(this.content))return;
-
-if(typeof type!=='string'){
-callback=type;
-type=null;
+/**
+ * @constructor
+ * @param {!WebInspector.FilmStripModel} model
+ * @param {number} timestamp
+ * @param {number} index
+ */
+WebInspector.FilmStripModel.Frame = function(model, timestamp, index)
+{
+    this._model = model;
+    this.timestamp = timestamp;
+    this.index = index;
+    /** @type {?string} */
+    this._imageData = null;
+    /** @type {?WebInspector.TracingModel.ObjectSnapshot} */
+    this._snapshot = null;
 }
 
-var i=0;
-var l=this.content.length;
-var breakLoop;
-
-for(;i<l;i++){
-if(breakLoop===null)break;
-
-if(!type||this.content[i]&&this.content[i].type===type)breakLoop=callback(this.content[i],i,this);
+/**
+ * @param {!WebInspector.FilmStripModel} model
+ * @param {!WebInspector.TracingModel.Event} event
+ * @param {number} index
+ * @return {!WebInspector.FilmStripModel.Frame}
+ */
+WebInspector.FilmStripModel.Frame._fromEvent = function(model, event, index)
+{
+    var frame = new WebInspector.FilmStripModel.Frame(model, event.startTime, index);
+    frame._imageData = event.args["data"];
+    return frame;
 }
 
-if(breakLoop===null)return null;
-};
-
-
-
-
-
-
-Node.prototype.get=function get(index){
-if(!Array.isArray(this.content))return null;
-
-var node=this.content[index];
-return node?node:null;
-};
-
-
-
-
-
-
-Node.prototype.insert=function insert(index,node){
-if(!Array.isArray(this.content))return;
-
-this.content.splice(index,0,node);
-};
-
-
-
-
-
-
-Node.prototype.is=function is(type){
-return this.type===type;
-};
-
-
-
-
-
-
-Node.prototype.last=function last(type){
-if(!Array.isArray(this.content))return null;
-
-var i=this.content.length-1;
-if(!type)return this.content[i];
-
-for(;;i--){
-if(this.content[i].type===type)return this.content[i];
+/**
+ * @param {!WebInspector.FilmStripModel} model
+ * @param {!WebInspector.TracingModel.ObjectSnapshot} snapshot
+ * @param {number} index
+ * @return {!WebInspector.FilmStripModel.Frame}
+ */
+WebInspector.FilmStripModel.Frame._fromSnapshot = function(model, snapshot, index)
+{
+    var frame = new WebInspector.FilmStripModel.Frame(model, snapshot.startTime, index);
+    frame._snapshot = snapshot;
+    return frame;
 }
 
-return null;
-};
+WebInspector.FilmStripModel.Frame.prototype = {
+    /**
+     * @return {!WebInspector.FilmStripModel}
+     */
+    model: function()
+    {
+        return this._model;
+    },
 
+    /**
+     * @return {!Promise<?string>}
+     */
+    imageDataPromise: function()
+    {
+        if (this._imageData || !this._snapshot)
+            return Promise.resolve(this._imageData);
 
-
-
-
-
-
-
-
-
-
-Node.prototype.removeChild=function removeChild(index){
-if(!Array.isArray(this.content))return;
-
-var removedChild=this.content.splice(index,1);
-
-return removedChild;
-};
-
-Node.prototype.toJson=function toJson(){
-return JSON.stringify(this,false,2);
-};
-
-Node.prototype.toString=function toString(){
-var stringify=undefined;
-
-try{
-stringify=__webpack_require__(2)("./"+this.syntax+'/stringify');
-}catch(e){
-var message='Syntax "'+this.syntax+'" is not supported yet, sorry';
-return console.error(message);
+        return /** @type {!Promise<?string>} */ (this._snapshot.objectPromise());
+    }
 }
 
-return stringify(this);
-};
+},{}],243:[function(require,module,exports){
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
 
-
-
-
-
-Node.prototype.traverse=function traverse(callback,index){
-var level=arguments.length<=2||arguments[2]===undefined?0:arguments[2];
-var parent=arguments.length<=3||arguments[3]===undefined?null:arguments[3];
-
-var breakLoop;
-var x;
-
-level++;
-
-callback(this,index,parent,level);
-
-if(!Array.isArray(this.content))return;
-
-for(var i=0,l=this.content.length;i<l;i++){
-breakLoop=this.content[i].traverse(callback,i,level,this);
-if(breakLoop===null)break;
-
-
-if(x=this.content.length-l){
-l+=x;
-i+=x;
-}
+/**
+ * @constructor
+ * @implements {WebInspector.FormatterWorkerContentParser}
+ */
+WebInspector.SCSSParser = function()
+{
 }
 
-if(breakLoop===null)return null;
-};
+WebInspector.SCSSParser.prototype = {
+    /**
+     * @override
+     * @param {string} content
+     * @return {!Array<!WebInspector.SCSSParser.Rule>}
+     */
+    parse: function(content)
+    {
+        var ast = null;
+        try {
+            ast = gonzales.parse(content, {syntax: "scss"});
+        } catch (e) {
+            return [];
+        }
 
-Node.prototype.traverseByType=function traverseByType(type,callback){
-this.traverse(function(node){
-if(node.type===type)callback.apply(node,arguments);
-});
-};
+        /** @type {!{properties: !Array<!Gonzales.Node>, node: !Gonzales.Node}} */
+        var rootBlock = {
+            properties: [],
+            node: ast
+        };
+        /** @type {!Array<!{properties: !Array<!Gonzales.Node>, node: !Gonzales.Node}>} */
+        var blocks = [rootBlock];
+        ast.selectors = [];
+        WebInspector.SCSSParser.extractNodes(ast, blocks, rootBlock);
 
-Node.prototype.traverseByTypes=function traverseByTypes(types,callback){
-this.traverse(function(node){
-if(types.indexOf(node.type)!==-1)callback.apply(node,arguments);
-});
-};
+        var rules = [];
+        for (var block of blocks)
+            this._handleBlock(block, rules);
+        return rules;
+    },
 
-_createClass(Node,[{
-key:'length',
-get:function(){
-if(!Array.isArray(this.content))return 0;
-return this.content.length;
-}}]);
+    /**
+     * @param {!{node: !Gonzales.Node, properties: !Array<!Gonzales.Node>}} block
+     * @param {!Array<!WebInspector.SCSSParser.Rule>} output
+     */
+    _handleBlock: function(block, output)
+    {
+        var selectors = block.node.selectors.map(WebInspector.SCSSParser.rangeFromNode);
+        var properties = [];
+        var styleRange = WebInspector.SCSSParser.rangeFromNode(block.node);
+        styleRange.startColumn += 1;
+        styleRange.endColumn -= 1;
+        for (var node of block.properties) {
+            if (node.type === "declaration")
+                this._handleDeclaration(node, properties);
+            else if (node.type === "include")
+                this._handleInclude(node, properties);
+            else if (node.type === "multilineComment" && node.start.line === node.end.line)
+                this._handleComment(node, properties);
+        }
+        if (!selectors.length && !properties.length)
+            return;
+        var rule = new WebInspector.SCSSParser.Rule(selectors, properties, styleRange);
+        output.push(rule);
+    },
 
+    /**
+     * @param {!Gonzales.Node} node
+     * @param {!Array<!WebInspector.SCSSParser.Property>} output
+     */
+    _handleDeclaration: function(node, output)
+    {
+        var propertyNode = node.content.find(node => node.type === "property");
+        var valueNode = node.content.find(node => node.type === "value");
+        if (!propertyNode || !valueNode)
+            return;
 
-return Node;
-}();
+        var nameRange = WebInspector.SCSSParser.rangeFromNode(propertyNode);
+        var valueRange = WebInspector.SCSSParser.rangeFromNode(valueNode);
+        var range = /** @type {!WebInspector.TextRange} */(node.declarationRange);
 
-module.exports=Node;
+        var property = new WebInspector.SCSSParser.Property(range, nameRange, valueRange, false);
+        output.push(property);
+    },
 
-},
+    /**
+     * @param {!Gonzales.Node} node
+     * @param {!Array<!WebInspector.SCSSParser.Property>} output
+     */
+    _handleInclude: function(node, output)
+    {
+        var mixinName = node.content.find(node => node.type === "ident");
+        if (!mixinName)
+            return;
+        var nameRange = WebInspector.SCSSParser.rangeFromNode(mixinName);
+        var mixinArguments = node.content.find(node => node.type === "arguments");
+        if (!mixinArguments)
+            return;
+        var parameters = mixinArguments.content.filter(node => node.type !== "delimiter" && node.type !== "space");
+        for (var parameter of parameters) {
+            var range = WebInspector.SCSSParser.rangeFromNode(node);
+            var valueRange = WebInspector.SCSSParser.rangeFromNode(parameter);
+            var property = new WebInspector.SCSSParser.Property(range, nameRange, valueRange, false);
+            output.push(property);
+        }
+    },
 
-function(module,exports,__webpack_require__){
-
-var map={
-"./css/stringify":3,
-"./less/stringify":4,
-"./sass/stringify":5,
-"./scss/stringify":6};
-
-function webpackContext(req){
-return __webpack_require__(webpackContextResolve(req));
-};
-function webpackContextResolve(req){
-return map[req]||function(){throw new Error("Cannot find module '"+req+"'.");}();
-};
-webpackContext.keys=function webpackContextKeys(){
-return Object.keys(map);
-};
-webpackContext.resolve=webpackContextResolve;
-module.exports=webpackContext;
-webpackContext.id=2;
-
-
-},
-
-function(module,exports){
-
-
-
-'use strict';
-
-module.exports=function stringify(tree){
-
-if(!tree)throw new Error('We need tree to translate');
-
-function _t(tree){
-var type=tree.type;
-if(_unique[type])return _unique[type](tree);
-if(typeof tree.content==='string')return tree.content;
-if(Array.isArray(tree.content))return _composite(tree.content);
-return'';
+    /**
+     * @param {!Gonzales.Node} node
+     * @param {!Array<!WebInspector.SCSSParser.Property>} output
+     */
+    _handleComment: function(node, output)
+    {
+        if (node.start.line !== node.end.line)
+            return;
+        var innerText = /** @type {string} */(node.content);
+        var innerResult = this.parse(innerText);
+        if (innerResult.length !== 1 || innerResult[0].properties.length !== 1)
+            return;
+        var property = innerResult[0].properties[0];
+        var disabledProperty = property.rebaseInsideOneLineComment(node);
+        output.push(disabledProperty);
+    },
 }
 
-function _composite(t,i){
-if(!t)return'';
-
-var s='';
-i=i||0;
-for(;i<t.length;i++)s+=_t(t[i]);
-return s;
+/**
+ * @param {!Gonzales.Node} node
+ * @return {!WebInspector.TextRange}
+ */
+WebInspector.SCSSParser.rangeFromNode = function(node)
+{
+    return new WebInspector.TextRange(node.start.line - 1, node.start.column - 1, node.end.line - 1, node.end.column);
 }
 
-var _unique={
-'arguments':function(t){
-return'('+_composite(t.content)+')';
-},
-'atkeyword':function(t){
-return'@'+_composite(t.content);
-},
-'attributeSelector':function(t){
-return'['+_composite(t.content)+']';
-},
-'block':function(t){
-return'{'+_composite(t.content)+'}';
-},
-'brackets':function(t){
-return'['+_composite(t.content)+']';
-},
-'class':function(t){
-return'.'+_composite(t.content);
-},
-'color':function(t){
-return'#'+t.content;
-},
-'expression':function(t){
-return'expression('+t.content+')';
-},
-'id':function(t){
-return'#'+_composite(t.content);
-},
-'multilineComment':function(t){
-return'/*'+t.content+'*/';
-},
-'nthSelector':function(t){
-return':'+_t(t.content[0])+'('+_composite(t.content.slice(1))+')';
-},
-'parentheses':function(t){
-return'('+_composite(t.content)+')';
-},
-'percentage':function(t){
-return _composite(t.content)+'%';
-},
-'pseudoClass':function(t){
-return':'+_composite(t.content);
-},
-'pseudoElement':function(t){
-return'::'+_composite(t.content);
-},
-'uri':function(t){
-return'url('+_composite(t.content)+')';
-}};
-
-
-return _t(tree);
-};
-
-},
-
-function(module,exports){
-
-
-
-'use strict';
-
-module.exports=function stringify(tree){
-
-if(!tree)throw new Error('We need tree to translate');
-
-function _t(tree){
-var type=tree.type;
-if(_unique[type])return _unique[type](tree);
-if(typeof tree.content==='string')return tree.content;
-if(Array.isArray(tree.content))return _composite(tree.content);
-return'';
+/**
+ * @constructor
+ * @param {!WebInspector.TextRange} range
+ * @param {!WebInspector.TextRange} nameRange
+ * @param {!WebInspector.TextRange} valueRange
+ * @param {boolean} disabled
+ */
+WebInspector.SCSSParser.Property = function(range, nameRange, valueRange, disabled)
+{
+    this.range = range;
+    this.name = nameRange;
+    this.value = valueRange;
+    this.disabled = disabled;
 }
 
-function _composite(t,i){
-if(!t)return'';
+WebInspector.SCSSParser.Property.prototype = {
+    /**
+     * @param {!Gonzales.Node} commentNode
+     * @return {!WebInspector.SCSSParser.Property}
+     */
+    rebaseInsideOneLineComment: function(commentNode)
+    {
+        var lineOffset = commentNode.start.line - 1;
+        // Account for the "/*".
+        var columnOffset = commentNode.start.column - 1 + 2;
+        var range = WebInspector.SCSSParser.rangeFromNode(commentNode);
+        var name = rebaseRange(this.name, lineOffset, columnOffset);
+        var value = rebaseRange(this.value, lineOffset, columnOffset);
+        return new WebInspector.SCSSParser.Property(range, name, value, true);
 
-var s='';
-i=i||0;
-for(;i<t.length;i++)s+=_t(t[i]);
-return s;
+        /**
+         * @param {!WebInspector.TextRange} range
+         * @param {number} lineOffset
+         * @param {number} columnOffset
+         * @return {!WebInspector.TextRange}
+         */
+        function rebaseRange(range, lineOffset, columnOffset)
+        {
+            return new WebInspector.TextRange(range.startLine + lineOffset, range.startColumn + columnOffset, range.endLine + lineOffset, range.endColumn + columnOffset);
+        }
+    }
 }
 
-var _unique={
-'arguments':function(t){
-return'('+_composite(t.content)+')';
-},
-'atkeyword':function(t){
-return'@'+_composite(t.content);
-},
-'attributeSelector':function(t){
-return'['+_composite(t.content)+']';
-},
-'block':function(t){
-return'{'+_composite(t.content)+'}';
-},
-'brackets':function(t){
-return'['+_composite(t.content)+']';
-},
-'class':function(t){
-return'.'+_composite(t.content);
-},
-'color':function(t){
-return'#'+t.content;
-},
-'escapedString':function(t){
-return'~'+t.content;
-},
-'expression':function(t){
-return'expression('+t.content+')';
-},
-'id':function(t){
-return'#'+_composite(t.content);
-},
-'interpolatedVariable':function(t){
-return'@{'+_composite(t.content)+'}';
-},
-'multilineComment':function(t){
-return'/*'+t.content+'*/';
-},
-'nthSelector':function(t){
-return':'+_t(t.content[0])+'('+_composite(t.content.slice(1))+')';
-},
-'parentheses':function(t){
-return'('+_composite(t.content)+')';
-},
-'percentage':function(t){
-return _composite(t.content)+'%';
-},
-'pseudoClass':function(t){
-return':'+_composite(t.content);
-},
-'pseudoElement':function(t){
-return'::'+_composite(t.content);
-},
-'singlelineComment':function(t){
-return'/'+'/'+t.content;
-},
-'uri':function(t){
-return'url('+_composite(t.content)+')';
-},
-'variable':function(t){
-return'@'+_composite(t.content);
-},
-'variablesList':function(t){
-return _composite(t.content)+'...';
-}};
-
-
-return _t(tree);
-};
-
-},
-
-function(module,exports){
-
-
-
-'use strict';
-
-module.exports=function stringify(tree){
-
-if(!tree)throw new Error('We need tree to translate');
-
-function _t(tree){
-var type=tree.type;
-if(_unique[type])return _unique[type](tree);
-if(typeof tree.content==='string')return tree.content;
-if(Array.isArray(tree.content))return _composite(tree.content);
-return'';
+/**
+ * @constructor
+ * @param {!Array<!WebInspector.TextRange>} selectors
+ * @param {!Array<!WebInspector.SCSSParser.Property>} properties
+ * @param {!WebInspector.TextRange} styleRange
+ */
+WebInspector.SCSSParser.Rule = function(selectors, properties, styleRange)
+{
+    this.selectors = selectors;
+    this.properties = properties;
+    this.styleRange = styleRange;
 }
 
-function _composite(t,i){
-if(!t)return'';
-
-var s='';
-i=i||0;
-for(;i<t.length;i++)s+=_t(t[i]);
-return s;
-}
-
-var _unique={
-'arguments':function(t){
-return'('+_composite(t.content)+')';
-},
-'atkeyword':function(t){
-return'@'+_composite(t.content);
-},
-'attributeSelector':function(t){
-return'['+_composite(t.content)+']';
-},
-'block':function(t){
-return _composite(t.content);
-},
-'brackets':function(t){
-return'['+_composite(t.content)+']';
-},
-'class':function(t){
-return'.'+_composite(t.content);
-},
-'color':function(t){
-return'#'+t.content;
-},
-'expression':function(t){
-return'expression('+t.content+')';
-},
-'id':function(t){
-return'#'+_composite(t.content);
-},
-'interpolation':function(t){
-return'#{'+_composite(t.content)+'}';
-},
-'multilineComment':function(t){
-return'/*'+t.content;
-},
-'nthSelector':function(t){
-return':'+_t(t.content[0])+'('+_composite(t.content.slice(1))+')';
-},
-'parentheses':function(t){
-return'('+_composite(t.content)+')';
-},
-'percentage':function(t){
-return _composite(t.content)+'%';
-},
-'placeholder':function(t){
-return'%'+_composite(t.content);
-},
-'pseudoClass':function(t){
-return':'+_composite(t.content);
-},
-'pseudoElement':function(t){
-return'::'+_composite(t.content);
-},
-'singlelineComment':function(t){
-return'/'+'/'+t.content;
-},
-'uri':function(t){
-return'url('+_composite(t.content)+')';
-},
-'variable':function(t){
-return'$'+_composite(t.content);
-},
-'variablesList':function(t){
-return _composite(t.content)+'...';
-}};
-
-
-return _t(tree);
-};
-
-},
-
-function(module,exports){
-
-
-
-'use strict';
-
-module.exports=function stringify(tree){
-
-if(!tree)throw new Error('We need tree to translate');
-
-function _t(tree){
-var type=tree.type;
-if(_unique[type])return _unique[type](tree);
-if(typeof tree.content==='string')return tree.content;
-if(Array.isArray(tree.content))return _composite(tree.content);
-return'';
-}
-
-function _composite(t,i){
-if(!t)return'';
-
-var s='';
-i=i||0;
-for(;i<t.length;i++)s+=_t(t[i]);
-return s;
-}
-
-var _unique={
-'arguments':function(t){
-return'('+_composite(t.content)+')';
-},
-'atkeyword':function(t){
-return'@'+_composite(t.content);
-},
-'attributeSelector':function(t){
-return'['+_composite(t.content)+']';
-},
-'block':function(t){
-return'{'+_composite(t.content)+'}';
-},
-'brackets':function(t){
-return'['+_composite(t.content)+']';
-},
-'class':function(t){
-return'.'+_composite(t.content);
-},
-'color':function(t){
-return'#'+t.content;
-},
-'expression':function(t){
-return'expression('+t.content+')';
-},
-'id':function(t){
-return'#'+_composite(t.content);
-},
-'interpolation':function(t){
-return'#{'+_composite(t.content)+'}';
-},
-'multilineComment':function(t){
-return'/*'+t.content+'*/';
-},
-'nthSelector':function(t){
-return':'+_t(t.content[0])+'('+_composite(t.content.slice(1))+')';
-},
-'parentheses':function(t){
-return'('+_composite(t.content)+')';
-},
-'percentage':function(t){
-return _composite(t.content)+'%';
-},
-'placeholder':function(t){
-return'%'+_composite(t.content);
-},
-'pseudoClass':function(t){
-return':'+_composite(t.content);
-},
-'pseudoElement':function(t){
-return'::'+_composite(t.content);
-},
-'singlelineComment':function(t){
-return'/'+'/'+t.content;
-},
-'uri':function(t){
-return'url('+_composite(t.content)+')';
-},
-'variable':function(t){
-return'$'+_composite(t.content);
-},
-'variablesList':function(t){
-return _composite(t.content)+'...';
-}};
-
-
-return _t(tree);
-};
-
-},
-
-function(module,exports,__webpack_require__){
-
-'use strict';
-
-var ParsingError=__webpack_require__(8);
-var syntaxes=__webpack_require__(10);
-
-var isInteger=Number.isInteger||function(value){
-return typeof value==='number'&&Math.floor(value)===value;
-};
-
-
-
-
-
-
-function parser(css,options){
-if(typeof css!=='string')throw new Error('Please, pass a string to parse');else if(!css)return __webpack_require__(16)();
-
-var syntax=options&&options.syntax||'css';
-var context=options&&options.context||'stylesheet';
-var tabSize=options&&options.tabSize;
-if(!isInteger(tabSize)||tabSize<1)tabSize=1;
-
-var syntaxParser=undefined;
-if(syntaxes[syntax]){
-syntaxParser=syntaxes[syntax];
-}else{
-syntaxParser=syntaxes;
-}
-
-if(!syntaxParser){
-var message='Syntax "'+syntax+'" is not supported yet, sorry';
-return console.error(message);
-}
-
-var getTokens=syntaxParser.tokenizer;
-var mark=syntaxParser.mark;
-var parse=syntaxParser.parse;
-
-var tokens=getTokens(css,tabSize);
-mark(tokens);
-
-var ast;
-try{
-ast=parse(tokens,context);
-}catch(e){
-if(!e.syntax)throw e;
-throw new ParsingError(e,css);
-}
-
-return ast;
-}
-
-module.exports=parser;
-
-},
-
-function(module,exports,__webpack_require__){
-
-'use strict';
-
-var parserPackage=__webpack_require__(9);
-
-
-
-
-
-function ParsingError(e,css){
-this.line=e.line;
-this.syntax=e.syntax;
-this.css_=css;
-}
-
-ParsingError.prototype=Object.defineProperties({
-
-
-
-
-customMessage_:'',
-
-
-
-
-line:null,
-
-
-
-
-name:'Parsing error',
-
-
-
-
-syntax:null,
-
-
-
-
-version:parserPackage.version,
-
-
-
-
-toString:function(){
-return[this.name+': '+this.message,'',this.context,'','Syntax: '+this.syntax,'Gonzales PE version: '+this.version].join('\n');
-}},
+/**
+ * @param {!Gonzales.Node} node
+ * @param {!Array<{node: !Gonzales.Node, properties: !Array<!Gonzales.Node>}>} blocks
+ * @param {!{node: !Gonzales.Node, properties: !Array<!Gonzales.Node>}} lastBlock
+ */
+WebInspector.SCSSParser.extractNodes = function(node, blocks, lastBlock)
 {
-context:{
-
-
-
-get:function(){
-var LINES_AROUND=2;
-
-var result=[];
-var currentLineNumber=this.line;
-var start=currentLineNumber-1-LINES_AROUND;
-var end=currentLineNumber+LINES_AROUND;
-var lines=this.css_.split(/\r\n|\r|\n/);
-
-for(var i=start;i<end;i++){
-var line=lines[i];
-if(!line)continue;
-var ln=i+1;
-var mark=ln===currentLineNumber?'*':' ';
-result.push(ln+mark+'| '+line);
+    if (!Array.isArray(node.content))
+        return;
+    if (node.type === "block") {
+        lastBlock = {
+            node: node,
+            properties: []
+        };
+        blocks.push(lastBlock);
+    }
+    var lastDeclaration = null;
+    var selectors = [];
+    for (var i = 0; i < node.content.length; ++i) {
+        var child = node.content[i];
+        if (child.type === "declarationDelimiter" && lastDeclaration) {
+            lastDeclaration.declarationRange.endLine = child.end.line - 1;
+            lastDeclaration.declarationRange.endColumn = child.end.column;
+            lastDeclaration = null;
+        } else if (child.type === "selector") {
+            selectors.push(child);
+        } else if (child.type === "block") {
+            child.selectors = selectors;
+            selectors = [];
+        }
+        if (child.type === "include" || child.type === "declaration" || child.type === "multilineComment")
+            lastBlock.properties.push(child);
+        if (child.type === "declaration") {
+            lastDeclaration = child;
+            lastDeclaration.declarationRange = WebInspector.TextRange.createFromLocation(child.start.line - 1, child.start.column - 1);
+        }
+        WebInspector.SCSSParser.extractNodes(child, blocks, lastBlock);
+    }
+    if (lastDeclaration) {
+        lastDeclaration.declarationRange.endLine = node.end.line - 1;
+        lastDeclaration.declarationRange.endColumn = node.end.column - 1;
+    }
 }
 
-return result.join('\n');
-},
-configurable:true,
-enumerable:true},
+},{}],244:[function(require,module,exports){
+(function webpackUniversalModuleDefinition(root, factory) {
+	if(typeof exports === 'object' && typeof module === 'object')
+		module.exports = factory();
+	else if(typeof define === 'function' && define.amd)
+		define([], factory);
+	else if(typeof exports === 'object')
+		exports["gonzales"] = factory();
+	else
+		root["gonzales"] = factory();
+})(this, function() {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
 
-message:{
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
 
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId])
+/******/ 			return installedModules[moduleId].exports;
 
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			exports: {},
+/******/ 			id: moduleId,
+/******/ 			loaded: false
+/******/ 		};
 
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
 
+/******/ 		// Flag the module as loaded
+/******/ 		module.loaded = true;
 
-get:function(){
-if(this.customMessage_){
-return this.customMessage_;
-}else{
-var message='Please check validity of the block';
-if(typeof this.line==='number')message+=' starting from line #'+this.line;
-return message;
-}
-},
-set:function(message){
-this.customMessage_=message;
-},
-configurable:true,
-enumerable:true}});
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
 
 
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
 
-module.exports=ParsingError;
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
 
-},
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
 
-function(module,exports){
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
 
-module.exports={
-"name":"gonzales-pe",
-"description":"Gonzales Preprocessor Edition (fast CSS parser)",
-"version":"3.3.1",
-"homepage":"http://github.com/tonyganch/gonzales-pe",
-"bugs":"http://github.com/tonyganch/gonzales-pe/issues",
-"license":"MIT",
-"author":{
-"name":"Tony Ganch",
-"email":"tonyganch+github@gmail.com",
-"url":"http://tonyganch.com"},
+	'use strict';
 
-"main":"./lib/gonzales",
-"repository":{
-"type":"git",
-"url":"http://github.com/tonyganch/gonzales-pe.git"},
+	var Node = __webpack_require__(1);
+	var parse = __webpack_require__(7);
 
-"scripts":{
-"autofix-tests":"bash ./scripts/build.sh && bash ./scripts/autofix-tests.sh",
-"build":"bash ./scripts/build.sh",
-"init":"bash ./scripts/init.sh",
-"log":"bash ./scripts/log.sh",
-"prepublish":"bash ./scripts/prepublish.sh",
-"postpublish":"bash ./scripts/postpublish.sh",
-"test":"bash ./scripts/build.sh && bash ./scripts/test.sh",
-"watch":"bash ./scripts/watch.sh"},
+	module.exports = {
+	  createNode: function (options) {
+	    return new Node(options);
+	  },
+	  parse: parse
+	};
 
-"bin":{
-"gonzales":"./bin/gonzales.js"},
+/***/ },
+/* 1 */
+/***/ function(module, exports, __webpack_require__) {
 
-"dependencies":{
-"minimist":"1.1.x"},
+	'use strict';
 
-"devDependencies":{
-"babel-loader":"^5.3.2",
-"coffee-script":"~1.7.1",
-"jscs":"2.1.0",
-"jshint":"2.8.0",
-"json-loader":"^0.5.3",
-"mocha":"2.2.x",
-"webpack":"^1.12.2"},
+	/**
+	 * @param {string} type
+	 * @param {array|string} content
+	 * @param {number} line
+	 * @param {number} column
+	 * @constructor
+	 */
 
-"engines":{
-"node":">=0.6.0"}};
+	var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
 
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
 
+	var Node = (function () {
+	  function Node(options) {
+	    _classCallCheck(this, Node);
 
-},
+	    this.type = options.type;
+	    this.content = options.content;
+	    this.syntax = options.syntax;
 
-function(module,exports,__webpack_require__){
+	    if (options.start) this.start = options.start;
+	    if (options.end) this.end = options.end;
+	  }
 
-'use strict';
+	  /**
+	   * @param {String} type Node type
+	   * @return {Boolean} Whether there is a child node of given type
+	   */
 
-exports.__esModule=true;
-exports['default']={
-mark:__webpack_require__(11),
-parse:__webpack_require__(13),
-stringify:__webpack_require__(6),
-tokenizer:__webpack_require__(15)};
+	  Node.prototype.contains = function contains(type) {
+	    return this.content.some(function (node) {
+	      return node.type === type;
+	    });
+	  };
 
-module.exports=exports['default'];
+	  /**
+	   * @param {String} type Node type
+	   * @param {Function} callback Function to call for every found node
+	   */
 
-},
+	  Node.prototype.eachFor = function eachFor(type, callback) {
+	    if (!Array.isArray(this.content)) return;
 
-function(module,exports,__webpack_require__){
+	    if (typeof type !== 'string') {
+	      callback = type;
+	      type = null;
+	    }
 
-'use strict';
+	    var l = this.content.length;
+	    var breakLoop;
 
-var TokenType=__webpack_require__(12);
+	    for (var i = l; i--;) {
+	      if (breakLoop === null) break;
 
-module.exports=function(){
+	      if (!type || this.content[i] && this.content[i].type === type) breakLoop = callback(this.content[i], i, this);
+	    }
 
+	    if (breakLoop === null) return null;
+	  };
 
+	  /**
+	   * @param {String} type
+	   * @return {?Node} First child node or `null` if nothing's been found.
+	   */
 
-function markSC(tokens){
-var tokensLength=tokens.length;
-var ws=-1;
-var sc=-1;
-var t=undefined;
+	  Node.prototype.first = function first(type) {
+	    if (!Array.isArray(this.content)) return null;
 
+	    if (!type) return this.content[0];
 
+	    var i = 0;
+	    var l = this.content.length;
 
+	    for (; i < l; i++) {
+	      if (this.content[i].type === type) return this.content[i];
+	    }
 
+	    return null;
+	  };
 
+	  /**
+	   * @param {String} type Node type
+	   * @param {Function} callback Function to call for every found node
+	   */
 
+	  Node.prototype.forEach = function forEach(type, callback) {
+	    if (!Array.isArray(this.content)) return;
 
+	    if (typeof type !== 'string') {
+	      callback = type;
+	      type = null;
+	    }
 
+	    var i = 0;
+	    var l = this.content.length;
+	    var breakLoop;
 
-for(var i=0;i<tokensLength;i++){
-t=tokens[i];
-switch(t.type){
-case TokenType.Space:
-case TokenType.Tab:
-case TokenType.Newline:
-t.ws=true;
-t.sc=true;
+	    for (; i < l; i++) {
+	      if (breakLoop === null) break;
 
-if(ws===-1)ws=i;
-if(sc===-1)sc=i;
+	      if (!type || this.content[i] && this.content[i].type === type) breakLoop = callback(this.content[i], i, this);
+	    }
 
-break;
-case TokenType.CommentML:
-case TokenType.CommentSL:
-if(ws!==-1){
-tokens[ws].ws_last=i-1;
-ws=-1;
-}
+	    if (breakLoop === null) return null;
+	  };
 
-t.sc=true;
+	  /**
+	   * @param {Number} index
+	   * @return {?Node}
+	   */
 
-break;
-default:
-if(ws!==-1){
-tokens[ws].ws_last=i-1;
-ws=-1;
-}
+	  Node.prototype.get = function get(index) {
+	    if (!Array.isArray(this.content)) return null;
 
-if(sc!==-1){
-tokens[sc].sc_last=i-1;
-sc=-1;
-}}
+	    var node = this.content[index];
+	    return node ? node : null;
+	  };
 
-}
+	  /**
+	   * @param {Number} index
+	   * @param {Node} node
+	   */
 
-if(ws!==-1)tokens[ws].ws_last=i-1;
-if(sc!==-1)tokens[sc].sc_last=i-1;
-}
+	  Node.prototype.insert = function insert(index, node) {
+	    if (!Array.isArray(this.content)) return;
 
+	    this.content.splice(index, 0, node);
+	  };
 
+	  /**
+	   * @param {String} type
+	   * @return {Boolean} Whether the node is of given type
+	   */
 
+	  Node.prototype.is = function is(type) {
+	    return this.type === type;
+	  };
 
-function markBrackets(tokens){
-var tokensLength=tokens.length;
-var ps=[];
-var sbs=[];
-var cbs=[];
-var t=undefined;
+	  /**
+	   * @param {String} type
+	   * @return {?Node} Last child node or `null` if nothing's been found.
+	   */
 
+	  Node.prototype.last = function last(type) {
+	    if (!Array.isArray(this.content)) return null;
 
+	    var i = this.content.length - 1;
+	    if (!type) return this.content[i];
 
+	    for (;; i--) {
+	      if (this.content[i].type === type) return this.content[i];
+	    }
 
+	    return null;
+	  };
 
+	  /**
+	   * Number of child nodes.
+	   * @type {number}
+	   */
 
+	  /**
+	   * @param {Number} index
+	   * @return {Node}
+	   */
 
+	  Node.prototype.removeChild = function removeChild(index) {
+	    if (!Array.isArray(this.content)) return;
 
-for(var i=0;i<tokensLength;i++){
-t=tokens[i];
-switch(t.type){
-case TokenType.LeftParenthesis:
-ps.push(i);
-break;
-case TokenType.RightParenthesis:
-if(ps.length){
-t.left=ps.pop();
-tokens[t.left].right=i;
-}
-break;
-case TokenType.LeftSquareBracket:
-sbs.push(i);
-break;
-case TokenType.RightSquareBracket:
-if(sbs.length){
-t.left=sbs.pop();
-tokens[t.left].right=i;
-}
-break;
-case TokenType.LeftCurlyBracket:
-cbs.push(i);
-break;
-case TokenType.RightCurlyBracket:
-if(cbs.length){
-t.left=cbs.pop();
-tokens[t.left].right=i;
-}
-break;}
+	    var removedChild = this.content.splice(index, 1);
 
-}
-}
+	    return removedChild;
+	  };
 
-return function(tokens){
-markBrackets(tokens);
-markSC(tokens);
-};
-}();
+	  Node.prototype.toJson = function toJson() {
+	    return JSON.stringify(this, false, 2);
+	  };
 
-},
+	  Node.prototype.toString = function toString() {
+	    var stringify = undefined;
 
-function(module,exports){
+	    try {
+	      stringify = __webpack_require__(2)("./" + this.syntax + '/stringify');
+	    } catch (e) {
+	      var message = 'Syntax "' + this.syntax + '" is not supported yet, sorry';
+	      return console.error(message);
+	    }
 
+	    return stringify(this);
+	  };
 
+	  /**
+	   * @param {Function} callback
+	   */
 
-'use strict';
+	  Node.prototype.traverse = function traverse(callback, index) {
+	    var level = arguments.length <= 2 || arguments[2] === undefined ? 0 : arguments[2];
+	    var parent = arguments.length <= 3 || arguments[3] === undefined ? null : arguments[3];
 
-module.exports={
-StringSQ:'StringSQ',
-StringDQ:'StringDQ',
-CommentML:'CommentML',
-CommentSL:'CommentSL',
+	    var breakLoop;
+	    var x;
 
-Newline:'Newline',
-Space:'Space',
-Tab:'Tab',
+	    level++;
 
-ExclamationMark:'ExclamationMark',
-QuotationMark:'QuotationMark',
-NumberSign:'NumberSign',
-DollarSign:'DollarSign',
-PercentSign:'PercentSign',
-Ampersand:'Ampersand',
-Apostrophe:'Apostrophe',
-LeftParenthesis:'LeftParenthesis',
-RightParenthesis:'RightParenthesis',
-Asterisk:'Asterisk',
-PlusSign:'PlusSign',
-Comma:'Comma',
-HyphenMinus:'HyphenMinus',
-FullStop:'FullStop',
-Solidus:'Solidus',
-Colon:'Colon',
-Semicolon:'Semicolon',
-LessThanSign:'LessThanSign',
-EqualsSign:'EqualsSign',
-EqualitySign:'EqualitySign',
-InequalitySign:'InequalitySign',
-GreaterThanSign:'GreaterThanSign',
-QuestionMark:'QuestionMark',
-CommercialAt:'CommercialAt',
-LeftSquareBracket:'LeftSquareBracket',
-ReverseSolidus:'ReverseSolidus',
-RightSquareBracket:'RightSquareBracket',
-CircumflexAccent:'CircumflexAccent',
-LowLine:'LowLine',
-LeftCurlyBracket:'LeftCurlyBracket',
-VerticalLine:'VerticalLine',
-RightCurlyBracket:'RightCurlyBracket',
-Tilde:'Tilde',
+	    callback(this, index, parent, level);
 
-Identifier:'Identifier',
-DecimalNumber:'DecimalNumber'};
+	    if (!Array.isArray(this.content)) return;
 
+	    for (var i = 0, l = this.content.length; i < l; i++) {
+	      breakLoop = this.content[i].traverse(callback, i, level, this);
+	      if (breakLoop === null) break;
 
-},
+	      // If some nodes were removed or added:
+	      if (x = this.content.length - l) {
+	        l += x;
+	        i += x;
+	      }
+	    }
 
-function(module,exports,__webpack_require__){
+	    if (breakLoop === null) return null;
+	  };
 
+	  Node.prototype.traverseByType = function traverseByType(type, callback) {
+	    this.traverse(function (node) {
+	      if (node.type === type) callback.apply(node, arguments);
+	    });
+	  };
 
-'use strict';var Node=__webpack_require__(1);var NodeType=__webpack_require__(14);var TokenType=__webpack_require__(12);var tokens=undefined;var tokensLength=undefined;var pos=undefined;var contexts={'arguments':function(){return checkArguments(pos)&&getArguments();},'atkeyword':function(){return checkAtkeyword(pos)&&getAtkeyword();},'atrule':function(){return checkAtrule(pos)&&getAtrule();},'block':function(){return checkBlock(pos)&&getBlock();},'brackets':function(){return checkBrackets(pos)&&getBrackets();},'class':function(){return checkClass(pos)&&getClass();},'combinator':function(){return checkCombinator(pos)&&getCombinator();},'commentML':function(){return checkCommentML(pos)&&getCommentML();},'commentSL':function(){return checkCommentSL(pos)&&getCommentSL();},'condition':function(){return checkCondition(pos)&&getCondition();},'conditionalStatement':function(){return checkConditionalStatement(pos)&&getConditionalStatement();},'declaration':function(){return checkDeclaration(pos)&&getDeclaration();},'declDelim':function(){return checkDeclDelim(pos)&&getDeclDelim();},'default':function(){return checkDefault(pos)&&getDefault();},'delim':function(){return checkDelim(pos)&&getDelim();},'dimension':function(){return checkDimension(pos)&&getDimension();},'expression':function(){return checkExpression(pos)&&getExpression();},'extend':function(){return checkExtend(pos)&&getExtend();},'function':function(){return checkFunction(pos)&&getFunction();},'global':function(){return checkGlobal(pos)&&getGlobal();},'ident':function(){return checkIdent(pos)&&getIdent();},'important':function(){return checkImportant(pos)&&getImportant();},'include':function(){return checkInclude(pos)&&getInclude();},'interpolation':function(){return checkInterpolation(pos)&&getInterpolation();},'loop':function(){return checkLoop(pos)&&getLoop();},'mixin':function(){return checkMixin(pos)&&getMixin();},'namespace':function(){return checkNamespace(pos)&&getNamespace();},'number':function(){return checkNumber(pos)&&getNumber();},'operator':function(){return checkOperator(pos)&&getOperator();},'optional':function(){return checkOptional(pos)&&getOptional();},'parentheses':function(){return checkParentheses(pos)&&getParentheses();},'parentselector':function(){return checkParentSelector(pos)&&getParentSelector();},'percentage':function(){return checkPercentage(pos)&&getPercentage();},'placeholder':function(){return checkPlaceholder(pos)&&getPlaceholder();},'progid':function(){return checkProgid(pos)&&getProgid();},'property':function(){return checkProperty(pos)&&getProperty();},'propertyDelim':function(){return checkPropertyDelim(pos)&&getPropertyDelim();},'pseudoc':function(){return checkPseudoc(pos)&&getPseudoc();},'pseudoe':function(){return checkPseudoe(pos)&&getPseudoe();},'ruleset':function(){return checkRuleset(pos)&&getRuleset();},'s':function(){return checkS(pos)&&getS();},'selector':function(){return checkSelector(pos)&&getSelector();},'shash':function(){return checkShash(pos)&&getShash();},'string':function(){return checkString(pos)&&getString();},'stylesheet':function(){return checkStylesheet(pos)&&getStylesheet();},'unary':function(){return checkUnary(pos)&&getUnary();},'uri':function(){return checkUri(pos)&&getUri();},'value':function(){return checkValue(pos)&&getValue();},'variable':function(){return checkVariable(pos)&&getVariable();},'variableslist':function(){return checkVariablesList(pos)&&getVariablesList();},'vhash':function(){return checkVhash(pos)&&getVhash();}};
+	  Node.prototype.traverseByTypes = function traverseByTypes(types, callback) {
+	    this.traverse(function (node) {
+	      if (types.indexOf(node.type) !== -1) callback.apply(node, arguments);
+	    });
+	  };
 
+	  _createClass(Node, [{
+	    key: 'length',
+	    get: function () {
+	      if (!Array.isArray(this.content)) return 0;
+	      return this.content.length;
+	    }
+	  }]);
 
-function throwError(i){var ln=i?tokens[i].ln:tokens[pos].ln;throw{line:ln,syntax:'scss'};}
+	  return Node;
+	})();
 
+	module.exports = Node;
 
+/***/ },
+/* 2 */
+/***/ function(module, exports, __webpack_require__) {
 
-function checkExcluding(exclude,i){var start=i;while(i<tokensLength){if(exclude[tokens[i++].type])break;}return i-start-2;}
+	var map = {
+		"./css/stringify": 3,
+		"./less/stringify": 4,
+		"./sass/stringify": 5,
+		"./scss/stringify": 6
+	};
+	function webpackContext(req) {
+		return __webpack_require__(webpackContextResolve(req));
+	};
+	function webpackContextResolve(req) {
+		return map[req] || (function() { throw new Error("Cannot find module '" + req + "'.") }());
+	};
+	webpackContext.keys = function webpackContextKeys() {
+		return Object.keys(map);
+	};
+	webpackContext.resolve = webpackContextResolve;
+	module.exports = webpackContext;
+	webpackContext.id = 2;
 
 
+/***/ },
+/* 3 */
+/***/ function(module, exports) {
 
-function joinValues(start,finish){var s='';for(var i=start;i<finish+1;i++){s+=tokens[i].value;}return s;}
+	// jscs:disable maximumLineLength
 
+	'use strict';
 
+	module.exports = function stringify(tree) {
+	  // TODO: Better error message
+	  if (!tree) throw new Error('We need tree to translate');
 
-function joinValues2(start,num){if(start+num-1>=tokensLength)return;var s='';for(var i=0;i<num;i++){s+=tokens[start+i].value;}return s;}function getLastPosition(content,line,column,colOffset){return typeof content==='string'?getLastPositionForString(content,line,column,colOffset):getLastPositionForArray(content,line,column,colOffset);}function getLastPositionForString(content,line,column,colOffset){var position=[];if(!content){position=[line,column];if(colOffset)position[1]+=colOffset-1;return position;}var lastLinebreak=content.lastIndexOf('\n');var endsWithLinebreak=lastLinebreak===content.length-1;var splitContent=content.split('\n');var linebreaksCount=splitContent.length-1;var prevLinebreak=linebreaksCount===0||linebreaksCount===1?-1:content.length-splitContent[linebreaksCount-1].length-2;
-var offset=endsWithLinebreak?linebreaksCount-1:linebreaksCount;position[0]=line+offset;
-if(endsWithLinebreak){offset=prevLinebreak!==-1?content.length-prevLinebreak:content.length-1;}else{offset=linebreaksCount!==0?content.length-lastLinebreak-column-1:content.length-1;}position[1]=column+offset;if(!colOffset)return position;if(endsWithLinebreak){position[0]++;position[1]=colOffset;}else{position[1]+=colOffset;}return position;}function getLastPositionForArray(content,line,column,colOffset){var position;if(content.length===0){position=[line,column];}else{var c=content[content.length-1];if(c.hasOwnProperty('end')){position=[c.end.line,c.end.column];}else{position=getLastPosition(c.content,line,column);}}if(!colOffset)return position;if(tokens[pos-1].type!=='Newline'){position[1]+=colOffset;}else{position[0]++;position[1]=1;}return position;}function newNode(type,content,line,column,end){if(!end)end=getLastPosition(content,line,column);return new Node({type:type,content:content,start:{line:line,column:column},end:{line:end[0],column:end[1]},syntax:'scss'});}
+	  function _t(tree) {
+	    var type = tree.type;
+	    if (_unique[type]) return _unique[type](tree);
+	    if (typeof tree.content === 'string') return tree.content;
+	    if (Array.isArray(tree.content)) return _composite(tree.content);
+	    return '';
+	  }
 
+	  function _composite(t, i) {
+	    if (!t) return '';
 
-function checkAny(i){return checkBrackets(i)||checkParentheses(i)||checkString(i)||checkVariablesList(i)||checkVariable(i)||checkPlaceholder(i)||checkPercentage(i)||checkDimension(i)||checkNumber(i)||checkUri(i)||checkExpression(i)||checkFunction(i)||checkInterpolation(i)||checkIdent(i)||checkClass(i)||checkUnary(i);}
+	    var s = '';
+	    i = i || 0;
+	    for (; i < t.length; i++) s += _t(t[i]);
+	    return s;
+	  }
 
-function getAny(){if(checkBrackets(pos))return getBrackets();else if(checkParentheses(pos))return getParentheses();else if(checkString(pos))return getString();else if(checkVariablesList(pos))return getVariablesList();else if(checkVariable(pos))return getVariable();else if(checkPlaceholder(pos))return getPlaceholder();else if(checkPercentage(pos))return getPercentage();else if(checkDimension(pos))return getDimension();else if(checkNumber(pos))return getNumber();else if(checkUri(pos))return getUri();else if(checkExpression(pos))return getExpression();else if(checkFunction(pos))return getFunction();else if(checkInterpolation(pos))return getInterpolation();else if(checkIdent(pos))return getIdent();else if(checkClass(pos))return getClass();else if(checkUnary(pos))return getUnary();}
+	  var _unique = {
+	    'arguments': function (t) {
+	      return '(' + _composite(t.content) + ')';
+	    },
+	    'atkeyword': function (t) {
+	      return '@' + _composite(t.content);
+	    },
+	    'attributeSelector': function (t) {
+	      return '[' + _composite(t.content) + ']';
+	    },
+	    'block': function (t) {
+	      return '{' + _composite(t.content) + '}';
+	    },
+	    'brackets': function (t) {
+	      return '[' + _composite(t.content) + ']';
+	    },
+	    'class': function (t) {
+	      return '.' + _composite(t.content);
+	    },
+	    'color': function (t) {
+	      return '#' + t.content;
+	    },
+	    'expression': function (t) {
+	      return 'expression(' + t.content + ')';
+	    },
+	    'id': function (t) {
+	      return '#' + _composite(t.content);
+	    },
+	    'multilineComment': function (t) {
+	      return '/*' + t.content + '*/';
+	    },
+	    'nthSelector': function (t) {
+	      return ':' + _t(t.content[0]) + '(' + _composite(t.content.slice(1)) + ')';
+	    },
+	    'parentheses': function (t) {
+	      return '(' + _composite(t.content) + ')';
+	    },
+	    'percentage': function (t) {
+	      return _composite(t.content) + '%';
+	    },
+	    'pseudoClass': function (t) {
+	      return ':' + _composite(t.content);
+	    },
+	    'pseudoElement': function (t) {
+	      return '::' + _composite(t.content);
+	    },
+	    'uri': function (t) {
+	      return 'url(' + _composite(t.content) + ')';
+	    }
+	  };
 
+	  return _t(tree);
+	};
 
+/***/ },
+/* 4 */
+/***/ function(module, exports) {
 
-function checkArguments(i){var start=i;var l=undefined;if(i>=tokensLength||tokens[i].type!==TokenType.LeftParenthesis)return 0;i++;while(i<tokens[start].right){if(l=checkArgument(i))i+=l;else return 0;}return tokens[start].right-start+1;}
+	// jscs:disable maximumLineLength
 
+	'use strict';
 
+	module.exports = function stringify(tree) {
+	  // TODO: Better error message
+	  if (!tree) throw new Error('We need tree to translate');
 
-function checkArgument(i){return checkBrackets(i)||checkParentheses(i)||checkDeclaration(i)||checkFunction(i)||checkVariablesList(i)||checkVariable(i)||checkSC(i)||checkDelim(i)||checkDeclDelim(i)||checkString(i)||checkPercentage(i)||checkDimension(i)||checkNumber(i)||checkUri(i)||checkInterpolation(i)||checkIdent(i)||checkVhash(i)||checkOperator(i)||checkUnary(i);}
+	  function _t(tree) {
+	    var type = tree.type;
+	    if (_unique[type]) return _unique[type](tree);
+	    if (typeof tree.content === 'string') return tree.content;
+	    if (Array.isArray(tree.content)) return _composite(tree.content);
+	    return '';
+	  }
 
-function getArgument(){if(checkBrackets(pos))return getBrackets();else if(checkParentheses(pos))return getParentheses();else if(checkDeclaration(pos))return getDeclaration();else if(checkFunction(pos))return getFunction();else if(checkVariablesList(pos))return getVariablesList();else if(checkVariable(pos))return getVariable();else if(checkSC(pos))return getSC();else if(checkDelim(pos))return getDelim();else if(checkDeclDelim(pos))return getDeclDelim();else if(checkString(pos))return getString();else if(checkPercentage(pos))return getPercentage();else if(checkDimension(pos))return getDimension();else if(checkNumber(pos))return getNumber();else if(checkUri(pos))return getUri();else if(checkInterpolation(pos))return getInterpolation();else if(checkIdent(pos))return getIdent();else if(checkVhash(pos))return getVhash();else if(checkOperator(pos))return getOperator();else if(checkUnary(pos))return getUnary();}
+	  function _composite(t, i) {
+	    if (!t) return '';
 
+	    var s = '';
+	    i = i || 0;
+	    for (; i < t.length; i++) s += _t(t[i]);
+	    return s;
+	  }
 
+	  var _unique = {
+	    'arguments': function (t) {
+	      return '(' + _composite(t.content) + ')';
+	    },
+	    'atkeyword': function (t) {
+	      return '@' + _composite(t.content);
+	    },
+	    'attributeSelector': function (t) {
+	      return '[' + _composite(t.content) + ']';
+	    },
+	    'block': function (t) {
+	      return '{' + _composite(t.content) + '}';
+	    },
+	    'brackets': function (t) {
+	      return '[' + _composite(t.content) + ']';
+	    },
+	    'class': function (t) {
+	      return '.' + _composite(t.content);
+	    },
+	    'color': function (t) {
+	      return '#' + t.content;
+	    },
+	    'escapedString': function (t) {
+	      return '~' + t.content;
+	    },
+	    'expression': function (t) {
+	      return 'expression(' + t.content + ')';
+	    },
+	    'id': function (t) {
+	      return '#' + _composite(t.content);
+	    },
+	    'interpolatedVariable': function (t) {
+	      return '@{' + _composite(t.content) + '}';
+	    },
+	    'multilineComment': function (t) {
+	      return '/*' + t.content + '*/';
+	    },
+	    'nthSelector': function (t) {
+	      return ':' + _t(t.content[0]) + '(' + _composite(t.content.slice(1)) + ')';
+	    },
+	    'parentheses': function (t) {
+	      return '(' + _composite(t.content) + ')';
+	    },
+	    'percentage': function (t) {
+	      return _composite(t.content) + '%';
+	    },
+	    'pseudoClass': function (t) {
+	      return ':' + _composite(t.content);
+	    },
+	    'pseudoElement': function (t) {
+	      return '::' + _composite(t.content);
+	    },
+	    'singlelineComment': function (t) {
+	      return '/' + '/' + t.content;
+	    },
+	    'uri': function (t) {
+	      return 'url(' + _composite(t.content) + ')';
+	    },
+	    'variable': function (t) {
+	      return '@' + _composite(t.content);
+	    },
+	    'variablesList': function (t) {
+	      return _composite(t.content) + '...';
+	    }
+	  };
 
-function checkAtkeyword(i){var l;
-if(i>=tokensLength||tokens[i++].type!==TokenType.CommercialAt)return 0;return(l=checkIdentOrInterpolation(i))?l+1:0;}
+	  return _t(tree);
+	};
 
+/***/ },
+/* 5 */
+/***/ function(module, exports) {
 
+	// jscs:disable maximumLineLength
 
+	'use strict';
 
-function getAtkeyword(){var startPos=pos;var x=undefined;pos++;x=getIdentOrInterpolation();var token=tokens[startPos];return newNode(NodeType.AtkeywordType,x,token.ln,token.col);}
+	module.exports = function stringify(tree) {
+	  // TODO: Better error message
+	  if (!tree) throw new Error('We need tree to translate');
 
+	  function _t(tree) {
+	    var type = tree.type;
+	    if (_unique[type]) return _unique[type](tree);
+	    if (typeof tree.content === 'string') return tree.content;
+	    if (Array.isArray(tree.content)) return _composite(tree.content);
+	    return '';
+	  }
 
+	  function _composite(t, i) {
+	    if (!t) return '';
 
-function checkAtrule(i){var l;if(i>=tokensLength)return 0;
+	    var s = '';
+	    i = i || 0;
+	    for (; i < t.length; i++) s += _t(t[i]);
+	    return s;
+	  }
 
-if(tokens[i].atrule_l!==undefined)return tokens[i].atrule_l;
-if(l=checkKeyframesRule(i))tokens[i].atrule_type=4;else if(l=checkAtruler(i))tokens[i].atrule_type=1;else
-if(l=checkAtruleb(i))tokens[i].atrule_type=2;else
-if(l=checkAtrules(i))tokens[i].atrule_type=3;else
-return 0;
-tokens[i].atrule_l=l;return l;}
+	  var _unique = {
+	    'arguments': function (t) {
+	      return '(' + _composite(t.content) + ')';
+	    },
+	    'atkeyword': function (t) {
+	      return '@' + _composite(t.content);
+	    },
+	    'attributeSelector': function (t) {
+	      return '[' + _composite(t.content) + ']';
+	    },
+	    'block': function (t) {
+	      return _composite(t.content);
+	    },
+	    'brackets': function (t) {
+	      return '[' + _composite(t.content) + ']';
+	    },
+	    'class': function (t) {
+	      return '.' + _composite(t.content);
+	    },
+	    'color': function (t) {
+	      return '#' + t.content;
+	    },
+	    'expression': function (t) {
+	      return 'expression(' + t.content + ')';
+	    },
+	    'id': function (t) {
+	      return '#' + _composite(t.content);
+	    },
+	    'interpolation': function (t) {
+	      return '#{' + _composite(t.content) + '}';
+	    },
+	    'multilineComment': function (t) {
+	      return '/*' + t.content;
+	    },
+	    'nthSelector': function (t) {
+	      return ':' + _t(t.content[0]) + '(' + _composite(t.content.slice(1)) + ')';
+	    },
+	    'parentheses': function (t) {
+	      return '(' + _composite(t.content) + ')';
+	    },
+	    'percentage': function (t) {
+	      return _composite(t.content) + '%';
+	    },
+	    'placeholder': function (t) {
+	      return '%' + _composite(t.content);
+	    },
+	    'pseudoClass': function (t) {
+	      return ':' + _composite(t.content);
+	    },
+	    'pseudoElement': function (t) {
+	      return '::' + _composite(t.content);
+	    },
+	    'singlelineComment': function (t) {
+	      return '/' + '/' + t.content;
+	    },
+	    'uri': function (t) {
+	      return 'url(' + _composite(t.content) + ')';
+	    },
+	    'variable': function (t) {
+	      return '$' + _composite(t.content);
+	    },
+	    'variablesList': function (t) {
+	      return _composite(t.content) + '...';
+	    }
+	  };
 
+	  return _t(tree);
+	};
 
-function getAtrule(){switch(tokens[pos].atrule_type){case 1:return getAtruler();
-case 2:return getAtruleb();
-case 3:return getAtrules();
-case 4:return getKeyframesRule();}}
+/***/ },
+/* 6 */
+/***/ function(module, exports) {
 
+	// jscs:disable maximumLineLength
 
+	'use strict';
 
-function checkAtruleb(i){var start=i;var l=undefined;if(i>=tokensLength)return 0;if(l=checkAtkeyword(i))i+=l;else return 0;if(l=checkTsets(i))i+=l;if(l=checkBlock(i))i+=l;else return 0;return i-start;}
+	module.exports = function stringify(tree) {
+	  // TODO: Better error message
+	  if (!tree) throw new Error('We need tree to translate');
 
+	  function _t(tree) {
+	    var type = tree.type;
+	    if (_unique[type]) return _unique[type](tree);
+	    if (typeof tree.content === 'string') return tree.content;
+	    if (Array.isArray(tree.content)) return _composite(tree.content);
+	    return '';
+	  }
 
-function getAtruleb(){var startPos=pos;var x=undefined;x=[getAtkeyword()].concat(getTsets()).concat([getBlock()]);var token=tokens[startPos];return newNode(NodeType.AtruleType,x,token.ln,token.col);}
+	  function _composite(t, i) {
+	    if (!t) return '';
 
+	    var s = '';
+	    i = i || 0;
+	    for (; i < t.length; i++) s += _t(t[i]);
+	    return s;
+	  }
 
+	  var _unique = {
+	    'arguments': function (t) {
+	      return '(' + _composite(t.content) + ')';
+	    },
+	    'atkeyword': function (t) {
+	      return '@' + _composite(t.content);
+	    },
+	    'attributeSelector': function (t) {
+	      return '[' + _composite(t.content) + ']';
+	    },
+	    'block': function (t) {
+	      return '{' + _composite(t.content) + '}';
+	    },
+	    'brackets': function (t) {
+	      return '[' + _composite(t.content) + ']';
+	    },
+	    'class': function (t) {
+	      return '.' + _composite(t.content);
+	    },
+	    'color': function (t) {
+	      return '#' + t.content;
+	    },
+	    'expression': function (t) {
+	      return 'expression(' + t.content + ')';
+	    },
+	    'id': function (t) {
+	      return '#' + _composite(t.content);
+	    },
+	    'interpolation': function (t) {
+	      return '#{' + _composite(t.content) + '}';
+	    },
+	    'multilineComment': function (t) {
+	      return '/*' + t.content + '*/';
+	    },
+	    'nthSelector': function (t) {
+	      return ':' + _t(t.content[0]) + '(' + _composite(t.content.slice(1)) + ')';
+	    },
+	    'parentheses': function (t) {
+	      return '(' + _composite(t.content) + ')';
+	    },
+	    'percentage': function (t) {
+	      return _composite(t.content) + '%';
+	    },
+	    'placeholder': function (t) {
+	      return '%' + _composite(t.content);
+	    },
+	    'pseudoClass': function (t) {
+	      return ':' + _composite(t.content);
+	    },
+	    'pseudoElement': function (t) {
+	      return '::' + _composite(t.content);
+	    },
+	    'singlelineComment': function (t) {
+	      return '/' + '/' + t.content;
+	    },
+	    'uri': function (t) {
+	      return 'url(' + _composite(t.content) + ')';
+	    },
+	    'variable': function (t) {
+	      return '$' + _composite(t.content);
+	    },
+	    'variablesList': function (t) {
+	      return _composite(t.content) + '...';
+	    }
+	  };
 
-function checkAtruler(i){var start=i;var l=undefined;if(i>=tokensLength)return 0;if(l=checkAtkeyword(i))i+=l;else return 0;if(l=checkTsets(i))i+=l;if(i<tokensLength&&tokens[i].type===TokenType.LeftCurlyBracket)i++;else return 0;if(l=checkAtrulers(i))i+=l;if(i<tokensLength&&tokens[i].type===TokenType.RightCurlyBracket)i++;else return 0;return i-start;}
+	  return _t(tree);
+	};
 
+/***/ },
+/* 7 */
+/***/ function(module, exports, __webpack_require__) {
 
-function getAtruler(){var startPos=pos;var x=undefined;x=[getAtkeyword()].concat(getTsets());x.push(getAtrulers());var token=tokens[startPos];return newNode(NodeType.AtruleType,x,token.ln,token.col);}
+	'use strict';
 
+	var ParsingError = __webpack_require__(8);
+	var syntaxes = __webpack_require__(10);
 
-function checkAtrulers(i){var start=i;var l=undefined;if(i>=tokensLength)return 0;while(l=checkRuleset(i)||checkAtrule(i)||checkSC(i)){i+=l;}if(i<tokensLength)tokens[i].atrulers_end=1;return i-start;}
+	var isInteger = Number.isInteger || function (value) {
+	  return typeof value === 'number' && Math.floor(value) === value;
+	};
 
-function getAtrulers(){var startPos=pos;var x=undefined;var token=tokens[startPos];var line=token.ln;var column=token.col;pos++;x=getSC();while(!tokens[pos].atrulers_end){if(checkSC(pos))x=x.concat(getSC());else if(checkAtrule(pos))x.push(getAtrule());else if(checkRuleset(pos))x.push(getRuleset());}x=x.concat(getSC());var end=getLastPosition(x,line,column,1);pos++;return newNode(NodeType.BlockType,x,token.ln,token.col,end);}
+	/**
+	 * @param {String} css
+	 * @param {Object} options
+	 * @return {Object} AST
+	 */
+	function parser(css, options) {
+	  if (typeof css !== 'string') throw new Error('Please, pass a string to parse');else if (!css) return __webpack_require__(16)();
 
+	  var syntax = options && options.syntax || 'css';
+	  var context = options && options.context || 'stylesheet';
+	  var tabSize = options && options.tabSize;
+	  if (!isInteger(tabSize) || tabSize < 1) tabSize = 1;
 
-function checkAtrules(i){var start=i;var l=undefined;if(i>=tokensLength)return 0;if(l=checkAtkeyword(i))i+=l;else return 0;if(l=checkTsets(i))i+=l;return i-start;}
-
-function getAtrules(){var startPos=pos;var x=undefined;x=[getAtkeyword()].concat(getTsets());var token=tokens[startPos];return newNode(NodeType.AtruleType,x,token.ln,token.col);}
-
-
-
-function checkBlock(i){return i<tokensLength&&tokens[i].type===TokenType.LeftCurlyBracket?tokens[i].right-i+1:0;}
-
-
-function getBlock(){var startPos=pos;var end=tokens[pos].right;var x=[];var token=tokens[startPos];var line=token.ln;var column=token.col;pos++;while(pos<end){if(checkBlockdecl(pos))x=x.concat(getBlockdecl());else throwError();}var end_=getLastPosition(x,line,column,1);pos=end+1;return newNode(NodeType.BlockType,x,token.ln,token.col,end_);}
-
-
-
-function checkBlockdecl(i){var l;if(i>=tokensLength)return 0;if(l=checkBlockdecl1(i))tokens[i].bd_type=1;else if(l=checkBlockdecl2(i))tokens[i].bd_type=2;else if(l=checkBlockdecl3(i))tokens[i].bd_type=3;else if(l=checkBlockdecl4(i))tokens[i].bd_type=4;else return 0;return l;}
-
-function getBlockdecl(){switch(tokens[pos].bd_type){case 1:return getBlockdecl1();case 2:return getBlockdecl2();case 3:return getBlockdecl3();case 4:return getBlockdecl4();}}
-
-
-function checkBlockdecl1(i){var start=i;var l=undefined;if(l=checkSC(i))i+=l;if(l=checkConditionalStatement(i))tokens[i].bd_kind=1;else if(l=checkInclude(i))tokens[i].bd_kind=2;else if(l=checkExtend(i))tokens[i].bd_kind=4;else if(l=checkLoop(i))tokens[i].bd_kind=3;else if(l=checkAtrule(i))tokens[i].bd_kind=6;else if(l=checkRuleset(i))tokens[i].bd_kind=7;else if(l=checkDeclaration(i))tokens[i].bd_kind=5;else return 0;i+=l;if(i<tokensLength&&(l=checkDeclDelim(i)))i+=l;else return 0;if(l=checkSC(i))i+=l;return i-start;}
-
-function getBlockdecl1(){var sc=getSC();var x=undefined;switch(tokens[pos].bd_kind){case 1:x=getConditionalStatement();break;case 2:x=getInclude();break;case 3:x=getLoop();break;case 4:x=getExtend();break;case 5:x=getDeclaration();break;case 6:x=getAtrule();break;case 7:x=getRuleset();break;}return sc.concat([x]).concat([getDeclDelim()]).concat(getSC());}
-
-
-function checkBlockdecl2(i){var start=i;var l=undefined;if(l=checkSC(i))i+=l;if(l=checkConditionalStatement(i))tokens[i].bd_kind=1;else if(l=checkInclude(i))tokens[i].bd_kind=2;else if(l=checkExtend(i))tokens[i].bd_kind=4;else if(l=checkMixin(i))tokens[i].bd_kind=8;else if(l=checkLoop(i))tokens[i].bd_kind=3;else if(l=checkAtrule(i))tokens[i].bd_kind=6;else if(l=checkRuleset(i))tokens[i].bd_kind=7;else if(l=checkDeclaration(i))tokens[i].bd_kind=5;else return 0;i+=l;if(l=checkSC(i))i+=l;return i-start;}
-
-function getBlockdecl2(){var sc=getSC();var x=undefined;switch(tokens[pos].bd_kind){case 1:x=getConditionalStatement();break;case 2:x=getInclude();break;case 3:x=getLoop();break;case 4:x=getExtend();break;case 5:x=getDeclaration();break;case 6:x=getAtrule();break;case 7:x=getRuleset();break;case 8:x=getMixin();break;}return sc.concat([x]).concat(getSC());}
-
-
-function checkBlockdecl3(i){var start=i;var l=undefined;if(l=checkSC(i))i+=l;if(l=checkDeclDelim(i))i+=l;else return 0;if(l=checkSC(i))i+=l;return i-start;}
-
-
-function getBlockdecl3(){return getSC().concat([getDeclDelim()]).concat(getSC());}
-
-
-function checkBlockdecl4(i){return checkSC(i);}
-
-function getBlockdecl4(){return getSC();}
-
-
-
-function checkBrackets(i){if(i>=tokensLength||tokens[i].type!==TokenType.LeftSquareBracket)return 0;return tokens[i].right-i+1;}
-
-
-function getBrackets(){var startPos=pos;var token=tokens[startPos];var line=token.ln;var column=token.col;pos++;var tsets=getTsets();var end=getLastPosition(tsets,line,column,1);pos++;return newNode(NodeType.BracketsType,tsets,token.ln,token.col,end);}
-
-
-
-function checkClass(i){var start=i;var l=undefined;if(i>=tokensLength)return 0;if(tokens[i].class_l)return tokens[i].class_l;if(tokens[i++].type!==TokenType.FullStop)return 0;if(l=checkIdentOrInterpolation(i))i+=l;else return 0;return i-start;}
-
-
-
-function getClass(){var startPos=pos;var x=[];pos++;x=x.concat(getIdentOrInterpolation());var token=tokens[startPos];return newNode(NodeType.ClassType,x,token.ln,token.col);}function checkCombinator(i){if(i>=tokensLength)return 0;var l=undefined;if(l=checkCombinator1(i))tokens[i].combinatorType=1;else if(l=checkCombinator2(i))tokens[i].combinatorType=2;else if(l=checkCombinator3(i))tokens[i].combinatorType=3;return l;}function getCombinator(){var type=tokens[pos].combinatorType;if(type===1)return getCombinator1();if(type===2)return getCombinator2();if(type===3)return getCombinator3();}
-
-function checkCombinator1(i){if(tokens[i].type===TokenType.VerticalLine&&tokens[i+1].type===TokenType.VerticalLine)return 2;else return 0;}function getCombinator1(){var type=NodeType.CombinatorType;var token=tokens[pos];var line=token.ln;var column=token.col;var content='||';pos+=2;return newNode(type,content,line,column);}
-
-
-
-function checkCombinator2(i){var type=tokens[i].type;if(type===TokenType.PlusSign||type===TokenType.GreaterThanSign||type===TokenType.Tilde)return 1;else return 0;}function getCombinator2(){var type=NodeType.CombinatorType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=tokens[pos++].value;return newNode(type,content,line,column);}
-
-function checkCombinator3(i){var start=i;if(tokens[i].type===TokenType.Solidus)i++;else return 0;var l=undefined;if(l=checkIdent(i))i+=l;else return 0;if(tokens[i].type===TokenType.Solidus)i++;else return 0;return i-start;}function getCombinator3(){var type=NodeType.CombinatorType;var token=tokens[pos];var line=token.ln;var column=token.col;
-pos++;var ident=getIdent();
-pos++;var content='/'+ident.content+'/';return newNode(type,content,line,column);}
-
-
-
-function checkCommentML(i){return i<tokensLength&&tokens[i].type===TokenType.CommentML?1:0;}
-
-
-
-function getCommentML(){var startPos=pos;var s=tokens[pos].value.substring(2);var l=s.length;var token=tokens[startPos];var line=token.ln;var column=token.col;if(s.charAt(l-2)==='*'&&s.charAt(l-1)==='/')s=s.substring(0,l-2);var end=getLastPosition(s,line,column,2);if(end[0]===line)end[1]+=2;pos++;return newNode(NodeType.CommentMLType,s,token.ln,token.col,end);}
-
-
-
-function checkCommentSL(i){return i<tokensLength&&tokens[i].type===TokenType.CommentSL?1:0;}
-
-
-
-function getCommentSL(){var startPos=pos;var x=undefined;var token=tokens[startPos];var line=token.ln;var column=token.col;x=tokens[pos++].value.substring(2);var end=getLastPosition(x,line,column+2);return newNode(NodeType.CommentSLType,x,token.ln,token.col,end);}
-
-
-
-
-function checkCondition(i){var start=i;var l=undefined;var _i=undefined;var s=undefined;if(i>=tokensLength)return 0;if(l=checkAtkeyword(i))i+=l;else return 0;if(['if','else'].indexOf(tokens[start+1].value)<0)return 0;while(i<tokensLength){if(l=checkBlock(i))break;s=checkSC(i);_i=i+s;if(l=_checkCondition(_i))i+=l+s;else break;}return i-start;}function _checkCondition(i){return checkVariable(i)||checkNumber(i)||checkInterpolation(i)||checkIdent(i)||checkOperator(i)||checkCombinator(i)||checkString(i);}
-
-
-function getCondition(){var startPos=pos;var x=[];var s;var _pos;x.push(getAtkeyword());while(pos<tokensLength){if(checkBlock(pos))break;s=checkSC(pos);_pos=pos+s;if(!_checkCondition(_pos))break;if(s)x=x.concat(getSC());x.push(_getCondition());}var token=tokens[startPos];return newNode(NodeType.ConditionType,x,token.ln,token.col);}function _getCondition(){if(checkVariable(pos))return getVariable();if(checkNumber(pos))return getNumber();if(checkInterpolation(pos))return getInterpolation();if(checkIdent(pos))return getIdent();if(checkOperator(pos))return getOperator();if(checkCombinator(pos))return getCombinator();if(checkString(pos))return getString();}
-
-
-
-
-function checkConditionalStatement(i){var start=i;var l=undefined;if(i>=tokensLength)return 0;if(l=checkCondition(i))i+=l;else return 0;if(l=checkSC(i))i+=l;if(l=checkBlock(i))i+=l;else return 0;return i-start;}
-
-
-function getConditionalStatement(){var startPos=pos;var x=[];x.push(getCondition());x=x.concat(getSC());x.push(getBlock());var token=tokens[startPos];return newNode(NodeType.ConditionalStatementType,x,token.ln,token.col);}
-
-
-
-function checkDeclaration(i){var start=i;var l=undefined;if(i>=tokensLength)return 0;if(l=checkProperty(i))i+=l;else return 0;if(l=checkSC(i))i+=l;if(l=checkPropertyDelim(i))i++;else return 0;if(l=checkSC(i))i+=l;if(l=checkValue(i))i+=l;else return 0;return i-start;}
-
-
-
-function getDeclaration(){var startPos=pos;var x=[];x.push(getProperty());x=x.concat(getSC());x.push(getPropertyDelim());x=x.concat(getSC());x.push(getValue());var token=tokens[startPos];return newNode(NodeType.DeclarationType,x,token.ln,token.col);}
-
-
-
-function checkDeclDelim(i){return i<tokensLength&&tokens[i].type===TokenType.Semicolon?1:0;}
-
-
-function getDeclDelim(){var startPos=pos++;var token=tokens[startPos];return newNode(NodeType.DeclDelimType,';',token.ln,token.col);}
-
-
-
-function checkDefault(i){var start=i;var l=undefined;if(i>=tokensLength||tokens[i++].type!==TokenType.ExclamationMark)return 0;if(l=checkSC(i))i+=l;if(tokens[i].value==='default'){tokens[start].defaultEnd=i;return i-start+1;}else{return 0;}}
-
-
-function getDefault(){var token=tokens[pos];var line=token.ln;var column=token.col;var content=joinValues(pos,token.defaultEnd);pos=token.defaultEnd+1;return newNode(NodeType.DefaultType,content,line,column);}
-
-
-
-function checkDelim(i){return i<tokensLength&&tokens[i].type===TokenType.Comma?1:0;}
-
-
-function getDelim(){var startPos=pos;pos++;var token=tokens[startPos];return newNode(NodeType.DelimType,',',token.ln,token.col);}
-
-
-
-function checkDimension(i){var ln=checkNumber(i);var li=undefined;if(i>=tokensLength||!ln||i+ln>=tokensLength)return 0;return(li=checkNmName2(i+ln))?ln+li:0;}
-
-
-
-
-function getDimension(){var startPos=pos;var x=[getNumber()];var token=tokens[pos];var ident=newNode(NodeType.IdentType,getNmName2(),token.ln,token.col);x.push(ident);token=tokens[startPos];return newNode(NodeType.DimensionType,x,token.ln,token.col);}
-
-
-function checkExpression(i){var start=i;if(i>=tokensLength||tokens[i++].value!=='expression'||i>=tokensLength||tokens[i].type!==TokenType.LeftParenthesis)return 0;return tokens[i].right-start+1;}
-
-function getExpression(){var startPos=pos;var e;var token=tokens[startPos];var line=token.ln;var column=token.col;pos++;e=joinValues(pos+1,tokens[pos].right-1);var end=getLastPosition(e,line,column,1);if(end[0]===line)end[1]+=11;pos=tokens[pos].right+1;return newNode(NodeType.ExpressionType,e,token.ln,token.col,end);}function checkExtend(i){var l=0;if(l=checkExtend1(i))tokens[i].extend_child=1;else if(l=checkExtend2(i))tokens[i].extend_child=2;return l;}function getExtend(){var type=tokens[pos].extend_child;if(type===1)return getExtend1();else if(type===2)return getExtend2();}
-
-
-function checkExtend1(i){var start=i;var l;if(i>=tokensLength)return 0;if(l=checkAtkeyword(i))i+=l;else return 0;if(tokens[start+1].value!=='extend')return 0;if(l=checkSC(i))i+=l;else return 0;if(l=checkSelectorsGroup(i))i+=l;else return 0;if(l=checkSC(i))i+=l;else return 0;if(l=checkOptional(i))i+=l;else return 0;return i-start;}function getExtend1(){var startPos=pos;var x=[].concat([getAtkeyword()],getSC(),getSelectorsGroup(),getSC(),[getOptional()]);var token=tokens[startPos];return newNode(NodeType.ExtendType,x,token.ln,token.col);}
-
-
-function checkExtend2(i){var start=i;var l;if(i>=tokensLength)return 0;if(l=checkAtkeyword(i))i+=l;else return 0;if(tokens[start+1].value!=='extend')return 0;if(l=checkSC(i))i+=l;else return 0;if(l=checkSelectorsGroup(i))i+=l;else return 0;return i-start;}function getExtend2(){var startPos=pos;var x=[].concat([getAtkeyword()],getSC(),getSelectorsGroup());var token=tokens[startPos];return newNode(NodeType.ExtendType,x,token.ln,token.col);}
-
-
-function checkFunction(i){var start=i;var l=undefined;if(i>=tokensLength)return 0;if(l=checkIdentOrInterpolation(i))i+=l;else return 0;return i<tokensLength&&tokens[i].type===TokenType.LeftParenthesis?tokens[i].right-start+1:0;}
-
-function getFunction(){var startPos=pos;var x=getIdentOrInterpolation();var body=undefined;body=getArguments();x.push(body);var token=tokens[startPos];return newNode(NodeType.FunctionType,x,token.ln,token.col);}
-
-function getArguments(){var startPos=pos;var x=[];var body=undefined;var token=tokens[startPos];var line=token.ln;var column=token.col;pos++;while(pos<tokensLength&&tokens[pos].type!==TokenType.RightParenthesis){if(checkDeclaration(pos))x.push(getDeclaration());else if(checkArgument(pos)){body=getArgument();if(typeof body.content==='string')x.push(body);else x=x.concat(body);}else if(checkClass(pos))x.push(getClass());else throwError();}var end=getLastPosition(x,line,column,1);pos++;return newNode(NodeType.ArgumentsType,x,token.ln,token.col,end);}
-
-
-
-function checkIdent(i){var start=i;var interpolations=[];var wasIdent=undefined;var wasInt=false;var l=undefined;if(i>=tokensLength)return 0;
-if(tokens[i].type===TokenType.LowLine)return checkIdentLowLine(i);if(tokens[i].type===TokenType.HyphenMinus&&tokens[i+1].type===TokenType.DecimalNumber)return 0;
-if(l=_checkIdent(i))i+=l;else return 0;
-wasIdent=tokens[i-1].type===TokenType.Identifier;while(i<tokensLength){l=_checkIdent(i);if(!l)break;wasIdent=true;i+=l;}if(!wasIdent&&!wasInt&&tokens[start].type!==TokenType.Asterisk)return 0;tokens[start].ident_last=i-1;if(interpolations.length)tokens[start].interpolations=interpolations;return i-start;}function _checkIdent(i){if(tokens[i].type===TokenType.HyphenMinus||tokens[i].type===TokenType.Identifier||tokens[i].type===TokenType.DollarSign||tokens[i].type===TokenType.LowLine||tokens[i].type===TokenType.DecimalNumber||tokens[i].type===TokenType.Asterisk)return 1;return 0;}
-
-
-
-function checkIdentLowLine(i){var start=i;if(i++>=tokensLength)return 0;for(;i<tokensLength;i++){if(tokens[i].type!==TokenType.HyphenMinus&&tokens[i].type!==TokenType.DecimalNumber&&tokens[i].type!==TokenType.LowLine&&tokens[i].type!==TokenType.Identifier)break;}
-tokens[start].ident_last=i-1;return i-start;}
-
-
-function getIdent(){var startPos=pos;var x=joinValues(pos,tokens[pos].ident_last);pos=tokens[pos].ident_last+1;var token=tokens[startPos];return newNode(NodeType.IdentType,x,token.ln,token.col);}function checkIdentOrInterpolation(i){var start=i;var l=undefined;while(i<tokensLength){if(l=checkInterpolation(i)||checkIdent(i))i+=l;else break;}return i-start;}function getIdentOrInterpolation(){var x=[];while(pos<tokensLength){if(checkInterpolation(pos))x.push(getInterpolation());else if(checkIdent(pos))x.push(getIdent());else break;}return x;}
-
-
-
-function checkImportant(i){var start=i;var l=undefined;if(i>=tokensLength||tokens[i++].type!==TokenType.ExclamationMark)return 0;if(l=checkSC(i))i+=l;if(tokens[i].value==='important'){tokens[start].importantEnd=i;return i-start+1;}else{return 0;}}
-
-
-function getImportant(){var token=tokens[pos];var line=token.ln;var column=token.col;var content=joinValues(pos,token.importantEnd);pos=token.importantEnd+1;return newNode(NodeType.ImportantType,content,line,column);}
-
-
-
-
-function checkInclude(i){var l;if(i>=tokensLength)return 0;if(l=checkInclude1(i))tokens[i].include_type=1;else if(l=checkInclude2(i))tokens[i].include_type=2;else if(l=checkInclude3(i))tokens[i].include_type=3;else if(l=checkInclude4(i))tokens[i].include_type=4;else if(l=checkInclude5(i))tokens[i].include_type=5;return l;}
-
-
-
-function checkGlobal(i){var start=i;var l=undefined;if(i>=tokensLength||tokens[i++].type!==TokenType.ExclamationMark)return 0;if(l=checkSC(i))i+=l;if(tokens[i].value==='global'){tokens[start].globalEnd=i;return i-start+1;}else{return 0;}}
-
-function getGlobal(){var token=tokens[pos];var line=token.ln;var column=token.col;var content=joinValues(pos,token.globalEnd);pos=token.globalEnd+1;return newNode(NodeType.GlobalType,content,line,column);}
-
-
-function getInclude(){switch(tokens[pos].include_type){case 1:return getInclude1();case 2:return getInclude2();case 3:return getInclude3();case 4:return getInclude4();case 5:return getInclude5();}}
-
-
-
-
-function checkInclude1(i){var start=i;var l=undefined;if(l=checkAtkeyword(i))i+=l;else return 0;if(tokens[start+1].value!=='include')return 0;if(l=checkSC(i))i+=l;else return 0;if(l=checkIdentOrInterpolation(i))i+=l;else return 0;if(l=checkSC(i))i+=l;if(l=checkArguments(i))i+=l;else return 0;if(l=checkSC(i))i+=l;if(l=checkKeyframesBlocks(i))i+=l;else return 0;return i-start;}
-
-
-
-
-
-
-
-
-function getInclude1(){var startPos=pos;var x=[].concat(getAtkeyword(),getSC(),getIdentOrInterpolation(),getSC(),getArguments(),getSC(),getKeyframesBlocks());var token=tokens[startPos];return newNode(NodeType.IncludeType,x,token.ln,token.col);}
-
-
-
-function checkInclude2(i){var start=i;var l=undefined;if(l=checkAtkeyword(i))i+=l;else return 0;if(tokens[start+1].value!=='include')return 0;if(l=checkSC(i))i+=l;else return 0;if(l=checkIdentOrInterpolation(i))i+=l;else return 0;if(l=checkSC(i))i+=l;if(l=checkArguments(i))i+=l;else return 0;if(l=checkSC(i))i+=l;if(l=checkBlock(i))i+=l;else return 0;return i-start;}
-
-
-
-
-
-
-function getInclude2(){var startPos=pos;var x=[].concat(getAtkeyword(),getSC(),getIdentOrInterpolation(),getSC(),getArguments(),getSC(),getBlock());var token=tokens[startPos];return newNode(NodeType.IncludeType,x,token.ln,token.col);}
-
-
-
-function checkInclude3(i){var start=i;var l=undefined;if(l=checkAtkeyword(i))i+=l;else return 0;if(tokens[start+1].value!=='include')return 0;if(l=checkSC(i))i+=l;else return 0;if(l=checkIdentOrInterpolation(i))i+=l;else return 0;if(l=checkSC(i))i+=l;if(l=checkArguments(i))i+=l;else return 0;return i-start;}
-
-
-
-
-
-function getInclude3(){var startPos=pos;var x=[].concat(getAtkeyword(),getSC(),getIdentOrInterpolation(),getSC(),getArguments());var token=tokens[startPos];return newNode(NodeType.IncludeType,x,token.ln,token.col);}
-
-
-
-
-function checkInclude4(i){var start=i;var l=undefined;if(l=checkAtkeyword(i))i+=l;else return 0;if(tokens[start+1].value!=='include')return 0;if(l=checkSC(i))i+=l;else return 0;if(l=checkIdentOrInterpolation(i))i+=l;else return 0;if(l=checkSC(i))i+=l;if(l=checkBlock(i))i+=l;else return 0;return i-start;}
-
-
-
-function getInclude4(){var startPos=pos;var x=[].concat(getAtkeyword(),getSC(),getIdentOrInterpolation(),getSC(),getBlock());var token=tokens[startPos];return newNode(NodeType.IncludeType,x,token.ln,token.col);}
-
-
-function checkInclude5(i){var start=i;var l=undefined;if(l=checkAtkeyword(i))i+=l;else return 0;if(tokens[start+1].value!=='include')return 0;if(l=checkSC(i))i+=l;else return 0;if(l=checkIdentOrInterpolation(i))i+=l;else return 0;return i-start;}
-
-function getInclude5(){var startPos=pos;var x=[].concat(getAtkeyword(),getSC(),getIdentOrInterpolation());var token=tokens[startPos];return newNode(NodeType.IncludeType,x,token.ln,token.col);}
-
-
-
-function checkInterpolation(i){var start=i;var l=undefined;if(i>=tokensLength)return 0;if(tokens[i].type!==TokenType.NumberSign||!tokens[i+1]||tokens[i+1].type!==TokenType.LeftCurlyBracket)return 0;i+=2;while(tokens[i].type!==TokenType.RightCurlyBracket){if(l=checkArgument(i))i+=l;else return 0;}return tokens[i].type===TokenType.RightCurlyBracket?i-start+1:0;}
-
-
-function getInterpolation(){var startPos=pos;var x=[];var token=tokens[startPos];var line=token.ln;var column=token.col;
-pos+=2;while(pos<tokensLength&&tokens[pos].type!==TokenType.RightCurlyBracket){var body=getArgument();if(typeof body.content==='string')x.push(body);else x=x.concat(body);}var end=getLastPosition(x,line,column,1);
-pos++;return newNode(NodeType.InterpolationType,x,token.ln,token.col,end);}function checkKeyframesBlock(i){var start=i;var l=undefined;if(i>=tokensLength)return 0;if(l=checkKeyframesSelector(i))i+=l;else return 0;if(l=checkSC(i))i+=l;if(l=checkBlock(i))i+=l;else return 0;return i-start;}function getKeyframesBlock(){var type=NodeType.RulesetType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[].concat([getKeyframesSelector()],getSC(),[getBlock()]);return newNode(type,content,line,column);}function checkKeyframesBlocks(i){var start=i;var l=undefined;if(i<tokensLength&&tokens[i].type===TokenType.LeftCurlyBracket)i++;else return 0;if(l=checkSC(i))i+=l;if(l=checkKeyframesBlock(i))i+=l;else return 0;while(tokens[i].type!==TokenType.RightCurlyBracket){if(l=checkSC(i))i+=l;else if(l=checkKeyframesBlock(i))i+=l;else break;}if(i<tokensLength&&tokens[i].type===TokenType.RightCurlyBracket)i++;else return 0;return i-start;}function getKeyframesBlocks(){var type=NodeType.BlockType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];var keyframesBlocksEnd=token.right;
-pos++;while(pos<keyframesBlocksEnd){if(checkSC(pos))content=content.concat(getSC());else if(checkKeyframesBlock(pos))content.push(getKeyframesBlock());}var end=getLastPosition(content,line,column,1);
-pos++;return newNode(type,content,line,column,end);}
-
-
-
-function checkKeyframesRule(i){var start=i;var l=undefined;if(i>=tokensLength)return 0;if(l=checkAtkeyword(i))i+=l;else return 0;var atruleName=joinValues2(i-l,l);if(atruleName.indexOf('keyframes')===-1)return 0;if(l=checkSC(i))i+=l;else return 0;if(l=checkIdentOrInterpolation(i))i+=l;else return 0;if(l=checkSC(i))i+=l;if(l=checkKeyframesBlocks(i))i+=l;else return 0;return i-start;}
-
-function getKeyframesRule(){var type=NodeType.AtruleType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[].concat([getAtkeyword()],getSC(),getIdentOrInterpolation(),getSC(),[getKeyframesBlocks()]);return newNode(type,content,line,column);}function checkKeyframesSelector(i){var start=i;var l=undefined;if(i>=tokensLength)return 0;if(l=checkIdent(i)){
-var selector=joinValues2(i,l);if(selector!=='from'&&selector!=='to')return 0;i+=l;tokens[start].keyframesSelectorType=1;}else if(l=checkPercentage(i)){i+=l;tokens[start].keyframesSelectorType=2;}else if(l=checkInterpolation(i)){i+=l;tokens[start].keyframesSelectorType=3;}else{return 0;}return i-start;}function getKeyframesSelector(){var keyframesSelectorType=NodeType.KeyframesSelectorType;var selectorType=NodeType.SelectorType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];if(token.keyframesSelectorType===1){content.push(getIdent());}else if(token.keyframesSelectorType===2){content.push(getPercentage());}else{content.push(getInterpolation());}var keyframesSelector=newNode(keyframesSelectorType,content,line,column);return newNode(selectorType,[keyframesSelector],line,column);}
-
-
-
-function checkLoop(i){var start=i;var l=undefined;if(i>=tokensLength)return 0;if(l=checkAtkeyword(i))i+=l;else return 0;if(['for','each','while'].indexOf(tokens[start+1].value)<0)return 0;while(i<tokensLength){if(l=checkBlock(i)){i+=l;break;}else if(l=checkVariable(i)||checkNumber(i)||checkInterpolation(i)||checkIdent(i)||checkSC(i)||checkOperator(i)||checkCombinator(i)||checkString(i))i+=l;else return 0;}return i-start;}
-
-
-function getLoop(){var startPos=pos;var x=[];x.push(getAtkeyword());while(pos<tokensLength){if(checkBlock(pos)){x.push(getBlock());break;}else if(checkVariable(pos))x.push(getVariable());else if(checkNumber(pos))x.push(getNumber());else if(checkInterpolation(pos))x.push(getInterpolation());else if(checkIdent(pos))x.push(getIdent());else if(checkOperator(pos))x.push(getOperator());else if(checkCombinator(pos))x.push(getCombinator());else if(checkSC(pos))x=x.concat(getSC());else if(checkString(pos))x.push(getString());}var token=tokens[startPos];return newNode(NodeType.LoopType,x,token.ln,token.col);}
-
-
-
-function checkMixin(i){var start=i;var l=undefined;if(i>=tokensLength)return 0;if((l=checkAtkeyword(i))&&tokens[i+1].value==='mixin')i+=l;else return 0;if(l=checkSC(i))i+=l;if(l=checkIdentOrInterpolation(i))i+=l;else return 0;if(l=checkSC(i))i+=l;if(l=checkArguments(i))i+=l;if(l=checkSC(i))i+=l;if(l=checkBlock(i))i+=l;else return 0;return i-start;}
-
-
-function getMixin(){var startPos=pos;var x=[getAtkeyword()];x=x.concat(getSC());if(checkIdentOrInterpolation(pos))x=x.concat(getIdentOrInterpolation());x=x.concat(getSC());if(checkArguments(pos))x.push(getArguments());x=x.concat(getSC());if(checkBlock(pos))x.push(getBlock());var token=tokens[startPos];return newNode(NodeType.MixinType,x,token.ln,token.col);}
-
-
-
-function checkNamespace(i){return i<tokensLength&&tokens[i].type===TokenType.VerticalLine?1:0;}
-
-
-function getNamespace(){var startPos=pos;pos++;var token=tokens[startPos];return newNode(NodeType.NamespaceType,'|',token.ln,token.col);}
-
-
-function checkNmName2(i){if(tokens[i].type===TokenType.Identifier)return 1;else if(tokens[i].type!==TokenType.DecimalNumber)return 0;i++;return i<tokensLength&&tokens[i].type===TokenType.Identifier?2:1;}
-
-function getNmName2(){var s=tokens[pos].value;if(tokens[pos++].type===TokenType.DecimalNumber&&pos<tokensLength&&tokens[pos].type===TokenType.Identifier)s+=tokens[pos++].value;return s;}
-
-
-
-function checkNumber(i){if(i>=tokensLength)return 0;if(tokens[i].number_l)return tokens[i].number_l;
-if(i<tokensLength&&tokens[i].type===TokenType.DecimalNumber&&(!tokens[i+1]||tokens[i+1]&&tokens[i+1].type!==TokenType.FullStop))return tokens[i].number_l=1,tokens[i].number_l;
-if(i<tokensLength&&tokens[i].type===TokenType.DecimalNumber&&tokens[i+1]&&tokens[i+1].type===TokenType.FullStop&&(!tokens[i+2]||tokens[i+2].type!==TokenType.DecimalNumber))return tokens[i].number_l=2,tokens[i].number_l;
-if(i<tokensLength&&tokens[i].type===TokenType.FullStop&&tokens[i+1].type===TokenType.DecimalNumber)return tokens[i].number_l=2,tokens[i].number_l;
-if(i<tokensLength&&tokens[i].type===TokenType.DecimalNumber&&tokens[i+1]&&tokens[i+1].type===TokenType.FullStop&&tokens[i+2]&&tokens[i+2].type===TokenType.DecimalNumber)return tokens[i].number_l=3,tokens[i].number_l;return 0;}
-
-
-
-function getNumber(){var s='';var startPos=pos;var l=tokens[pos].number_l;for(var j=0;j<l;j++){s+=tokens[pos+j].value;}pos+=l;var token=tokens[startPos];return newNode(NodeType.NumberType,s,token.ln,token.col);}
-
-
-
-function checkOperator(i){if(i>=tokensLength)return 0;switch(tokens[i].type){case TokenType.Solidus:case TokenType.PercentSign:case TokenType.Comma:case TokenType.Colon:case TokenType.EqualsSign:case TokenType.EqualitySign:case TokenType.InequalitySign:case TokenType.LessThanSign:case TokenType.GreaterThanSign:case TokenType.Asterisk:return 1;}return 0;}
-
-
-
-function getOperator(){var startPos=pos;var x=tokens[pos++].value;var token=tokens[startPos];return newNode(NodeType.OperatorType,x,token.ln,token.col);}
-
-
-
-function checkOptional(i){var start=i;var l=undefined;if(i>=tokensLength||tokens[i++].type!==TokenType.ExclamationMark)return 0;if(l=checkSC(i))i+=l;if(tokens[i].value==='optional'){tokens[start].optionalEnd=i;return i-start+1;}else{return 0;}}
-
-function getOptional(){var token=tokens[pos];var line=token.ln;var column=token.col;var content=joinValues(pos,token.optionalEnd);pos=token.optionalEnd+1;return newNode(NodeType.OptionalType,content,line,column);}
-
-
-
-function checkParentheses(i){if(i>=tokensLength||tokens[i].type!==TokenType.LeftParenthesis)return 0;return tokens[i].right-i+1;}
-
-
-function getParentheses(){var type=NodeType.ParenthesesType;var token=tokens[pos];var line=token.ln;var column=token.col;pos++;var tsets=getTsets();var end=getLastPosition(tsets,line,column,1);pos++;return newNode(type,tsets,line,column,end);}
-
-
-
-function checkParentSelector(i){return i<tokensLength&&tokens[i].type===TokenType.Ampersand?1:0;}
-
-function getParentSelector(){var startPos=pos;pos++;var token=tokens[startPos];return newNode(NodeType.ParentSelectorType,'&',token.ln,token.col);}function checkParentSelectorExtension(i){if(i>=tokensLength)return 0;var start=i;var l=undefined;while(i<tokensLength){if(l=checkNumber(i)||checkIdentOrInterpolation(i))i+=l;else break;}return i-start;}function getParentSelectorExtension(){var type=NodeType.ParentSelectorExtensionType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];while(pos<tokensLength){if(checkNumber(pos))content.push(getNumber());else if(checkIdentOrInterpolation(pos))content=content.concat(getIdentOrInterpolation());else break;}return newNode(type,content,line,column);}function checkParentSelectorWithExtension(i){if(i>=tokensLength)return 0;var start=i;var l=undefined;if(l=checkParentSelector(i))i+=l;else return 0;if(l=checkParentSelectorExtension(i))i+=l;return i-start;}function getParentSelectorWithExtension(){var content=[getParentSelector()];if(checkParentSelectorExtension(pos))content.push(getParentSelectorExtension());return content;}
-
-
-
-function checkPercentage(i){var x;if(i>=tokensLength)return 0;x=checkNumber(i);if(!x||i+x>=tokensLength)return 0;return tokens[i+x].type===TokenType.PercentSign?x+1:0;}
-
-
-
-function getPercentage(){var startPos=pos;var x=[getNumber()];var token=tokens[startPos];var line=token.ln;var column=token.col;var end=getLastPosition(x,line,column,1);pos++;return newNode(NodeType.PercentageType,x,token.ln,token.col,end);}
-
-
-
-function checkPlaceholder(i){var l;if(i>=tokensLength)return 0;if(tokens[i].placeholder_l)return tokens[i].placeholder_l;if(tokens[i].type===TokenType.PercentSign&&(l=checkIdentOrInterpolation(i+1))){tokens[i].placeholder_l=l+1;return l+1;}else return 0;}
-
-
-
-function getPlaceholder(){var startPos=pos;pos++;var x=getIdentOrInterpolation();var token=tokens[startPos];return newNode(NodeType.PlaceholderType,x,token.ln,token.col);}
-
-
-function checkProgid(i){var start=i;var l=undefined;if(i>=tokensLength)return 0;if(joinValues2(i,6)==='progid:DXImageTransform.Microsoft.')i+=6;else return 0;if(l=checkIdentOrInterpolation(i))i+=l;else return 0;if(l=checkSC(i))i+=l;if(tokens[i].type===TokenType.LeftParenthesis){tokens[start].progid_end=tokens[i].right;i=tokens[i].right+1;}else return 0;return i-start;}
-
-function getProgid(){var startPos=pos;var progid_end=tokens[pos].progid_end;var x=joinValues(pos,progid_end);pos=progid_end+1;var token=tokens[startPos];return newNode(NodeType.ProgidType,x,token.ln,token.col);}
-
-
-
-function checkProperty(i){var start=i;var l=undefined;if(i>=tokensLength)return 0;if(l=checkVariable(i)||checkIdentOrInterpolation(i))i+=l;else return 0;return i-start;}
-
-
-function getProperty(){var startPos=pos;var x=[];if(checkVariable(pos)){x.push(getVariable());}else{x=x.concat(getIdentOrInterpolation());}var token=tokens[startPos];return newNode(NodeType.PropertyType,x,token.ln,token.col);}
-
-
-
-function checkPropertyDelim(i){return i<tokensLength&&tokens[i].type===TokenType.Colon?1:0;}
-
-
-function getPropertyDelim(){var startPos=pos;pos++;var token=tokens[startPos];return newNode(NodeType.PropertyDelimType,':',token.ln,token.col);}
-
-
-function checkPseudo(i){return checkPseudoe(i)||checkPseudoc(i);}
-
-function getPseudo(){if(checkPseudoe(pos))return getPseudoe();if(checkPseudoc(pos))return getPseudoc();}
-
-
-function checkPseudoe(i){var l;if(i>=tokensLength||tokens[i++].type!==TokenType.Colon||i>=tokensLength||tokens[i++].type!==TokenType.Colon)return 0;return(l=checkIdentOrInterpolation(i))?l+2:0;}
-
-function getPseudoe(){var startPos=pos;pos+=2;var x=getIdentOrInterpolation();var token=tokens[startPos];return newNode(NodeType.PseudoeType,x,token.ln,token.col);}
-
-
-function checkPseudoc(i){var l;if(i>=tokensLength||tokens[i].type!==TokenType.Colon)return 0;if(l=checkPseudoClass3(i))tokens[i].pseudoClassType=3;else if(l=checkPseudoClass4(i))tokens[i].pseudoClassType=4;else if(l=checkPseudoClass5(i))tokens[i].pseudoClassType=5;else if(l=checkPseudoClass1(i))tokens[i].pseudoClassType=1;else if(l=checkPseudoClass2(i))tokens[i].pseudoClassType=2;else if(l=checkPseudoClass6(i))tokens[i].pseudoClassType=6;else return 0;return l;}
-
-function getPseudoc(){var childType=tokens[pos].pseudoClassType;if(childType===1)return getPseudoClass1();if(childType===2)return getPseudoClass2();if(childType===3)return getPseudoClass3();if(childType===4)return getPseudoClass4();if(childType===5)return getPseudoClass5();if(childType===6)return getPseudoClass6();}
-
-function checkPseudoClass1(i){var start=i;
-i++;if(i>=tokensLength)return 0;var l=undefined;if(l=checkIdentOrInterpolation(i))i+=l;else return 0;if(i>=tokensLength||tokens[i].type!==TokenType.LeftParenthesis)return 0;var right=tokens[i].right;
-i++;if(l=checkSelectorsGroup(i))i+=l;else return 0;if(i!==right)return 0;return right-start+1;}
-
-function getPseudoClass1(){var type=NodeType.PseudocType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];
-pos++;content=content.concat(getIdentOrInterpolation());{var _type=NodeType.ArgumentsType;var _token=tokens[pos];var _line=_token.ln;var _column=_token.col;
-pos++;var selectors=getSelectorsGroup();var end=getLastPosition(selectors,_line,_column,1);var args=newNode(_type,selectors,_line,_column,end);content.push(args);
-pos++;}return newNode(type,content,line,column);}
-
-
-
-function checkPseudoClass2(i){var start=i;var l=0;
-i++;if(i>=tokensLength)return 0;if(l=checkIdentOrInterpolation(i))i+=l;else return 0;if(i>=tokensLength||tokens[i].type!==TokenType.LeftParenthesis)return 0;var right=tokens[i].right;
-i++;if(l=checkSC(i))i+=l;if(l=checkIdentOrInterpolation(i))i+=l;else return 0;if(l=checkSC(i))i+=l;if(i!==right)return 0;return i-start+1;}function getPseudoClass2(){var type=NodeType.PseudocType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];
-pos++;content=content.concat(getIdentOrInterpolation());var l=tokens[pos].ln;var c=tokens[pos].col;var value=[];
-pos++;value=value.concat(getSC()).concat(getIdentOrInterpolation()).concat(getSC());var end=getLastPosition(value,l,c,1);var args=newNode(NodeType.ArgumentsType,value,l,c,end);content.push(args);
-pos++;return newNode(type,content,line,column);}
-
-function checkPseudoClass3(i){var start=i;var l=0;
-i++;if(i>=tokensLength)return 0;if(l=checkIdentOrInterpolation(i))i+=l;else return 0;if(i>=tokensLength||tokens[i].type!==TokenType.LeftParenthesis)return 0;var right=tokens[i].right;
-i++;if(l=checkSC(i))i+=l;if(l=checkUnary(i))i+=l;if(i>=tokensLength)return 0;if(tokens[i].type===TokenType.DecimalNumber)i++;if(i>=tokensLength)return 0;if(tokens[i].value==='n')i++;else return 0;if(l=checkSC(i))i+=l;if(i>=tokensLength)return 0;if(tokens[i].value==='+'||tokens[i].value==='-')i++;else return 0;if(l=checkSC(i))i+=l;if(tokens[i].type===TokenType.DecimalNumber)i++;else return 0;if(l=checkSC(i))i+=l;if(i!==right)return 0;return i-start+1;}function getPseudoClass3(){var type=NodeType.PseudocType;var token=tokens[pos];var line=token.ln;var column=token.col;
-pos++;var content=getIdentOrInterpolation();var l=tokens[pos].ln;var c=tokens[pos].col;var value=[];
-pos++;if(checkUnary(pos))value.push(getUnary());if(checkNumber(pos))value.push(getNumber());{var _l=tokens[pos].ln;var _c=tokens[pos].col;var _content=tokens[pos].value;var ident=newNode(NodeType.IdentType,_content,_l,_c);value.push(ident);pos++;}value=value.concat(getSC());if(checkUnary(pos))value.push(getUnary());value=value.concat(getSC());if(checkNumber(pos))value.push(getNumber());value=value.concat(getSC());var end=getLastPosition(value,l,c,1);var args=newNode(NodeType.ArgumentsType,value,l,c,end);content.push(args);
-pos++;return newNode(type,content,line,column);}
-
-function checkPseudoClass4(i){var start=i;var l=0;
-i++;if(i>=tokensLength)return 0;if(l=checkIdentOrInterpolation(i))i+=l;else return 0;if(i>=tokensLength)return 0;if(tokens[i].type!==TokenType.LeftParenthesis)return 0;var right=tokens[i].right;
-i++;if(l=checkSC(i))i+=l;if(l=checkUnary(i))i+=l;if(tokens[i].type===TokenType.DecimalNumber)i++;if(tokens[i].value==='n')i++;else return 0;if(l=checkSC(i))i+=l;if(i!==right)return 0;return i-start+1;}function getPseudoClass4(){var type=NodeType.PseudocType;var token=tokens[pos];var line=token.ln;var column=token.col;
-pos++;var content=getIdentOrInterpolation();var l=tokens[pos].ln;var c=tokens[pos].col;var value=[];
-pos++;value=value.concat(getSC());if(checkUnary(pos))value.push(getUnary());if(checkNumber(pos))value.push(getNumber());if(checkIdent(pos))value.push(getIdent());value=value.concat(getSC());var end=getLastPosition(value,l,c,1);var args=newNode(NodeType.ArgumentsType,value,l,c,end);content.push(args);
-pos++;return newNode(type,content,line,column);}
-
-function checkPseudoClass5(i){var start=i;var l=0;
-i++;if(i>=tokensLength)return 0;if(l=checkIdentOrInterpolation(i))i+=l;else return 0;if(i>=tokensLength)return 0;if(tokens[i].type!==TokenType.LeftParenthesis)return 0;var right=tokens[i].right;
-i++;if(l=checkSC(i))i+=l;if(l=checkUnary(i))i+=l;if(tokens[i].type===TokenType.DecimalNumber)i++;else return 0;if(l=checkSC(i))i+=l;if(i!==right)return 0;return i-start+1;}function getPseudoClass5(){var type=NodeType.PseudocType;var token=tokens[pos];var line=token.ln;var column=token.col;
-pos++;var content=getIdentOrInterpolation();var l=tokens[pos].ln;var c=tokens[pos].col;var value=[];
-pos++;if(checkUnary(pos))value.push(getUnary());if(checkNumber(pos))value.push(getNumber());value=value.concat(getSC());var end=getLastPosition(value,l,c,1);var args=newNode(NodeType.ArgumentsType,value,l,c,end);content.push(args);
-pos++;return newNode(type,content,line,column);}
-
-function checkPseudoClass6(i){var start=i;var l=0;
-i++;if(i>=tokensLength)return 0;if(l=checkIdentOrInterpolation(i))i+=l;else return 0;return i-start;}function getPseudoClass6(){var type=NodeType.PseudocType;var token=tokens[pos];var line=token.ln;var column=token.col;
-pos++;var content=getIdentOrInterpolation();return newNode(type,content,line,column);}
-
-
-function checkRuleset(i){var start=i;var l=undefined;if(i>=tokensLength)return 0;if(l=checkSelectorsGroup(i))i+=l;else return 0;if(l=checkSC(i))i+=l;if(l=checkBlock(i))i+=l;else return 0;return i-start;}function getRuleset(){var type=NodeType.RulesetType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];content=content.concat(getSelectorsGroup());content=content.concat(getSC());content.push(getBlock());return newNode(type,content,line,column);}
-
-
-
-
-function checkS(i){return i<tokensLength&&tokens[i].ws?tokens[i].ws_last-i+1:0;}
-
-
-function getS(){var startPos=pos;var x=joinValues(pos,tokens[pos].ws_last);pos=tokens[pos].ws_last+1;var token=tokens[startPos];return newNode(NodeType.SType,x,token.ln,token.col);}
-
-
-
-
-function checkSC(i){if(i>=tokensLength)return 0;var l=undefined;var lsc=0;while(i<tokensLength){if(!(l=checkS(i))&&!(l=checkCommentML(i))&&!(l=checkCommentSL(i)))break;i+=l;lsc+=l;}return lsc||0;}
-
-
-
-
-
-function getSC(){var sc=[];if(pos>=tokensLength)return sc;while(pos<tokensLength){if(checkS(pos))sc.push(getS());else if(checkCommentML(pos))sc.push(getCommentML());else if(checkCommentSL(pos))sc.push(getCommentSL());else break;}return sc;}
-
-
-
-
-function checkShash(i){var l;if(i>=tokensLength||tokens[i].type!==TokenType.NumberSign)return 0;return(l=checkIdentOrInterpolation(i+1))?l+1:0;}
-
-
-
-
-function getShash(){var startPos=pos;var token=tokens[startPos];pos++;var x=getIdentOrInterpolation();return newNode(NodeType.ShashType,x,token.ln,token.col);}
-
-
-
-function checkString(i){return i<tokensLength&&(tokens[i].type===TokenType.StringSQ||tokens[i].type===TokenType.StringDQ)?1:0;}
-
-
-
-function getString(){var startPos=pos;var x=tokens[pos++].value;var token=tokens[startPos];return newNode(NodeType.StringType,x,token.ln,token.col);}
-
-
-
-
-
-function checkStylesheet(i){var start=i;var l=undefined;while(i<tokensLength){if(l=checkSC(i)||checkDeclaration(i)||checkDeclDelim(i)||checkInclude(i)||checkExtend(i)||checkMixin(i)||checkLoop(i)||checkConditionalStatement(i)||checkAtrule(i)||checkRuleset(i))i+=l;else throwError(i);}return i-start;}
-
-
-function getStylesheet(){var startPos=pos;var x=[];while(pos<tokensLength){if(checkSC(pos))x=x.concat(getSC());else if(checkRuleset(pos))x.push(getRuleset());else if(checkInclude(pos))x.push(getInclude());else if(checkExtend(pos))x.push(getExtend());else if(checkMixin(pos))x.push(getMixin());else if(checkLoop(pos))x.push(getLoop());else if(checkConditionalStatement(pos))x.push(getConditionalStatement());else if(checkAtrule(pos))x.push(getAtrule());else if(checkDeclaration(pos))x.push(getDeclaration());else if(checkDeclDelim(pos))x.push(getDeclDelim());else throwError();}var token=tokens[startPos];return newNode(NodeType.StylesheetType,x,token.ln,token.col);}
-
-
-function checkTset(i){return checkVhash(i)||checkOperator(i)||checkAny(i)||checkSC(i)||checkInterpolation(i);}
-
-function getTset(){if(checkVhash(pos))return getVhash();else if(checkOperator(pos))return getOperator();else if(checkAny(pos))return getAny();else if(checkSC(pos))return getSC();else if(checkInterpolation(pos))return getInterpolation();}
-
-
-function checkTsets(i){var start=i;var l=undefined;if(i>=tokensLength)return 0;while(l=checkTset(i)){i+=l;}return i-start;}
-
-function getTsets(){var x=[];var t=undefined;while(t=getTset()){if(typeof t.content==='string')x.push(t);else x=x.concat(t);}return x;}
-
-
-
-function checkUnary(i){return i<tokensLength&&(tokens[i].type===TokenType.HyphenMinus||tokens[i].type===TokenType.PlusSign)?1:0;}
-
-
-
-function getUnary(){var startPos=pos;var x=tokens[pos++].value;var token=tokens[startPos];return newNode(NodeType.OperatorType,x,token.ln,token.col);}
-
-
-
-function checkUri(i){var start=i;if(i>=tokensLength||tokens[i++].value!=='url'||i>=tokensLength||tokens[i].type!==TokenType.LeftParenthesis)return 0;return tokens[i].right-start+1;}
-
-
-
-function getUri(){var startPos=pos;var uriExcluding={};var uri=undefined;var token=undefined;var l=undefined;var raw=undefined;pos+=2;uriExcluding[TokenType.Space]=1;uriExcluding[TokenType.Tab]=1;uriExcluding[TokenType.Newline]=1;uriExcluding[TokenType.LeftParenthesis]=1;uriExcluding[TokenType.RightParenthesis]=1;if(checkUriContent(pos)){uri=[].concat(getSC()).concat(getUriContent()).concat(getSC());}else{uri=[].concat(getSC());l=checkExcluding(uriExcluding,pos);token=tokens[pos];raw=newNode(NodeType.RawType,joinValues(pos,pos+l),token.ln,token.col);uri.push(raw);pos+=l+1;uri=uri.concat(getSC());}token=tokens[startPos];var line=token.ln;var column=token.col;var end=getLastPosition(uri,line,column,1);pos++;return newNode(NodeType.UriType,uri,token.ln,token.col,end);}
-
-
-function checkUriContent(i){return checkUri1(i)||checkFunction(i);}
-
-function getUriContent(){if(checkUri1(pos))return getString();else if(checkFunction(pos))return getFunction();}
-
-
-function checkUri1(i){var start=i;var l=undefined;if(i>=tokensLength)return 0;if(l=checkSC(i))i+=l;if(tokens[i].type!==TokenType.StringDQ&&tokens[i].type!==TokenType.StringSQ)return 0;i++;if(l=checkSC(i))i+=l;return i-start;}
-
-
-
-function checkValue(i){var start=i;var l=undefined;var s=undefined;var _i=undefined;while(i<tokensLength){if(checkDeclDelim(i))break;s=checkSC(i);_i=i+s;if(l=_checkValue(_i))i+=l+s;if(!l||checkBlock(i-l))break;}return i-start;}
-
-
-function _checkValue(i){return checkInterpolation(i)||checkVariable(i)||checkVhash(i)||checkBlock(i)||checkAtkeyword(i)||checkOperator(i)||checkImportant(i)||checkGlobal(i)||checkDefault(i)||checkProgid(i)||checkAny(i);}
-
-function getValue(){var startPos=pos;var x=[];var _pos=undefined;var s=undefined;while(pos<tokensLength){s=checkSC(pos);_pos=pos+s;if(checkDeclDelim(_pos))break;if(!_checkValue(_pos))break;if(s)x=x.concat(getSC());x.push(_getValue());if(checkBlock(_pos))break;}var token=tokens[startPos];return newNode(NodeType.ValueType,x,token.ln,token.col);}
-
-function _getValue(){if(checkInterpolation(pos))return getInterpolation();else if(checkVariable(pos))return getVariable();else if(checkVhash(pos))return getVhash();else if(checkBlock(pos))return getBlock();else if(checkAtkeyword(pos))return getAtkeyword();else if(checkOperator(pos))return getOperator();else if(checkImportant(pos))return getImportant();else if(checkGlobal(pos))return getGlobal();else if(checkDefault(pos))return getDefault();else if(checkProgid(pos))return getProgid();else if(checkAny(pos))return getAny();}
-
-
-
-function checkVariable(i){var l;if(i>=tokensLength||tokens[i].type!==TokenType.DollarSign)return 0;return(l=checkIdent(i+1))?l+1:0;}
-
-
-
-function getVariable(){var startPos=pos;var x=[];pos++;x.push(getIdent());var token=tokens[startPos];return newNode(NodeType.VariableType,x,token.ln,token.col);}
-
-
-
-function checkVariablesList(i){var d=0;
-var l=undefined;if(i>=tokensLength)return 0;if(l=checkVariable(i))i+=l;else return 0;while(i<tokensLength&&tokens[i].type===TokenType.FullStop){d++;i++;}return d===3?l+d:0;}
-
-
-
-function getVariablesList(){var startPos=pos;var x=getVariable();var token=tokens[startPos];var line=token.ln;var column=token.col;var end=getLastPosition([x],line,column,3);pos+=3;return newNode(NodeType.VariablesListType,[x],token.ln,token.col,end);}
-
-
-
-
-function checkVhash(i){var l;if(i>=tokensLength||tokens[i].type!==TokenType.NumberSign)return 0;return(l=checkNmName2(i+1))?l+1:0;}
-
-
-
-function getVhash(){var startPos=pos;var x=undefined;var token=tokens[startPos];var line=token.ln;var column=token.col;pos++;x=getNmName2();var end=getLastPosition(x,line,column+1);return newNode(NodeType.VhashType,x,token.ln,token.col,end);}module.exports=function(_tokens,context){tokens=_tokens;tokensLength=tokens.length;pos=0;return contexts[context]();};function checkSelectorsGroup(i){if(i>=tokensLength)return 0;var start=i;var l=undefined;if(l=checkSelector(i))i+=l;else return 0;while(i<tokensLength){var sb=checkSC(i);var c=checkDelim(i+sb);if(!c)break;var sa=checkSC(i+sb+c);if(l=checkSelector(i+sb+c+sa))i+=sb+c+sa+l;else break;}tokens[start].selectorsGroupEnd=i;return i-start;}function getSelectorsGroup(){var selectorsGroup=[];var selectorsGroupEnd=tokens[pos].selectorsGroupEnd;selectorsGroup.push(getSelector());while(pos<selectorsGroupEnd){selectorsGroup=selectorsGroup.concat(getSC());selectorsGroup.push(getDelim());selectorsGroup=selectorsGroup.concat(getSC());selectorsGroup.push(getSelector());}return selectorsGroup;}function checkSelector(i){var l;if(l=checkSelector1(i))tokens[i].selectorType=1;else if(l=checkSelector2(i))tokens[i].selectorType=2;return l;}function getSelector(){var selectorType=tokens[pos].selectorType;if(selectorType===1)return getSelector1();else return getSelector2();}
-
-function checkSelector1(i){if(i>=tokensLength)return 0;var start=i;var l=undefined;if(l=checkCompoundSelector(i))i+=l;else return 0;while(i<tokensLength){var s=checkSC(i);var c=checkCombinator(i+s);if(!s&&!c)break;if(c){i+=s+c;s=checkSC(i);}if(l=checkCompoundSelector(i+s))i+=s+l;else break;}tokens[start].selectorEnd=i;return i-start;}function getSelector1(){var type=NodeType.SelectorType;var token=tokens[pos];var line=token.ln;var column=token.col;var selectorEnd=token.selectorEnd;var content=getCompoundSelector();while(pos<selectorEnd){if(checkSC(pos))content=content.concat(getSC());else if(checkCombinator(pos))content.push(getCombinator());else if(checkCompoundSelector(pos))content=content.concat(getCompoundSelector());}return newNode(type,content,line,column);}
-
-function checkSelector2(i){if(i>=tokensLength)return 0;var start=i;var l=undefined;if(l=checkCombinator(i))i+=l;else return 0;while(i<tokensLength){var sb=checkSC(i);if(l=checkCompoundSelector(i+sb))i+=sb+l;else break;var sa=checkSC(i);var c=checkCombinator(i+sa);if(!sa&&!c)break;if(c){i+=sa+c;}}tokens[start].selectorEnd=i;return i-start;}function getSelector2(){var type=NodeType.SelectorType;var token=tokens[pos];var line=token.ln;var column=token.col;var selectorEnd=token.selectorEnd;var content=[getCombinator()];while(pos<selectorEnd){if(checkSC(pos))content=content.concat(getSC());else if(checkCombinator(pos))content.push(getCombinator());else if(checkCompoundSelector(pos))content=content.concat(getCompoundSelector());}return newNode(type,content,line,column);}function checkCompoundSelector(i){var l=undefined;if(l=checkCompoundSelector1(i)){tokens[i].compoundSelectorType=1;}else if(l=checkCompoundSelector2(i)){tokens[i].compoundSelectorType=2;}return l;}function getCompoundSelector(){var type=tokens[pos].compoundSelectorType;if(type===1)return getCompoundSelector1();if(type===2)return getCompoundSelector2();}function checkCompoundSelector1(i){if(i>=tokensLength)return 0;var start=i;var l=undefined;if(l=checkTypeSelector(i)||checkPlaceholder(i)||checkParentSelectorWithExtension(i))i+=l;else return 0;while(i<tokensLength){var _l2=checkShash(i)||checkClass(i)||checkAttributeSelector(i)||checkPseudo(i)||checkPlaceholder(i);if(_l2)i+=_l2;else break;}tokens[start].compoundSelectorEnd=i;return i-start;}function getCompoundSelector1(){var sequence=[];var compoundSelectorEnd=tokens[pos].compoundSelectorEnd;if(checkTypeSelector(pos))sequence.push(getTypeSelector());else if(checkPlaceholder(pos))sequence.push(getPlaceholder());else if(checkParentSelectorWithExtension(pos))sequence=sequence.concat(getParentSelectorWithExtension());while(pos<compoundSelectorEnd){if(checkShash(pos))sequence.push(getShash());else if(checkClass(pos))sequence.push(getClass());else if(checkAttributeSelector(pos))sequence.push(getAttributeSelector());else if(checkPseudo(pos))sequence.push(getPseudo());else if(checkPlaceholder(pos))sequence.push(getPlaceholder());}return sequence;}function checkCompoundSelector2(i){if(i>=tokensLength)return 0;var start=i;while(i<tokensLength){var l=checkShash(i)||checkClass(i)||checkAttributeSelector(i)||checkPseudo(i)||checkPlaceholder(i);if(l)i+=l;else break;}tokens[start].compoundSelectorEnd=i;return i-start;}function getCompoundSelector2(){var sequence=[];var compoundSelectorEnd=tokens[pos].compoundSelectorEnd;while(pos<compoundSelectorEnd){if(checkShash(pos))sequence.push(getShash());else if(checkClass(pos))sequence.push(getClass());else if(checkAttributeSelector(pos))sequence.push(getAttributeSelector());else if(checkPseudo(pos))sequence.push(getPseudo());else if(checkPlaceholder(pos))sequence.push(getPlaceholder());}return sequence;}function checkTypeSelector(i){if(i>=tokensLength)return 0;var start=i;var l=undefined;if(l=checkNamePrefix(i))i+=l;if(tokens[i].type===TokenType.Asterisk)i++;else if(l=checkIdentOrInterpolation(i))i+=l;else return 0;return i-start;}function getTypeSelector(){var type=NodeType.TypeSelectorType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];if(checkNamePrefix(pos))content.push(getNamePrefix());if(checkIdentOrInterpolation(pos))content=content.concat(getIdentOrInterpolation());return newNode(type,content,line,column);}function checkAttributeSelector(i){var l=undefined;if(l=checkAttributeSelector1(i))tokens[i].attributeSelectorType=1;else if(l=checkAttributeSelector2(i))tokens[i].attributeSelectorType=2;return l;}function getAttributeSelector(){var type=tokens[pos].attributeSelectorType;if(type===1)return getAttributeSelector1();else return getAttributeSelector2();}
-
-
-
-
-function checkAttributeSelector1(i){var start=i;if(tokens[i].type===TokenType.LeftSquareBracket)i++;else return 0;var l=undefined;if(l=checkSC(i))i+=l;if(l=checkAttributeName(i))i+=l;else return 0;if(l=checkSC(i))i+=l;if(l=checkAttributeMatch(i))i+=l;else return 0;if(l=checkSC(i))i+=l;if(l=checkAttributeValue(i))i+=l;else return 0;if(l=checkSC(i))i+=l;if(l=checkAttributeFlags(i)){i+=l;if(l=checkSC(i))i+=l;}if(tokens[i].type===TokenType.RightSquareBracket)i++;else return 0;return i-start;}function getAttributeSelector1(){var type=NodeType.AttributeSelectorType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];
-pos++;content=content.concat(getSC());content.push(getAttributeName());content=content.concat(getSC());content.push(getAttributeMatch());content=content.concat(getSC());content.push(getAttributeValue());content=content.concat(getSC());if(checkAttributeFlags(pos)){content.push(getAttributeFlags());content=content.concat(getSC());}
-pos++;var end=getLastPosition(content,line,column,1);return newNode(type,content,line,column,end);}
-
-function checkAttributeSelector2(i){var start=i;if(tokens[i].type===TokenType.LeftSquareBracket)i++;else return 0;var l=undefined;if(l=checkSC(i))i+=l;if(l=checkAttributeName(i))i+=l;else return 0;if(l=checkSC(i))i+=l;if(tokens[i].type===TokenType.RightSquareBracket)i++;else return 0;return i-start;}function getAttributeSelector2(){var type=NodeType.AttributeSelectorType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];
-pos++;content=content.concat(getSC());content.push(getAttributeName());content=content.concat(getSC());
-pos++;var end=getLastPosition(content,line,column,1);return newNode(type,content,line,column,end);}function checkAttributeName(i){var start=i;var l=undefined;if(l=checkNamePrefix(i))i+=l;if(l=checkIdentOrInterpolation(i))i+=l;else return 0;return i-start;}function getAttributeName(){var type=NodeType.AttributeNameType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];if(checkNamePrefix(pos))content.push(getNamePrefix());content=content.concat(getIdentOrInterpolation());return newNode(type,content,line,column);}function checkAttributeMatch(i){var l=undefined;if(l=checkAttributeMatch1(i))tokens[i].attributeMatchType=1;else if(l=checkAttributeMatch2(i))tokens[i].attributeMatchType=2;return l;}function getAttributeMatch(){var type=tokens[pos].attributeMatchType;if(type===1)return getAttributeMatch1();else return getAttributeMatch2();}function checkAttributeMatch1(i){var start=i;var type=tokens[i].type;if(type===TokenType.Tilde||type===TokenType.VerticalLine||type===TokenType.CircumflexAccent||type===TokenType.DollarSign||type===TokenType.Asterisk)i++;else return 0;if(tokens[i].type===TokenType.EqualsSign)i++;else return 0;return i-start;}function getAttributeMatch1(){var type=NodeType.AttributeMatchType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=tokens[pos].value+tokens[pos+1].value;pos+=2;return newNode(type,content,line,column);}function checkAttributeMatch2(i){if(tokens[i].type===TokenType.EqualsSign)return 1;else return 0;}function getAttributeMatch2(){var type=NodeType.AttributeMatchType;var token=tokens[pos];var line=token.ln;var column=token.col;var content='=';pos++;return newNode(type,content,line,column);}function checkAttributeValue(i){return checkString(i)||checkIdentOrInterpolation(i);}function getAttributeValue(){var type=NodeType.AttributeValueType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];if(checkString(pos))content.push(getString());else content=content.concat(getIdentOrInterpolation());return newNode(type,content,line,column);}function checkAttributeFlags(i){return checkIdentOrInterpolation(i);}function getAttributeFlags(){var type=NodeType.AttributeFlagsType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=getIdentOrInterpolation();return newNode(type,content,line,column);}function checkNamePrefix(i){if(i>=tokensLength)return 0;var l=undefined;if(l=checkNamePrefix1(i))tokens[i].namePrefixType=1;else if(l=checkNamePrefix2(i))tokens[i].namePrefixType=2;return l;}function getNamePrefix(){var type=tokens[pos].namePrefixType;if(type===1)return getNamePrefix1();else return getNamePrefix2();}
-
-
-function checkNamePrefix1(i){var start=i;var l=undefined;if(l=checkNamespacePrefix(i))i+=l;else return 0;if(l=checkCommentML(i))i+=l;if(l=checkNamespaceSeparator(i))i+=l;else return 0;return i-start;}function getNamePrefix1(){var type=NodeType.NamePrefixType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];content.push(getNamespacePrefix());if(checkCommentML(pos))content.push(getCommentML());content.push(getNamespaceSeparator());return newNode(type,content,line,column);}
-
-function checkNamePrefix2(i){return checkNamespaceSeparator(i);}function getNamePrefix2(){var type=NodeType.NamePrefixType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[getNamespaceSeparator()];return newNode(type,content,line,column);}
-
-
-function checkNamespacePrefix(i){if(i>=tokensLength)return 0;var l=undefined;if(tokens[i].type===TokenType.Asterisk)return 1;else if(l=checkIdentOrInterpolation(i))return l;else return 0;}function getNamespacePrefix(){var type=NodeType.NamespacePrefixType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];if(checkIdentOrInterpolation(pos))content=content.concat(getIdentOrInterpolation());return newNode(type,content,line,column);}
-
-function checkNamespaceSeparator(i){if(i>=tokensLength)return 0;if(tokens[i].type===TokenType.VerticalLine)return 1;else return 0;}function getNamespaceSeparator(){var type=NodeType.NamespaceSeparatorType;var token=tokens[pos];var line=token.ln;var column=token.col;var content='|';pos++;return newNode(type,content,line,column);}
-
-},
-
-function(module,exports){
-
-'use strict';
-
-module.exports={
-ArgumentsType:'arguments',
-AtkeywordType:'atkeyword',
-AtruleType:'atrule',
-AttributeSelectorType:'attributeSelector',
-AttributeNameType:'attributeName',
-AttributeFlagsType:'attributeFlags',
-AttributeMatchType:'attributeMatch',
-AttributeValueType:'attributeValue',
-BlockType:'block',
-BracketsType:'brackets',
-ClassType:'class',
-CombinatorType:'combinator',
-CommentMLType:'multilineComment',
-CommentSLType:'singlelineComment',
-ConditionType:'condition',
-ConditionalStatementType:'conditionalStatement',
-DeclarationType:'declaration',
-DeclDelimType:'declarationDelimiter',
-DefaultType:'default',
-DelimType:'delimiter',
-DimensionType:'dimension',
-EscapedStringType:'escapedString',
-ExtendType:'extend',
-ExpressionType:'expression',
-FunctionType:'function',
-GlobalType:'global',
-IdentType:'ident',
-ImportantType:'important',
-IncludeType:'include',
-InterpolationType:'interpolation',
-InterpolatedVariableType:'interpolatedVariable',
-KeyframesSelectorType:'keyframesSelector',
-LoopType:'loop',
-MixinType:'mixin',
-NamePrefixType:'namePrefix',
-NamespacePrefixType:'namespacePrefix',
-NamespaceSeparatorType:'namespaceSeparator',
-NumberType:'number',
-OperatorType:'operator',
-OptionalType:'optional',
-ParenthesesType:'parentheses',
-ParentSelectorType:'parentSelector',
-ParentSelectorExtensionType:'parentSelectorExtension',
-PercentageType:'percentage',
-PlaceholderType:'placeholder',
-ProgidType:'progid',
-PropertyType:'property',
-PropertyDelimType:'propertyDelimiter',
-PseudocType:'pseudoClass',
-PseudoeType:'pseudoElement',
-RawType:'raw',
-RulesetType:'ruleset',
-SType:'space',
-SelectorType:'selector',
-ShashType:'id',
-StringType:'string',
-StylesheetType:'stylesheet',
-TypeSelectorType:'typeSelector',
-UriType:'uri',
-ValueType:'value',
-VariableType:'variable',
-VariablesListType:'variablesList',
-VhashType:'color'};
-
-
-},
-
-function(module,exports,__webpack_require__){
-
-'use strict';
-
-module.exports=function(css,tabSize){
-var TokenType=__webpack_require__(12);
-
-var tokens=[];
-var urlMode=false;
-var blockMode=0;
-var c=undefined;
-var cn=undefined;
-var pos=0;
-var tn=0;
-var ln=1;
-var col=1;
-
-var Punctuation={
-' ':TokenType.Space,
-'\n':TokenType.Newline,
-'\r':TokenType.Newline,
-'\t':TokenType.Tab,
-'!':TokenType.ExclamationMark,
-'"':TokenType.QuotationMark,
-'#':TokenType.NumberSign,
-'$':TokenType.DollarSign,
-'%':TokenType.PercentSign,
-'&':TokenType.Ampersand,
-'\'':TokenType.Apostrophe,
-'(':TokenType.LeftParenthesis,
-')':TokenType.RightParenthesis,
-'*':TokenType.Asterisk,
-'+':TokenType.PlusSign,
-',':TokenType.Comma,
-'-':TokenType.HyphenMinus,
-'.':TokenType.FullStop,
-'/':TokenType.Solidus,
-':':TokenType.Colon,
-';':TokenType.Semicolon,
-'<':TokenType.LessThanSign,
-'=':TokenType.EqualsSign,
-'==':TokenType.EqualitySign,
-'!=':TokenType.InequalitySign,
-'>':TokenType.GreaterThanSign,
-'?':TokenType.QuestionMark,
-'@':TokenType.CommercialAt,
-'[':TokenType.LeftSquareBracket,
-']':TokenType.RightSquareBracket,
-'^':TokenType.CircumflexAccent,
-'_':TokenType.LowLine,
-'{':TokenType.LeftCurlyBracket,
-'|':TokenType.VerticalLine,
-'}':TokenType.RightCurlyBracket,
-'~':TokenType.Tilde};
-
-
-
-
-
-
-
-function pushToken(type,value,column){
-tokens.push({
-tn:tn++,
-ln:ln,
-col:column,
-type:type,
-value:value});
-
-}
-
-
-
-
-
-
-function isDecimalDigit(c){
-return'0123456789'.indexOf(c)>=0;
-}
-
-
-
-
-
-function parseSpaces(css){
-var start=pos;
-
-
-for(;pos<css.length;pos++){
-if(css.charAt(pos)!==' ')break;
-}
-
-
-pushToken(TokenType.Space,css.substring(start,pos--),col);
-col+=pos-start;
-}
-
-
-
-
-
-
-function parseString(css,q){
-var start=pos;
-
-
-for(pos++;pos<css.length;pos++){
-
-if(css.charAt(pos)==='\\')pos++;else if(css.charAt(pos)===q)break;
-}
-
-
-var type=q==='"'?TokenType.StringDQ:TokenType.StringSQ;
-pushToken(type,css.substring(start,pos+1),col);
-col+=pos-start;
-}
-
-
+	  var syntaxParser = undefined;
+	  if (syntaxes[syntax]) {
+	    syntaxParser = syntaxes[syntax];
+	  } else {
+	    syntaxParser = syntaxes;
+	  }
 
+	  if (!syntaxParser) {
+	    var message = 'Syntax "' + syntax + '" is not supported yet, sorry';
+	    return console.error(message);
+	  }
 
+	  var getTokens = syntaxParser.tokenizer;
+	  var mark = syntaxParser.mark;
+	  var parse = syntaxParser.parse;
 
-function parseDecimalNumber(css){
-var start=pos;
+	  var tokens = getTokens(css, tabSize);
+	  mark(tokens);
 
+	  var ast;
+	  try {
+	    ast = parse(tokens, context);
+	  } catch (e) {
+	    if (!e.syntax) throw e;
+	    throw new ParsingError(e, css);
+	  }
 
-for(;pos<css.length;pos++){
-if(!isDecimalDigit(css.charAt(pos)))break;
-}
+	  return ast;
+	}
 
+	module.exports = parser;
 
-pushToken(TokenType.DecimalNumber,css.substring(start,pos--),col);
-col+=pos-start;
-}
+/***/ },
+/* 8 */
+/***/ function(module, exports, __webpack_require__) {
 
+	'use strict';
 
+	var parserPackage = __webpack_require__(9);
 
+	/**
+	 * @param {Error} e
+	 * @param {String} css
+	 */
+	function ParsingError(e, css) {
+	  this.line = e.line;
+	  this.syntax = e.syntax;
+	  this.css_ = css;
+	}
 
+	ParsingError.prototype = Object.defineProperties({
+	  /**
+	   * @type {String}
+	   * @private
+	   */
+	  customMessage_: '',
 
-function parseIdentifier(css){
-var start=pos;
+	  /**
+	   * @type {Number}
+	   */
+	  line: null,
 
+	  /**
+	   * @type {String}
+	   */
+	  name: 'Parsing error',
 
-while(css.charAt(pos)==='/')pos++;
+	  /**
+	   * @type {String}
+	   */
+	  syntax: null,
 
+	  /**
+	   * @type {String}
+	   */
+	  version: parserPackage.version,
 
-for(;pos<css.length;pos++){
+	  /**
+	   * @return {String}
+	   */
+	  toString: function () {
+	    return [this.name + ': ' + this.message, '', this.context, '', 'Syntax: ' + this.syntax, 'Gonzales PE version: ' + this.version].join('\n');
+	  }
+	}, {
+	  context: { /**
+	              * @type {String}
+	              */
 
-if(css.charAt(pos)==='\\')pos++;else if(css.charAt(pos)in Punctuation)break;
-}
+	    get: function () {
+	      var LINES_AROUND = 2;
 
-var ident=css.substring(start,pos--);
+	      var result = [];
+	      var currentLineNumber = this.line;
+	      var start = currentLineNumber - 1 - LINES_AROUND;
+	      var end = currentLineNumber + LINES_AROUND;
+	      var lines = this.css_.split(/\r\n|\r|\n/);
 
+	      for (var i = start; i < end; i++) {
+	        var line = lines[i];
+	        if (!line) continue;
+	        var ln = i + 1;
+	        var mark = ln === currentLineNumber ? '*' : ' ';
+	        result.push(ln + mark + '| ' + line);
+	      }
 
-if(!urlMode&&ident==='url'&&css.charAt(pos+1)==='('){
-urlMode=true;
-}
+	      return result.join('\n');
+	    },
+	    configurable: true,
+	    enumerable: true
+	  },
+	  message: {
 
+	    /**
+	     * @type {String}
+	     */
 
-pushToken(TokenType.Identifier,ident,col);
-col+=pos-start;
-}
+	    get: function () {
+	      if (this.customMessage_) {
+	        return this.customMessage_;
+	      } else {
+	        var message = 'Please check validity of the block';
+	        if (typeof this.line === 'number') message += ' starting from line #' + this.line;
+	        return message;
+	      }
+	    },
+	    set: function (message) {
+	      this.customMessage_ = message;
+	    },
+	    configurable: true,
+	    enumerable: true
+	  }
+	});
 
+	module.exports = ParsingError;
 
+/***/ },
+/* 9 */
+/***/ function(module, exports) {
 
+	module.exports = {
+		"name": "gonzales-pe",
+		"description": "Gonzales Preprocessor Edition (fast CSS parser)",
+		"version": "3.3.1",
+		"homepage": "http://github.com/tonyganch/gonzales-pe",
+		"bugs": "http://github.com/tonyganch/gonzales-pe/issues",
+		"license": "MIT",
+		"author": {
+			"name": "Tony Ganch",
+			"email": "tonyganch+github@gmail.com",
+			"url": "http://tonyganch.com"
+		},
+		"main": "./lib/gonzales",
+		"repository": {
+			"type": "git",
+			"url": "http://github.com/tonyganch/gonzales-pe.git"
+		},
+		"scripts": {
+			"autofix-tests": "bash ./scripts/build.sh && bash ./scripts/autofix-tests.sh",
+			"build": "bash ./scripts/build.sh",
+			"init": "bash ./scripts/init.sh",
+			"log": "bash ./scripts/log.sh",
+			"prepublish": "bash ./scripts/prepublish.sh",
+			"postpublish": "bash ./scripts/postpublish.sh",
+			"test": "bash ./scripts/build.sh && bash ./scripts/test.sh",
+			"watch": "bash ./scripts/watch.sh"
+		},
+		"bin": {
+			"gonzales": "./bin/gonzales.js"
+		},
+		"dependencies": {
+			"minimist": "1.1.x"
+		},
+		"devDependencies": {
+			"babel-loader": "^5.3.2",
+			"coffee-script": "~1.7.1",
+			"jscs": "2.1.0",
+			"jshint": "2.8.0",
+			"json-loader": "^0.5.3",
+			"mocha": "2.2.x",
+			"webpack": "^1.12.2"
+		},
+		"engines": {
+			"node": ">=0.6.0"
+		}
+	};
 
-function parseEquality(){
-pushToken(TokenType.EqualitySign,'==',col);
-pos++;
-col++;
-}
+/***/ },
+/* 10 */
+/***/ function(module, exports, __webpack_require__) {
 
+	'use strict';
 
+	exports.__esModule = true;
+	exports['default'] = {
+	  mark: __webpack_require__(11),
+	  parse: __webpack_require__(13),
+	  stringify: __webpack_require__(6),
+	  tokenizer: __webpack_require__(15)
+	};
+	module.exports = exports['default'];
 
+/***/ },
+/* 11 */
+/***/ function(module, exports, __webpack_require__) {
 
-function parseInequality(){
-pushToken(TokenType.InequalitySign,'!=',col);
-pos++;
-col++;
-}
+	'use strict';
 
+	var TokenType = __webpack_require__(12);
 
+	module.exports = (function () {
+	  /**
+	  * Mark whitespaces and comments
+	  */
+	  function markSC(tokens) {
+	    var tokensLength = tokens.length;
+	    var ws = -1; // Flag for whitespaces
+	    var sc = -1; // Flag for whitespaces and comments
+	    var t = undefined; // Current token
 
+	    // For every token in the token list, mark spaces and line breaks
+	    // as spaces (set both `ws` and `sc` flags). Mark multiline comments
+	    // with `sc` flag.
+	    // If there are several spaces or tabs or line breaks or multiline
+	    // comments in a row, group them: take the last one's index number
+	    // and save it to the first token in the group as a reference:
+	    // e.g., `ws_last = 7` for a group of whitespaces or `sc_last = 9`
+	    // for a group of whitespaces and comments.
+	    for (var i = 0; i < tokensLength; i++) {
+	      t = tokens[i];
+	      switch (t.type) {
+	        case TokenType.Space:
+	        case TokenType.Tab:
+	        case TokenType.Newline:
+	          t.ws = true;
+	          t.sc = true;
 
+	          if (ws === -1) ws = i;
+	          if (sc === -1) sc = i;
 
-function parseMLComment(css){
-var start=pos;
+	          break;
+	        case TokenType.CommentML:
+	        case TokenType.CommentSL:
+	          if (ws !== -1) {
+	            tokens[ws].ws_last = i - 1;
+	            ws = -1;
+	          }
 
+	          t.sc = true;
 
+	          break;
+	        default:
+	          if (ws !== -1) {
+	            tokens[ws].ws_last = i - 1;
+	            ws = -1;
+	          }
 
+	          if (sc !== -1) {
+	            tokens[sc].sc_last = i - 1;
+	            sc = -1;
+	          }
+	      }
+	    }
 
-for(pos+=2;pos<css.length;pos++){
-if(css.charAt(pos)==='*'&&css.charAt(pos+1)==='/'){
-pos++;
-break;
-}
-}
+	    if (ws !== -1) tokens[ws].ws_last = i - 1;
+	    if (sc !== -1) tokens[sc].sc_last = i - 1;
+	  }
 
+	  /**
+	  * Pair brackets
+	  */
+	  function markBrackets(tokens) {
+	    var tokensLength = tokens.length;
+	    var ps = []; // Parentheses
+	    var sbs = []; // Square brackets
+	    var cbs = []; // Curly brackets
+	    var t = undefined; // Current token
 
-var comment=css.substring(start,pos+1);
-pushToken(TokenType.CommentML,comment,col);
+	    // For every token in the token list, if we meet an opening (left)
+	    // bracket, push its index number to a corresponding array.
+	    // If we then meet a closing (right) bracket, look at the corresponding
+	    // array. If there are any elements (records about previously met
+	    // left brackets), take a token of the last left bracket (take
+	    // the last index number from the array and find a token with
+	    // this index number) and save right bracket's index as a reference:
+	    for (var i = 0; i < tokensLength; i++) {
+	      t = tokens[i];
+	      switch (t.type) {
+	        case TokenType.LeftParenthesis:
+	          ps.push(i);
+	          break;
+	        case TokenType.RightParenthesis:
+	          if (ps.length) {
+	            t.left = ps.pop();
+	            tokens[t.left].right = i;
+	          }
+	          break;
+	        case TokenType.LeftSquareBracket:
+	          sbs.push(i);
+	          break;
+	        case TokenType.RightSquareBracket:
+	          if (sbs.length) {
+	            t.left = sbs.pop();
+	            tokens[t.left].right = i;
+	          }
+	          break;
+	        case TokenType.LeftCurlyBracket:
+	          cbs.push(i);
+	          break;
+	        case TokenType.RightCurlyBracket:
+	          if (cbs.length) {
+	            t.left = cbs.pop();
+	            tokens[t.left].right = i;
+	          }
+	          break;
+	      }
+	    }
+	  }
 
-var newlines=comment.split('\n');
-if(newlines.length>1){
-ln+=newlines.length-1;
-col=newlines[newlines.length-1].length;
-}else{
-col+=pos-start;
-}
-}
+	  return function (tokens) {
+	    markBrackets(tokens);
+	    markSC(tokens);
+	  };
+	})();
 
+/***/ },
+/* 12 */
+/***/ function(module, exports) {
 
+	// jscs:disable
 
+	'use strict';
 
+	module.exports = {
+	    StringSQ: 'StringSQ',
+	    StringDQ: 'StringDQ',
+	    CommentML: 'CommentML',
+	    CommentSL: 'CommentSL',
 
-function parseSLComment(css){
-var start=pos;
+	    Newline: 'Newline',
+	    Space: 'Space',
+	    Tab: 'Tab',
 
+	    ExclamationMark: 'ExclamationMark', // !
+	    QuotationMark: 'QuotationMark', // "
+	    NumberSign: 'NumberSign', // #
+	    DollarSign: 'DollarSign', // $
+	    PercentSign: 'PercentSign', // %
+	    Ampersand: 'Ampersand', // &
+	    Apostrophe: 'Apostrophe', // '
+	    LeftParenthesis: 'LeftParenthesis', // (
+	    RightParenthesis: 'RightParenthesis', // )
+	    Asterisk: 'Asterisk', // *
+	    PlusSign: 'PlusSign', // +
+	    Comma: 'Comma', // ,
+	    HyphenMinus: 'HyphenMinus', // -
+	    FullStop: 'FullStop', // .
+	    Solidus: 'Solidus', // /
+	    Colon: 'Colon', // :
+	    Semicolon: 'Semicolon', // ;
+	    LessThanSign: 'LessThanSign', // <
+	    EqualsSign: 'EqualsSign', // =
+	    EqualitySign: 'EqualitySign', // ==
+	    InequalitySign: 'InequalitySign', // !=
+	    GreaterThanSign: 'GreaterThanSign', // >
+	    QuestionMark: 'QuestionMark', // ?
+	    CommercialAt: 'CommercialAt', // @
+	    LeftSquareBracket: 'LeftSquareBracket', // [
+	    ReverseSolidus: 'ReverseSolidus', // \
+	    RightSquareBracket: 'RightSquareBracket', // ]
+	    CircumflexAccent: 'CircumflexAccent', // ^
+	    LowLine: 'LowLine', // _
+	    LeftCurlyBracket: 'LeftCurlyBracket', // {
+	    VerticalLine: 'VerticalLine', // |
+	    RightCurlyBracket: 'RightCurlyBracket', // }
+	    Tilde: 'Tilde', // ~
 
+	    Identifier: 'Identifier',
+	    DecimalNumber: 'DecimalNumber'
+	};
 
+/***/ },
+/* 13 */
+/***/ function(module, exports, __webpack_require__) {
 
-for(pos+=2;pos<css.length;pos++){
-if(css.charAt(pos)==='\n'||css.charAt(pos)==='\r'){
-break;
-}
-}
+	// jscs:disable maximumLineLength
+	'use strict';var Node=__webpack_require__(1);var NodeType=__webpack_require__(14);var TokenType=__webpack_require__(12);var tokens=undefined;var tokensLength=undefined;var pos=undefined;var contexts={'arguments':function(){return checkArguments(pos) && getArguments();},'atkeyword':function(){return checkAtkeyword(pos) && getAtkeyword();},'atrule':function(){return checkAtrule(pos) && getAtrule();},'block':function(){return checkBlock(pos) && getBlock();},'brackets':function(){return checkBrackets(pos) && getBrackets();},'class':function(){return checkClass(pos) && getClass();},'combinator':function(){return checkCombinator(pos) && getCombinator();},'commentML':function(){return checkCommentML(pos) && getCommentML();},'commentSL':function(){return checkCommentSL(pos) && getCommentSL();},'condition':function(){return checkCondition(pos) && getCondition();},'conditionalStatement':function(){return checkConditionalStatement(pos) && getConditionalStatement();},'declaration':function(){return checkDeclaration(pos) && getDeclaration();},'declDelim':function(){return checkDeclDelim(pos) && getDeclDelim();},'default':function(){return checkDefault(pos) && getDefault();},'delim':function(){return checkDelim(pos) && getDelim();},'dimension':function(){return checkDimension(pos) && getDimension();},'expression':function(){return checkExpression(pos) && getExpression();},'extend':function(){return checkExtend(pos) && getExtend();},'function':function(){return checkFunction(pos) && getFunction();},'global':function(){return checkGlobal(pos) && getGlobal();},'ident':function(){return checkIdent(pos) && getIdent();},'important':function(){return checkImportant(pos) && getImportant();},'include':function(){return checkInclude(pos) && getInclude();},'interpolation':function(){return checkInterpolation(pos) && getInterpolation();},'loop':function(){return checkLoop(pos) && getLoop();},'mixin':function(){return checkMixin(pos) && getMixin();},'namespace':function(){return checkNamespace(pos) && getNamespace();},'number':function(){return checkNumber(pos) && getNumber();},'operator':function(){return checkOperator(pos) && getOperator();},'optional':function(){return checkOptional(pos) && getOptional();},'parentheses':function(){return checkParentheses(pos) && getParentheses();},'parentselector':function(){return checkParentSelector(pos) && getParentSelector();},'percentage':function(){return checkPercentage(pos) && getPercentage();},'placeholder':function(){return checkPlaceholder(pos) && getPlaceholder();},'progid':function(){return checkProgid(pos) && getProgid();},'property':function(){return checkProperty(pos) && getProperty();},'propertyDelim':function(){return checkPropertyDelim(pos) && getPropertyDelim();},'pseudoc':function(){return checkPseudoc(pos) && getPseudoc();},'pseudoe':function(){return checkPseudoe(pos) && getPseudoe();},'ruleset':function(){return checkRuleset(pos) && getRuleset();},'s':function(){return checkS(pos) && getS();},'selector':function(){return checkSelector(pos) && getSelector();},'shash':function(){return checkShash(pos) && getShash();},'string':function(){return checkString(pos) && getString();},'stylesheet':function(){return checkStylesheet(pos) && getStylesheet();},'unary':function(){return checkUnary(pos) && getUnary();},'uri':function(){return checkUri(pos) && getUri();},'value':function(){return checkValue(pos) && getValue();},'variable':function(){return checkVariable(pos) && getVariable();},'variableslist':function(){return checkVariablesList(pos) && getVariablesList();},'vhash':function(){return checkVhash(pos) && getVhash();}}; /**
+	 * Stop parsing and display error
+	 * @param {Number=} i Token's index number
+	 */function throwError(i){var ln=i?tokens[i].ln:tokens[pos].ln;throw {line:ln,syntax:'scss'};} /**
+	 * @param {Object} exclude
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkExcluding(exclude,i){var start=i;while(i < tokensLength) {if(exclude[tokens[i++].type])break;}return i - start - 2;} /**
+	 * @param {Number} start
+	 * @param {Number} finish
+	 * @returns {String}
+	 */function joinValues(start,finish){var s='';for(var i=start;i < finish + 1;i++) {s += tokens[i].value;}return s;} /**
+	 * @param {Number} start
+	 * @param {Number} num
+	 * @returns {String}
+	 */function joinValues2(start,num){if(start + num - 1 >= tokensLength)return;var s='';for(var i=0;i < num;i++) {s += tokens[start + i].value;}return s;}function getLastPosition(content,line,column,colOffset){return typeof content === 'string'?getLastPositionForString(content,line,column,colOffset):getLastPositionForArray(content,line,column,colOffset);}function getLastPositionForString(content,line,column,colOffset){var position=[];if(!content){position = [line,column];if(colOffset)position[1] += colOffset - 1;return position;}var lastLinebreak=content.lastIndexOf('\n');var endsWithLinebreak=lastLinebreak === content.length - 1;var splitContent=content.split('\n');var linebreaksCount=splitContent.length - 1;var prevLinebreak=linebreaksCount === 0 || linebreaksCount === 1?-1:content.length - splitContent[linebreaksCount - 1].length - 2; // Line:
+	var offset=endsWithLinebreak?linebreaksCount - 1:linebreaksCount;position[0] = line + offset; // Column:
+	if(endsWithLinebreak){offset = prevLinebreak !== -1?content.length - prevLinebreak:content.length - 1;}else {offset = linebreaksCount !== 0?content.length - lastLinebreak - column - 1:content.length - 1;}position[1] = column + offset;if(!colOffset)return position;if(endsWithLinebreak){position[0]++;position[1] = colOffset;}else {position[1] += colOffset;}return position;}function getLastPositionForArray(content,line,column,colOffset){var position;if(content.length === 0){position = [line,column];}else {var c=content[content.length - 1];if(c.hasOwnProperty('end')){position = [c.end.line,c.end.column];}else {position = getLastPosition(c.content,line,column);}}if(!colOffset)return position;if(tokens[pos - 1].type !== 'Newline'){position[1] += colOffset;}else {position[0]++;position[1] = 1;}return position;}function newNode(type,content,line,column,end){if(!end)end = getLastPosition(content,line,column);return new Node({type:type,content:content,start:{line:line,column:column},end:{line:end[0],column:end[1]},syntax:'scss'});} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkAny(i){return checkBrackets(i) || checkParentheses(i) || checkString(i) || checkVariablesList(i) || checkVariable(i) || checkPlaceholder(i) || checkPercentage(i) || checkDimension(i) || checkNumber(i) || checkUri(i) || checkExpression(i) || checkFunction(i) || checkInterpolation(i) || checkIdent(i) || checkClass(i) || checkUnary(i);} /**
+	 * @returns {Array}
+	 */function getAny(){if(checkBrackets(pos))return getBrackets();else if(checkParentheses(pos))return getParentheses();else if(checkString(pos))return getString();else if(checkVariablesList(pos))return getVariablesList();else if(checkVariable(pos))return getVariable();else if(checkPlaceholder(pos))return getPlaceholder();else if(checkPercentage(pos))return getPercentage();else if(checkDimension(pos))return getDimension();else if(checkNumber(pos))return getNumber();else if(checkUri(pos))return getUri();else if(checkExpression(pos))return getExpression();else if(checkFunction(pos))return getFunction();else if(checkInterpolation(pos))return getInterpolation();else if(checkIdent(pos))return getIdent();else if(checkClass(pos))return getClass();else if(checkUnary(pos))return getUnary();} /**
+	 * Check if token is part of mixin's arguments.
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of arguments
+	 */function checkArguments(i){var start=i;var l=undefined;if(i >= tokensLength || tokens[i].type !== TokenType.LeftParenthesis)return 0;i++;while(i < tokens[start].right) {if(l = checkArgument(i))i += l;else return 0;}return tokens[start].right - start + 1;} /**
+	 * Check if token is valid to be part of arguments list
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of argument
+	 */function checkArgument(i){return checkBrackets(i) || checkParentheses(i) || checkDeclaration(i) || checkFunction(i) || checkVariablesList(i) || checkVariable(i) || checkSC(i) || checkDelim(i) || checkDeclDelim(i) || checkString(i) || checkPercentage(i) || checkDimension(i) || checkNumber(i) || checkUri(i) || checkInterpolation(i) || checkIdent(i) || checkVhash(i) || checkOperator(i) || checkUnary(i);} /**
+	 * @returns {Array} Node that is part of arguments list
+	 */function getArgument(){if(checkBrackets(pos))return getBrackets();else if(checkParentheses(pos))return getParentheses();else if(checkDeclaration(pos))return getDeclaration();else if(checkFunction(pos))return getFunction();else if(checkVariablesList(pos))return getVariablesList();else if(checkVariable(pos))return getVariable();else if(checkSC(pos))return getSC();else if(checkDelim(pos))return getDelim();else if(checkDeclDelim(pos))return getDeclDelim();else if(checkString(pos))return getString();else if(checkPercentage(pos))return getPercentage();else if(checkDimension(pos))return getDimension();else if(checkNumber(pos))return getNumber();else if(checkUri(pos))return getUri();else if(checkInterpolation(pos))return getInterpolation();else if(checkIdent(pos))return getIdent();else if(checkVhash(pos))return getVhash();else if(checkOperator(pos))return getOperator();else if(checkUnary(pos))return getUnary();} /**
+	 * Check if token is part of an @-word (e.g. `@import`, `@include`)
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkAtkeyword(i){var l; // Check that token is `@`:
+	if(i >= tokensLength || tokens[i++].type !== TokenType.CommercialAt)return 0;return (l = checkIdentOrInterpolation(i))?l + 1:0;} /**
+	 * Get node with @-word
+	 * @returns {Array} `['atkeyword', ['ident', x]]` where `x` is
+	 *      an identifier without
+	 *      `@` (e.g. `import`, `include`)
+	 */function getAtkeyword(){var startPos=pos;var x=undefined;pos++;x = getIdentOrInterpolation();var token=tokens[startPos];return newNode(NodeType.AtkeywordType,x,token.ln,token.col);} /**
+	 * Check if token is a part of an @-rule
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of @-rule
+	 */function checkAtrule(i){var l;if(i >= tokensLength)return 0; // If token already has a record of being part of an @-rule,
+	// return the @-rule's length:
+	if(tokens[i].atrule_l !== undefined)return tokens[i].atrule_l; // If token is part of an @-rule, save the rule's type to token:
+	if(l = checkKeyframesRule(i))tokens[i].atrule_type = 4;else if(l = checkAtruler(i))tokens[i].atrule_type = 1; // @-rule with ruleset
+	else if(l = checkAtruleb(i))tokens[i].atrule_type = 2; // Block @-rule
+	else if(l = checkAtrules(i))tokens[i].atrule_type = 3; // Single-line @-rule
+	else return 0; // If token is part of an @-rule, save the rule's length to token:
+	tokens[i].atrule_l = l;return l;} /**
+	 * Get node with @-rule
+	 * @returns {Array}
+	 */function getAtrule(){switch(tokens[pos].atrule_type){case 1:return getAtruler(); // @-rule with ruleset
+	case 2:return getAtruleb(); // Block @-rule
+	case 3:return getAtrules(); // Single-line @-rule
+	case 4:return getKeyframesRule();}} /**
+	 * Check if token is part of a block @-rule
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the @-rule
+	 */function checkAtruleb(i){var start=i;var l=undefined;if(i >= tokensLength)return 0;if(l = checkAtkeyword(i))i += l;else return 0;if(l = checkTsets(i))i += l;if(l = checkBlock(i))i += l;else return 0;return i - start;} /**
+	 * Get node with a block @-rule
+	 * @returns {Array} `['atruleb', ['atkeyword', x], y, ['block', z]]`
+	 */function getAtruleb(){var startPos=pos;var x=undefined;x = [getAtkeyword()].concat(getTsets()).concat([getBlock()]);var token=tokens[startPos];return newNode(NodeType.AtruleType,x,token.ln,token.col);} /**
+	 * Check if token is part of an @-rule with ruleset
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the @-rule
+	 */function checkAtruler(i){var start=i;var l=undefined;if(i >= tokensLength)return 0;if(l = checkAtkeyword(i))i += l;else return 0;if(l = checkTsets(i))i += l;if(i < tokensLength && tokens[i].type === TokenType.LeftCurlyBracket)i++;else return 0;if(l = checkAtrulers(i))i += l;if(i < tokensLength && tokens[i].type === TokenType.RightCurlyBracket)i++;else return 0;return i - start;} /**
+	 * Get node with an @-rule with ruleset
+	 * @returns {Array} ['atruler', ['atkeyword', x], y, z]
+	 */function getAtruler(){var startPos=pos;var x=undefined;x = [getAtkeyword()].concat(getTsets());x.push(getAtrulers());var token=tokens[startPos];return newNode(NodeType.AtruleType,x,token.ln,token.col);} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkAtrulers(i){var start=i;var l=undefined;if(i >= tokensLength)return 0;while(l = checkRuleset(i) || checkAtrule(i) || checkSC(i)) {i += l;}if(i < tokensLength)tokens[i].atrulers_end = 1;return i - start;} /**
+	 * @returns {Array} `['atrulers', x]`
+	 */function getAtrulers(){var startPos=pos;var x=undefined;var token=tokens[startPos];var line=token.ln;var column=token.col;pos++;x = getSC();while(!tokens[pos].atrulers_end) {if(checkSC(pos))x = x.concat(getSC());else if(checkAtrule(pos))x.push(getAtrule());else if(checkRuleset(pos))x.push(getRuleset());}x = x.concat(getSC());var end=getLastPosition(x,line,column,1);pos++;return newNode(NodeType.BlockType,x,token.ln,token.col,end);} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkAtrules(i){var start=i;var l=undefined;if(i >= tokensLength)return 0;if(l = checkAtkeyword(i))i += l;else return 0;if(l = checkTsets(i))i += l;return i - start;} /**
+	 * @returns {Array} `['atrules', ['atkeyword', x], y]`
+	 */function getAtrules(){var startPos=pos;var x=undefined;x = [getAtkeyword()].concat(getTsets());var token=tokens[startPos];return newNode(NodeType.AtruleType,x,token.ln,token.col);} /**
+	 * Check if token is part of a block (e.g. `{...}`).
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the block
+	 */function checkBlock(i){return i < tokensLength && tokens[i].type === TokenType.LeftCurlyBracket?tokens[i].right - i + 1:0;} /**
+	 * Get node with a block
+	 * @returns {Array} `['block', x]`
+	 */function getBlock(){var startPos=pos;var end=tokens[pos].right;var x=[];var token=tokens[startPos];var line=token.ln;var column=token.col;pos++;while(pos < end) {if(checkBlockdecl(pos))x = x.concat(getBlockdecl());else throwError();}var end_=getLastPosition(x,line,column,1);pos = end + 1;return newNode(NodeType.BlockType,x,token.ln,token.col,end_);} /**
+	 * Check if token is part of a declaration (property-value pair)
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the declaration
+	 */function checkBlockdecl(i){var l;if(i >= tokensLength)return 0;if(l = checkBlockdecl1(i))tokens[i].bd_type = 1;else if(l = checkBlockdecl2(i))tokens[i].bd_type = 2;else if(l = checkBlockdecl3(i))tokens[i].bd_type = 3;else if(l = checkBlockdecl4(i))tokens[i].bd_type = 4;else return 0;return l;} /**
+	 * @returns {Array}
+	 */function getBlockdecl(){switch(tokens[pos].bd_type){case 1:return getBlockdecl1();case 2:return getBlockdecl2();case 3:return getBlockdecl3();case 4:return getBlockdecl4();}} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkBlockdecl1(i){var start=i;var l=undefined;if(l = checkSC(i))i += l;if(l = checkConditionalStatement(i))tokens[i].bd_kind = 1;else if(l = checkInclude(i))tokens[i].bd_kind = 2;else if(l = checkExtend(i))tokens[i].bd_kind = 4;else if(l = checkLoop(i))tokens[i].bd_kind = 3;else if(l = checkAtrule(i))tokens[i].bd_kind = 6;else if(l = checkRuleset(i))tokens[i].bd_kind = 7;else if(l = checkDeclaration(i))tokens[i].bd_kind = 5;else return 0;i += l;if(i < tokensLength && (l = checkDeclDelim(i)))i += l;else return 0;if(l = checkSC(i))i += l;return i - start;} /**
+	 * @returns {Array}
+	 */function getBlockdecl1(){var sc=getSC();var x=undefined;switch(tokens[pos].bd_kind){case 1:x = getConditionalStatement();break;case 2:x = getInclude();break;case 3:x = getLoop();break;case 4:x = getExtend();break;case 5:x = getDeclaration();break;case 6:x = getAtrule();break;case 7:x = getRuleset();break;}return sc.concat([x]).concat([getDeclDelim()]).concat(getSC());} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkBlockdecl2(i){var start=i;var l=undefined;if(l = checkSC(i))i += l;if(l = checkConditionalStatement(i))tokens[i].bd_kind = 1;else if(l = checkInclude(i))tokens[i].bd_kind = 2;else if(l = checkExtend(i))tokens[i].bd_kind = 4;else if(l = checkMixin(i))tokens[i].bd_kind = 8;else if(l = checkLoop(i))tokens[i].bd_kind = 3;else if(l = checkAtrule(i))tokens[i].bd_kind = 6;else if(l = checkRuleset(i))tokens[i].bd_kind = 7;else if(l = checkDeclaration(i))tokens[i].bd_kind = 5;else return 0;i += l;if(l = checkSC(i))i += l;return i - start;} /**
+	 * @returns {Array}
+	 */function getBlockdecl2(){var sc=getSC();var x=undefined;switch(tokens[pos].bd_kind){case 1:x = getConditionalStatement();break;case 2:x = getInclude();break;case 3:x = getLoop();break;case 4:x = getExtend();break;case 5:x = getDeclaration();break;case 6:x = getAtrule();break;case 7:x = getRuleset();break;case 8:x = getMixin();break;}return sc.concat([x]).concat(getSC());} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkBlockdecl3(i){var start=i;var l=undefined;if(l = checkSC(i))i += l;if(l = checkDeclDelim(i))i += l;else return 0;if(l = checkSC(i))i += l;return i - start;} /**
+	 * @returns {Array} `[s0, ['declDelim'], s1]` where `s0` and `s1` are
+	 *      are optional whitespaces.
+	 */function getBlockdecl3(){return getSC().concat([getDeclDelim()]).concat(getSC());} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkBlockdecl4(i){return checkSC(i);} /**
+	 * @returns {Array}
+	 */function getBlockdecl4(){return getSC();} /**
+	 * Check if token is part of text inside square brackets, e.g. `[1]`
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkBrackets(i){if(i >= tokensLength || tokens[i].type !== TokenType.LeftSquareBracket)return 0;return tokens[i].right - i + 1;} /**
+	 * Get node with text inside parentheses or square brackets (e.g. `(1)`)
+	 * @return {Node}
+	 */function getBrackets(){var startPos=pos;var token=tokens[startPos];var line=token.ln;var column=token.col;pos++;var tsets=getTsets();var end=getLastPosition(tsets,line,column,1);pos++;return newNode(NodeType.BracketsType,tsets,token.ln,token.col,end);} /**
+	 * Check if token is part of a class selector (e.g. `.abc`)
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the class selector
+	 */function checkClass(i){var start=i;var l=undefined;if(i >= tokensLength)return 0;if(tokens[i].class_l)return tokens[i].class_l;if(tokens[i++].type !== TokenType.FullStop)return 0;if(l = checkIdentOrInterpolation(i))i += l;else return 0;return i - start;} /**
+	 * Get node with a class selector
+	 * @returns {Array} `['class', ['ident', x]]` where x is a class's
+	 *      identifier (without `.`, e.g. `abc`).
+	 */function getClass(){var startPos=pos;var x=[];pos++;x = x.concat(getIdentOrInterpolation());var token=tokens[startPos];return newNode(NodeType.ClassType,x,token.ln,token.col);}function checkCombinator(i){if(i >= tokensLength)return 0;var l=undefined;if(l = checkCombinator1(i))tokens[i].combinatorType = 1;else if(l = checkCombinator2(i))tokens[i].combinatorType = 2;else if(l = checkCombinator3(i))tokens[i].combinatorType = 3;return l;}function getCombinator(){var type=tokens[pos].combinatorType;if(type === 1)return getCombinator1();if(type === 2)return getCombinator2();if(type === 3)return getCombinator3();} /**
+	 * (1) `||`
+	 */function checkCombinator1(i){if(tokens[i].type === TokenType.VerticalLine && tokens[i + 1].type === TokenType.VerticalLine)return 2;else return 0;}function getCombinator1(){var type=NodeType.CombinatorType;var token=tokens[pos];var line=token.ln;var column=token.col;var content='||';pos += 2;return newNode(type,content,line,column);} /**
+	 * (1) `>`
+	 * (2) `+`
+	 * (3) `~`
+	 */function checkCombinator2(i){var type=tokens[i].type;if(type === TokenType.PlusSign || type === TokenType.GreaterThanSign || type === TokenType.Tilde)return 1;else return 0;}function getCombinator2(){var type=NodeType.CombinatorType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=tokens[pos++].value;return newNode(type,content,line,column);} /**
+	 * (1) `/panda/`
+	 */function checkCombinator3(i){var start=i;if(tokens[i].type === TokenType.Solidus)i++;else return 0;var l=undefined;if(l = checkIdent(i))i += l;else return 0;if(tokens[i].type === TokenType.Solidus)i++;else return 0;return i - start;}function getCombinator3(){var type=NodeType.CombinatorType;var token=tokens[pos];var line=token.ln;var column=token.col; // Skip `/`.
+	pos++;var ident=getIdent(); // Skip `/`.
+	pos++;var content='/' + ident.content + '/';return newNode(type,content,line,column);} /**
+	 * Check if token is a multiline comment.
+	 * @param {Number} i Token's index number
+	 * @returns {Number} `1` if token is a multiline comment, otherwise `0`
+	 */function checkCommentML(i){return i < tokensLength && tokens[i].type === TokenType.CommentML?1:0;} /**
+	 * Get node with a multiline comment
+	 * @returns {Array} `['commentML', x]` where `x`
+	 *      is the comment's text (without `/*` and `* /`).
+	 */function getCommentML(){var startPos=pos;var s=tokens[pos].value.substring(2);var l=s.length;var token=tokens[startPos];var line=token.ln;var column=token.col;if(s.charAt(l - 2) === '*' && s.charAt(l - 1) === '/')s = s.substring(0,l - 2);var end=getLastPosition(s,line,column,2);if(end[0] === line)end[1] += 2;pos++;return newNode(NodeType.CommentMLType,s,token.ln,token.col,end);} /**
+	 * Check if token is part of a single-line comment.
+	 * @param {Number} i Token's index number
+	 * @returns {Number} `1` if token is a single-line comment, otherwise `0`
+	 */function checkCommentSL(i){return i < tokensLength && tokens[i].type === TokenType.CommentSL?1:0;} /**
+	 * Get node with a single-line comment.
+	 * @returns {Array} `['commentSL', x]` where `x` is comment's message
+	 *      (without `//`)
+	 */function getCommentSL(){var startPos=pos;var x=undefined;var token=tokens[startPos];var line=token.ln;var column=token.col;x = tokens[pos++].value.substring(2);var end=getLastPosition(x,line,column + 2);return newNode(NodeType.CommentSLType,x,token.ln,token.col,end);} /**
+	 * Check if token is part of a condition
+	 * (e.g. `@if ...`, `@else if ...` or `@else ...`).
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the condition
+	 */function checkCondition(i){var start=i;var l=undefined;var _i=undefined;var s=undefined;if(i >= tokensLength)return 0;if(l = checkAtkeyword(i))i += l;else return 0;if(['if','else'].indexOf(tokens[start + 1].value) < 0)return 0;while(i < tokensLength) {if(l = checkBlock(i))break;s = checkSC(i);_i = i + s;if(l = _checkCondition(_i))i += l + s;else break;}return i - start;}function _checkCondition(i){return checkVariable(i) || checkNumber(i) || checkInterpolation(i) || checkIdent(i) || checkOperator(i) || checkCombinator(i) || checkString(i);} /**
+	 * Get node with a condition.
+	 * @returns {Array} `['condition', x]`
+	 */function getCondition(){var startPos=pos;var x=[];var s;var _pos;x.push(getAtkeyword());while(pos < tokensLength) {if(checkBlock(pos))break;s = checkSC(pos);_pos = pos + s;if(!_checkCondition(_pos))break;if(s)x = x.concat(getSC());x.push(_getCondition());}var token=tokens[startPos];return newNode(NodeType.ConditionType,x,token.ln,token.col);}function _getCondition(){if(checkVariable(pos))return getVariable();if(checkNumber(pos))return getNumber();if(checkInterpolation(pos))return getInterpolation();if(checkIdent(pos))return getIdent();if(checkOperator(pos))return getOperator();if(checkCombinator(pos))return getCombinator();if(checkString(pos))return getString();} /**
+	 * Check if token is part of a conditional statement
+	 * (e.g. `@if ... {} @else if ... {} @else ... {}`).
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the condition
+	 */function checkConditionalStatement(i){var start=i;var l=undefined;if(i >= tokensLength)return 0;if(l = checkCondition(i))i += l;else return 0;if(l = checkSC(i))i += l;if(l = checkBlock(i))i += l;else return 0;return i - start;} /**
+	 * Get node with a condition.
+	 * @returns {Array} `['condition', x]`
+	 */function getConditionalStatement(){var startPos=pos;var x=[];x.push(getCondition());x = x.concat(getSC());x.push(getBlock());var token=tokens[startPos];return newNode(NodeType.ConditionalStatementType,x,token.ln,token.col);} /**
+	 * Check if token is part of a declaration (property-value pair)
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the declaration
+	 */function checkDeclaration(i){var start=i;var l=undefined;if(i >= tokensLength)return 0;if(l = checkProperty(i))i += l;else return 0;if(l = checkSC(i))i += l;if(l = checkPropertyDelim(i))i++;else return 0;if(l = checkSC(i))i += l;if(l = checkValue(i))i += l;else return 0;return i - start;} /**
+	 * Get node with a declaration
+	 * @returns {Array} `['declaration', ['property', x], ['propertyDelim'],
+	 *       ['value', y]]`
+	 */function getDeclaration(){var startPos=pos;var x=[];x.push(getProperty());x = x.concat(getSC());x.push(getPropertyDelim());x = x.concat(getSC());x.push(getValue());var token=tokens[startPos];return newNode(NodeType.DeclarationType,x,token.ln,token.col);} /**
+	 * Check if token is a semicolon
+	 * @param {Number} i Token's index number
+	 * @returns {Number} `1` if token is a semicolon, otherwise `0`
+	 */function checkDeclDelim(i){return i < tokensLength && tokens[i].type === TokenType.Semicolon?1:0;} /**
+	 * Get node with a semicolon
+	 * @returns {Array} `['declDelim']`
+	 */function getDeclDelim(){var startPos=pos++;var token=tokens[startPos];return newNode(NodeType.DeclDelimType,';',token.ln,token.col);} /**
+	 * Check if token if part of `!default` word.
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the `!default` word
+	 */function checkDefault(i){var start=i;var l=undefined;if(i >= tokensLength || tokens[i++].type !== TokenType.ExclamationMark)return 0;if(l = checkSC(i))i += l;if(tokens[i].value === 'default'){tokens[start].defaultEnd = i;return i - start + 1;}else {return 0;}} /**
+	 * Get node with a `!default` word
+	 * @returns {Array} `['default', sc]` where `sc` is optional whitespace
+	 */function getDefault(){var token=tokens[pos];var line=token.ln;var column=token.col;var content=joinValues(pos,token.defaultEnd);pos = token.defaultEnd + 1;return newNode(NodeType.DefaultType,content,line,column);} /**
+	 * Check if token is a comma
+	 * @param {Number} i Token's index number
+	 * @returns {Number} `1` if token is a comma, otherwise `0`
+	 */function checkDelim(i){return i < tokensLength && tokens[i].type === TokenType.Comma?1:0;} /**
+	 * Get node with a comma
+	 * @returns {Array} `['delim']`
+	 */function getDelim(){var startPos=pos;pos++;var token=tokens[startPos];return newNode(NodeType.DelimType,',',token.ln,token.col);} /**
+	 * Check if token is part of a number with dimension unit (e.g. `10px`)
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkDimension(i){var ln=checkNumber(i);var li=undefined;if(i >= tokensLength || !ln || i + ln >= tokensLength)return 0;return (li = checkNmName2(i + ln))?ln + li:0;} /**
+	 * Get node of a number with dimension unit
+	 * @returns {Array} `['dimension', ['number', x], ['ident', y]]` where
+	 *      `x` is a number converted to string (e.g. `'10'`) and `y` is
+	 *      a dimension unit (e.g. `'px'`).
+	 */function getDimension(){var startPos=pos;var x=[getNumber()];var token=tokens[pos];var ident=newNode(NodeType.IdentType,getNmName2(),token.ln,token.col);x.push(ident);token = tokens[startPos];return newNode(NodeType.DimensionType,x,token.ln,token.col);} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkExpression(i){var start=i;if(i >= tokensLength || tokens[i++].value !== 'expression' || i >= tokensLength || tokens[i].type !== TokenType.LeftParenthesis)return 0;return tokens[i].right - start + 1;} /**
+	 * @returns {Array}
+	 */function getExpression(){var startPos=pos;var e;var token=tokens[startPos];var line=token.ln;var column=token.col;pos++;e = joinValues(pos + 1,tokens[pos].right - 1);var end=getLastPosition(e,line,column,1);if(end[0] === line)end[1] += 11;pos = tokens[pos].right + 1;return newNode(NodeType.ExpressionType,e,token.ln,token.col,end);}function checkExtend(i){var l=0;if(l = checkExtend1(i))tokens[i].extend_child = 1;else if(l = checkExtend2(i))tokens[i].extend_child = 2;return l;}function getExtend(){var type=tokens[pos].extend_child;if(type === 1)return getExtend1();else if(type === 2)return getExtend2();} /**
+	 * Checks if token is part of an extend with `!optional` flag.
+	 * @param {Number} i
+	 */function checkExtend1(i){var start=i;var l;if(i >= tokensLength)return 0;if(l = checkAtkeyword(i))i += l;else return 0;if(tokens[start + 1].value !== 'extend')return 0;if(l = checkSC(i))i += l;else return 0;if(l = checkSelectorsGroup(i))i += l;else return 0;if(l = checkSC(i))i += l;else return 0;if(l = checkOptional(i))i += l;else return 0;return i - start;}function getExtend1(){var startPos=pos;var x=[].concat([getAtkeyword()],getSC(),getSelectorsGroup(),getSC(),[getOptional()]);var token=tokens[startPos];return newNode(NodeType.ExtendType,x,token.ln,token.col);} /**
+	 * Checks if token is part of an extend without `!optional` flag.
+	 * @param {Number} i
+	 */function checkExtend2(i){var start=i;var l;if(i >= tokensLength)return 0;if(l = checkAtkeyword(i))i += l;else return 0;if(tokens[start + 1].value !== 'extend')return 0;if(l = checkSC(i))i += l;else return 0;if(l = checkSelectorsGroup(i))i += l;else return 0;return i - start;}function getExtend2(){var startPos=pos;var x=[].concat([getAtkeyword()],getSC(),getSelectorsGroup());var token=tokens[startPos];return newNode(NodeType.ExtendType,x,token.ln,token.col);} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkFunction(i){var start=i;var l=undefined;if(i >= tokensLength)return 0;if(l = checkIdentOrInterpolation(i))i += l;else return 0;return i < tokensLength && tokens[i].type === TokenType.LeftParenthesis?tokens[i].right - start + 1:0;} /**
+	 * @returns {Array}
+	 */function getFunction(){var startPos=pos;var x=getIdentOrInterpolation();var body=undefined;body = getArguments();x.push(body);var token=tokens[startPos];return newNode(NodeType.FunctionType,x,token.ln,token.col);} /**
+	 * @returns {Array}
+	 */function getArguments(){var startPos=pos;var x=[];var body=undefined;var token=tokens[startPos];var line=token.ln;var column=token.col;pos++;while(pos < tokensLength && tokens[pos].type !== TokenType.RightParenthesis) {if(checkDeclaration(pos))x.push(getDeclaration());else if(checkArgument(pos)){body = getArgument();if(typeof body.content === 'string')x.push(body);else x = x.concat(body);}else if(checkClass(pos))x.push(getClass());else throwError();}var end=getLastPosition(x,line,column,1);pos++;return newNode(NodeType.ArgumentsType,x,token.ln,token.col,end);} /**
+	 * Check if token is part of an identifier
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the identifier
+	 */function checkIdent(i){var start=i;var interpolations=[];var wasIdent=undefined;var wasInt=false;var l=undefined;if(i >= tokensLength)return 0; // Check if token is part of an identifier starting with `_`:
+	if(tokens[i].type === TokenType.LowLine)return checkIdentLowLine(i);if(tokens[i].type === TokenType.HyphenMinus && tokens[i + 1].type === TokenType.DecimalNumber)return 0; // If token is a character, `-`, `$` or `*`, skip it & continue:
+	if(l = _checkIdent(i))i += l;else return 0; // Remember if previous token's type was identifier:
+	wasIdent = tokens[i - 1].type === TokenType.Identifier;while(i < tokensLength) {l = _checkIdent(i);if(!l)break;wasIdent = true;i += l;}if(!wasIdent && !wasInt && tokens[start].type !== TokenType.Asterisk)return 0;tokens[start].ident_last = i - 1;if(interpolations.length)tokens[start].interpolations = interpolations;return i - start;}function _checkIdent(i){if(tokens[i].type === TokenType.HyphenMinus || tokens[i].type === TokenType.Identifier || tokens[i].type === TokenType.DollarSign || tokens[i].type === TokenType.LowLine || tokens[i].type === TokenType.DecimalNumber || tokens[i].type === TokenType.Asterisk)return 1;return 0;} /**
+	 * Check if token is part of an identifier starting with `_`
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the identifier
+	 */function checkIdentLowLine(i){var start=i;if(i++ >= tokensLength)return 0;for(;i < tokensLength;i++) {if(tokens[i].type !== TokenType.HyphenMinus && tokens[i].type !== TokenType.DecimalNumber && tokens[i].type !== TokenType.LowLine && tokens[i].type !== TokenType.Identifier)break;} // Save index number of the last token of the identifier:
+	tokens[start].ident_last = i - 1;return i - start;} /**
+	 * Get node with an identifier
+	 * @returns {Array} `['ident', x]` where `x` is identifier's name
+	 */function getIdent(){var startPos=pos;var x=joinValues(pos,tokens[pos].ident_last);pos = tokens[pos].ident_last + 1;var token=tokens[startPos];return newNode(NodeType.IdentType,x,token.ln,token.col);}function checkIdentOrInterpolation(i){var start=i;var l=undefined;while(i < tokensLength) {if(l = checkInterpolation(i) || checkIdent(i))i += l;else break;}return i - start;}function getIdentOrInterpolation(){var x=[];while(pos < tokensLength) {if(checkInterpolation(pos))x.push(getInterpolation());else if(checkIdent(pos))x.push(getIdent());else break;}return x;} /**
+	 * Check if token is part of `!important` word
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkImportant(i){var start=i;var l=undefined;if(i >= tokensLength || tokens[i++].type !== TokenType.ExclamationMark)return 0;if(l = checkSC(i))i += l;if(tokens[i].value === 'important'){tokens[start].importantEnd = i;return i - start + 1;}else {return 0;}} /**
+	 * Get node with `!important` word
+	 * @returns {Array} `['important', sc]` where `sc` is optional whitespace
+	 */function getImportant(){var token=tokens[pos];var line=token.ln;var column=token.col;var content=joinValues(pos,token.importantEnd);pos = token.importantEnd + 1;return newNode(NodeType.ImportantType,content,line,column);} /**
+	 * Check if token is part of an included mixin (`@include` or `@extend`
+	 *      directive).
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the included mixin
+	 */function checkInclude(i){var l;if(i >= tokensLength)return 0;if(l = checkInclude1(i))tokens[i].include_type = 1;else if(l = checkInclude2(i))tokens[i].include_type = 2;else if(l = checkInclude3(i))tokens[i].include_type = 3;else if(l = checkInclude4(i))tokens[i].include_type = 4;else if(l = checkInclude5(i))tokens[i].include_type = 5;return l;} /**
+	 * Check if token is part of `!global` word
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkGlobal(i){var start=i;var l=undefined;if(i >= tokensLength || tokens[i++].type !== TokenType.ExclamationMark)return 0;if(l = checkSC(i))i += l;if(tokens[i].value === 'global'){tokens[start].globalEnd = i;return i - start + 1;}else {return 0;}} /**
+	 * Get node with `!global` word
+	 */function getGlobal(){var token=tokens[pos];var line=token.ln;var column=token.col;var content=joinValues(pos,token.globalEnd);pos = token.globalEnd + 1;return newNode(NodeType.GlobalType,content,line,column);} /**
+	 * Get node with included mixin
+	 * @returns {Array} `['include', x]`
+	 */function getInclude(){switch(tokens[pos].include_type){case 1:return getInclude1();case 2:return getInclude2();case 3:return getInclude3();case 4:return getInclude4();case 5:return getInclude5();}} /**
+	 * Get node with included mixin with keyfames selector like
+	 * `@include nani(foo) { 0% {}}`
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the include
+	 */function checkInclude1(i){var start=i;var l=undefined;if(l = checkAtkeyword(i))i += l;else return 0;if(tokens[start + 1].value !== 'include')return 0;if(l = checkSC(i))i += l;else return 0;if(l = checkIdentOrInterpolation(i))i += l;else return 0;if(l = checkSC(i))i += l;if(l = checkArguments(i))i += l;else return 0;if(l = checkSC(i))i += l;if(l = checkKeyframesBlocks(i))i += l;else return 0;return i - start;} /**
+	 * Get node with included mixin with keyfames selector like
+	 * `@include nani(foo) { 0% {}}`
+	 * @returns {Array} `['include', ['atkeyword', x], sc, ['selector', y], sc,
+	 *      ['arguments', z], sc, ['block', q], sc` where `x` is `include` or
+	 *      `extend`, `y` is mixin's identifier (selector), `z` are arguments
+	 *      passed to the mixin, `q` is block passed to the mixin containing a
+	 *      ruleset > selector > keyframesSelector, and `sc` are optional
+	 *      whitespaces
+	 */function getInclude1(){var startPos=pos;var x=[].concat(getAtkeyword(),getSC(),getIdentOrInterpolation(),getSC(),getArguments(),getSC(),getKeyframesBlocks());var token=tokens[startPos];return newNode(NodeType.IncludeType,x,token.ln,token.col);} /**
+	 * Check if token is part of an included mixin like `@include nani(foo) {...}`
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the include
+	 */function checkInclude2(i){var start=i;var l=undefined;if(l = checkAtkeyword(i))i += l;else return 0;if(tokens[start + 1].value !== 'include')return 0;if(l = checkSC(i))i += l;else return 0;if(l = checkIdentOrInterpolation(i))i += l;else return 0;if(l = checkSC(i))i += l;if(l = checkArguments(i))i += l;else return 0;if(l = checkSC(i))i += l;if(l = checkBlock(i))i += l;else return 0;return i - start;} /**
+	 * Get node with included mixin like `@include nani(foo) {...}`
+	 * @returns {Array} `['include', ['atkeyword', x], sc, ['selector', y], sc,
+	 *      ['arguments', z], sc, ['block', q], sc` where `x` is `include` or
+	 *      `extend`, `y` is mixin's identifier (selector), `z` are arguments
+	 *      passed to the mixin, `q` is block passed to the mixin and `sc`
+	 *      are optional whitespaces
+	 */function getInclude2(){var startPos=pos;var x=[].concat(getAtkeyword(),getSC(),getIdentOrInterpolation(),getSC(),getArguments(),getSC(),getBlock());var token=tokens[startPos];return newNode(NodeType.IncludeType,x,token.ln,token.col);} /**
+	 * Check if token is part of an included mixin like `@include nani(foo)`
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the include
+	 */function checkInclude3(i){var start=i;var l=undefined;if(l = checkAtkeyword(i))i += l;else return 0;if(tokens[start + 1].value !== 'include')return 0;if(l = checkSC(i))i += l;else return 0;if(l = checkIdentOrInterpolation(i))i += l;else return 0;if(l = checkSC(i))i += l;if(l = checkArguments(i))i += l;else return 0;return i - start;} /**
+	 * Get node with included mixin like `@include nani(foo)`
+	 * @returns {Array} `['include', ['atkeyword', x], sc, ['selector', y], sc,
+	 *      ['arguments', z], sc]` where `x` is `include` or `extend`, `y` is
+	 *      mixin's identifier (selector), `z` are arguments passed to the
+	 *      mixin and `sc` are optional whitespaces
+	 */function getInclude3(){var startPos=pos;var x=[].concat(getAtkeyword(),getSC(),getIdentOrInterpolation(),getSC(),getArguments());var token=tokens[startPos];return newNode(NodeType.IncludeType,x,token.ln,token.col);} /**
+	 * Check if token is part of an included mixin with a content block passed
+	 *      as an argument (e.g. `@include nani {...}`)
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the mixin
+	 */function checkInclude4(i){var start=i;var l=undefined;if(l = checkAtkeyword(i))i += l;else return 0;if(tokens[start + 1].value !== 'include')return 0;if(l = checkSC(i))i += l;else return 0;if(l = checkIdentOrInterpolation(i))i += l;else return 0;if(l = checkSC(i))i += l;if(l = checkBlock(i))i += l;else return 0;return i - start;} /**
+	 * Get node with an included mixin with a content block passed
+	 *      as an argument (e.g. `@include nani {...}`)
+	 * @returns {Array} `['include', x]`
+	 */function getInclude4(){var startPos=pos;var x=[].concat(getAtkeyword(),getSC(),getIdentOrInterpolation(),getSC(),getBlock());var token=tokens[startPos];return newNode(NodeType.IncludeType,x,token.ln,token.col);} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkInclude5(i){var start=i;var l=undefined;if(l = checkAtkeyword(i))i += l;else return 0;if(tokens[start + 1].value !== 'include')return 0;if(l = checkSC(i))i += l;else return 0;if(l = checkIdentOrInterpolation(i))i += l;else return 0;return i - start;} /**
+	 * @returns {Array} `['include', x]`
+	 */function getInclude5(){var startPos=pos;var x=[].concat(getAtkeyword(),getSC(),getIdentOrInterpolation());var token=tokens[startPos];return newNode(NodeType.IncludeType,x,token.ln,token.col);} /**
+	 * Check if token is part of an interpolated variable (e.g. `#{$nani}`).
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkInterpolation(i){var start=i;var l=undefined;if(i >= tokensLength)return 0;if(tokens[i].type !== TokenType.NumberSign || !tokens[i + 1] || tokens[i + 1].type !== TokenType.LeftCurlyBracket)return 0;i += 2;while(tokens[i].type !== TokenType.RightCurlyBracket) {if(l = checkArgument(i))i += l;else return 0;}return tokens[i].type === TokenType.RightCurlyBracket?i - start + 1:0;} /**
+	 * Get node with an interpolated variable
+	 * @returns {Array} `['interpolation', x]`
+	 */function getInterpolation(){var startPos=pos;var x=[];var token=tokens[startPos];var line=token.ln;var column=token.col; // Skip `#{`:
+	pos += 2;while(pos < tokensLength && tokens[pos].type !== TokenType.RightCurlyBracket) {var body=getArgument();if(typeof body.content === 'string')x.push(body);else x = x.concat(body);}var end=getLastPosition(x,line,column,1); // Skip `}`:
+	pos++;return newNode(NodeType.InterpolationType,x,token.ln,token.col,end);}function checkKeyframesBlock(i){var start=i;var l=undefined;if(i >= tokensLength)return 0;if(l = checkKeyframesSelector(i))i += l;else return 0;if(l = checkSC(i))i += l;if(l = checkBlock(i))i += l;else return 0;return i - start;}function getKeyframesBlock(){var type=NodeType.RulesetType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[].concat([getKeyframesSelector()],getSC(),[getBlock()]);return newNode(type,content,line,column);}function checkKeyframesBlocks(i){var start=i;var l=undefined;if(i < tokensLength && tokens[i].type === TokenType.LeftCurlyBracket)i++;else return 0;if(l = checkSC(i))i += l;if(l = checkKeyframesBlock(i))i += l;else return 0;while(tokens[i].type !== TokenType.RightCurlyBracket) {if(l = checkSC(i))i += l;else if(l = checkKeyframesBlock(i))i += l;else break;}if(i < tokensLength && tokens[i].type === TokenType.RightCurlyBracket)i++;else return 0;return i - start;}function getKeyframesBlocks(){var type=NodeType.BlockType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];var keyframesBlocksEnd=token.right; // Skip `{`.
+	pos++;while(pos < keyframesBlocksEnd) {if(checkSC(pos))content = content.concat(getSC());else if(checkKeyframesBlock(pos))content.push(getKeyframesBlock());}var end=getLastPosition(content,line,column,1); // Skip `}`.
+	pos++;return newNode(type,content,line,column,end);} /**
+	 * Check if token is part of a @keyframes rule.
+	 * @param {Number} i Token's index number
+	 * @return {Number} Length of the @keyframes rule
+	 */function checkKeyframesRule(i){var start=i;var l=undefined;if(i >= tokensLength)return 0;if(l = checkAtkeyword(i))i += l;else return 0;var atruleName=joinValues2(i - l,l);if(atruleName.indexOf('keyframes') === -1)return 0;if(l = checkSC(i))i += l;else return 0;if(l = checkIdentOrInterpolation(i))i += l;else return 0;if(l = checkSC(i))i += l;if(l = checkKeyframesBlocks(i))i += l;else return 0;return i - start;} /**
+	 * @return {Node}
+	 */function getKeyframesRule(){var type=NodeType.AtruleType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[].concat([getAtkeyword()],getSC(),getIdentOrInterpolation(),getSC(),[getKeyframesBlocks()]);return newNode(type,content,line,column);}function checkKeyframesSelector(i){var start=i;var l=undefined;if(i >= tokensLength)return 0;if(l = checkIdent(i)){ // Valid selectors are only `from` and `to`.
+	var selector=joinValues2(i,l);if(selector !== 'from' && selector !== 'to')return 0;i += l;tokens[start].keyframesSelectorType = 1;}else if(l = checkPercentage(i)){i += l;tokens[start].keyframesSelectorType = 2;}else if(l = checkInterpolation(i)){i += l;tokens[start].keyframesSelectorType = 3;}else {return 0;}return i - start;}function getKeyframesSelector(){var keyframesSelectorType=NodeType.KeyframesSelectorType;var selectorType=NodeType.SelectorType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];if(token.keyframesSelectorType === 1){content.push(getIdent());}else if(token.keyframesSelectorType === 2){content.push(getPercentage());}else {content.push(getInterpolation());}var keyframesSelector=newNode(keyframesSelectorType,content,line,column);return newNode(selectorType,[keyframesSelector],line,column);} /**
+	 * Check if token is part of a loop.
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the loop
+	 */function checkLoop(i){var start=i;var l=undefined;if(i >= tokensLength)return 0;if(l = checkAtkeyword(i))i += l;else return 0;if(['for','each','while'].indexOf(tokens[start + 1].value) < 0)return 0;while(i < tokensLength) {if(l = checkBlock(i)){i += l;break;}else if(l = checkVariable(i) || checkNumber(i) || checkInterpolation(i) || checkIdent(i) || checkSC(i) || checkOperator(i) || checkCombinator(i) || checkString(i))i += l;else return 0;}return i - start;} /**
+	 * Get node with a loop.
+	 * @returns {Array} `['loop', x]`
+	 */function getLoop(){var startPos=pos;var x=[];x.push(getAtkeyword());while(pos < tokensLength) {if(checkBlock(pos)){x.push(getBlock());break;}else if(checkVariable(pos))x.push(getVariable());else if(checkNumber(pos))x.push(getNumber());else if(checkInterpolation(pos))x.push(getInterpolation());else if(checkIdent(pos))x.push(getIdent());else if(checkOperator(pos))x.push(getOperator());else if(checkCombinator(pos))x.push(getCombinator());else if(checkSC(pos))x = x.concat(getSC());else if(checkString(pos))x.push(getString());}var token=tokens[startPos];return newNode(NodeType.LoopType,x,token.ln,token.col);} /**
+	 * Check if token is part of a mixin
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the mixin
+	 */function checkMixin(i){var start=i;var l=undefined;if(i >= tokensLength)return 0;if((l = checkAtkeyword(i)) && tokens[i + 1].value === 'mixin')i += l;else return 0;if(l = checkSC(i))i += l;if(l = checkIdentOrInterpolation(i))i += l;else return 0;if(l = checkSC(i))i += l;if(l = checkArguments(i))i += l;if(l = checkSC(i))i += l;if(l = checkBlock(i))i += l;else return 0;return i - start;} /**
+	 * Get node with a mixin
+	 * @returns {Array} `['mixin', x]`
+	 */function getMixin(){var startPos=pos;var x=[getAtkeyword()];x = x.concat(getSC());if(checkIdentOrInterpolation(pos))x = x.concat(getIdentOrInterpolation());x = x.concat(getSC());if(checkArguments(pos))x.push(getArguments());x = x.concat(getSC());if(checkBlock(pos))x.push(getBlock());var token=tokens[startPos];return newNode(NodeType.MixinType,x,token.ln,token.col);} /**
+	 * Check if token is a namespace sign (`|`)
+	 * @param {Number} i Token's index number
+	 * @returns {Number} `1` if token is `|`, `0` if not
+	 */function checkNamespace(i){return i < tokensLength && tokens[i].type === TokenType.VerticalLine?1:0;} /**
+	 * Get node with a namespace sign
+	 * @returns {Array} `['namespace']`
+	 */function getNamespace(){var startPos=pos;pos++;var token=tokens[startPos];return newNode(NodeType.NamespaceType,'|',token.ln,token.col);} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkNmName2(i){if(tokens[i].type === TokenType.Identifier)return 1;else if(tokens[i].type !== TokenType.DecimalNumber)return 0;i++;return i < tokensLength && tokens[i].type === TokenType.Identifier?2:1;} /**
+	 * @returns {String}
+	 */function getNmName2(){var s=tokens[pos].value;if(tokens[pos++].type === TokenType.DecimalNumber && pos < tokensLength && tokens[pos].type === TokenType.Identifier)s += tokens[pos++].value;return s;} /**
+	 * Check if token is part of a number
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of number
+	 */function checkNumber(i){if(i >= tokensLength)return 0;if(tokens[i].number_l)return tokens[i].number_l; // `10`:
+	if(i < tokensLength && tokens[i].type === TokenType.DecimalNumber && (!tokens[i + 1] || tokens[i + 1] && tokens[i + 1].type !== TokenType.FullStop))return tokens[i].number_l = 1,tokens[i].number_l; // `10.`:
+	if(i < tokensLength && tokens[i].type === TokenType.DecimalNumber && tokens[i + 1] && tokens[i + 1].type === TokenType.FullStop && (!tokens[i + 2] || tokens[i + 2].type !== TokenType.DecimalNumber))return tokens[i].number_l = 2,tokens[i].number_l; // `.10`:
+	if(i < tokensLength && tokens[i].type === TokenType.FullStop && tokens[i + 1].type === TokenType.DecimalNumber)return tokens[i].number_l = 2,tokens[i].number_l; // `10.10`:
+	if(i < tokensLength && tokens[i].type === TokenType.DecimalNumber && tokens[i + 1] && tokens[i + 1].type === TokenType.FullStop && tokens[i + 2] && tokens[i + 2].type === TokenType.DecimalNumber)return tokens[i].number_l = 3,tokens[i].number_l;return 0;} /**
+	 * Get node with number
+	 * @returns {Array} `['number', x]` where `x` is a number converted
+	 *      to string.
+	 */function getNumber(){var s='';var startPos=pos;var l=tokens[pos].number_l;for(var j=0;j < l;j++) {s += tokens[pos + j].value;}pos += l;var token=tokens[startPos];return newNode(NodeType.NumberType,s,token.ln,token.col);} /**
+	 * Check if token is an operator (`/`, `%`, `,`, `:` or `=`).
+	 * @param {Number} i Token's index number
+	 * @returns {Number} `1` if token is an operator, otherwise `0`
+	 */function checkOperator(i){if(i >= tokensLength)return 0;switch(tokens[i].type){case TokenType.Solidus:case TokenType.PercentSign:case TokenType.Comma:case TokenType.Colon:case TokenType.EqualsSign:case TokenType.EqualitySign:case TokenType.InequalitySign:case TokenType.LessThanSign:case TokenType.GreaterThanSign:case TokenType.Asterisk:return 1;}return 0;} /**
+	 * Get node with an operator
+	 * @returns {Array} `['operator', x]` where `x` is an operator converted
+	 *      to string.
+	 */function getOperator(){var startPos=pos;var x=tokens[pos++].value;var token=tokens[startPos];return newNode(NodeType.OperatorType,x,token.ln,token.col);} /**
+	 * Check if token is part of `!optional` word
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkOptional(i){var start=i;var l=undefined;if(i >= tokensLength || tokens[i++].type !== TokenType.ExclamationMark)return 0;if(l = checkSC(i))i += l;if(tokens[i].value === 'optional'){tokens[start].optionalEnd = i;return i - start + 1;}else {return 0;}} /**
+	 * Get node with `!optional` word
+	 */function getOptional(){var token=tokens[pos];var line=token.ln;var column=token.col;var content=joinValues(pos,token.optionalEnd);pos = token.optionalEnd + 1;return newNode(NodeType.OptionalType,content,line,column);} /**
+	 * Check if token is part of text inside parentheses, e.g. `(1)`
+	 * @param {Number} i Token's index number
+	 * @return {Number}
+	 */function checkParentheses(i){if(i >= tokensLength || tokens[i].type !== TokenType.LeftParenthesis)return 0;return tokens[i].right - i + 1;} /**
+	 * Get node with text inside parentheses, e.g. `(1)`
+	 * @return {Node}
+	 */function getParentheses(){var type=NodeType.ParenthesesType;var token=tokens[pos];var line=token.ln;var column=token.col;pos++;var tsets=getTsets();var end=getLastPosition(tsets,line,column,1);pos++;return newNode(type,tsets,line,column,end);} /**
+	 * Check if token is a parent selector (`&`).
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkParentSelector(i){return i < tokensLength && tokens[i].type === TokenType.Ampersand?1:0;} /**
+	 * Get node with a parent selector
+	 */function getParentSelector(){var startPos=pos;pos++;var token=tokens[startPos];return newNode(NodeType.ParentSelectorType,'&',token.ln,token.col);}function checkParentSelectorExtension(i){if(i >= tokensLength)return 0;var start=i;var l=undefined;while(i < tokensLength) {if(l = checkNumber(i) || checkIdentOrInterpolation(i))i += l;else break;}return i - start;}function getParentSelectorExtension(){var type=NodeType.ParentSelectorExtensionType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];while(pos < tokensLength) {if(checkNumber(pos))content.push(getNumber());else if(checkIdentOrInterpolation(pos))content = content.concat(getIdentOrInterpolation());else break;}return newNode(type,content,line,column);}function checkParentSelectorWithExtension(i){if(i >= tokensLength)return 0;var start=i;var l=undefined;if(l = checkParentSelector(i))i += l;else return 0;if(l = checkParentSelectorExtension(i))i += l;return i - start;}function getParentSelectorWithExtension(){var content=[getParentSelector()];if(checkParentSelectorExtension(pos))content.push(getParentSelectorExtension());return content;} /**
+	 * Check if token is part of a number with percent sign (e.g. `10%`)
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkPercentage(i){var x;if(i >= tokensLength)return 0;x = checkNumber(i);if(!x || i + x >= tokensLength)return 0;return tokens[i + x].type === TokenType.PercentSign?x + 1:0;} /**
+	 * Get node of number with percent sign
+	 * @returns {Array} `['percentage', ['number', x]]` where `x` is a number
+	 *      (without percent sign) converted to string.
+	 */function getPercentage(){var startPos=pos;var x=[getNumber()];var token=tokens[startPos];var line=token.ln;var column=token.col;var end=getLastPosition(x,line,column,1);pos++;return newNode(NodeType.PercentageType,x,token.ln,token.col,end);} /**
+	 * Check if token is part of a placeholder selector (e.g. `%abc`).
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the selector
+	 */function checkPlaceholder(i){var l;if(i >= tokensLength)return 0;if(tokens[i].placeholder_l)return tokens[i].placeholder_l;if(tokens[i].type === TokenType.PercentSign && (l = checkIdentOrInterpolation(i + 1))){tokens[i].placeholder_l = l + 1;return l + 1;}else return 0;} /**
+	 * Get node with a placeholder selector
+	 * @returns {Array} `['placeholder', ['ident', x]]` where x is a placeholder's
+	 *      identifier (without `%`, e.g. `abc`).
+	 */function getPlaceholder(){var startPos=pos;pos++;var x=getIdentOrInterpolation();var token=tokens[startPos];return newNode(NodeType.PlaceholderType,x,token.ln,token.col);} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkProgid(i){var start=i;var l=undefined;if(i >= tokensLength)return 0;if(joinValues2(i,6) === 'progid:DXImageTransform.Microsoft.')i += 6;else return 0;if(l = checkIdentOrInterpolation(i))i += l;else return 0;if(l = checkSC(i))i += l;if(tokens[i].type === TokenType.LeftParenthesis){tokens[start].progid_end = tokens[i].right;i = tokens[i].right + 1;}else return 0;return i - start;} /**
+	 * @returns {Array}
+	 */function getProgid(){var startPos=pos;var progid_end=tokens[pos].progid_end;var x=joinValues(pos,progid_end);pos = progid_end + 1;var token=tokens[startPos];return newNode(NodeType.ProgidType,x,token.ln,token.col);} /**
+	 * Check if token is part of a property
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the property
+	 */function checkProperty(i){var start=i;var l=undefined;if(i >= tokensLength)return 0;if(l = checkVariable(i) || checkIdentOrInterpolation(i))i += l;else return 0;return i - start;} /**
+	 * Get node with a property
+	 * @returns {Array} `['property', x]`
+	 */function getProperty(){var startPos=pos;var x=[];if(checkVariable(pos)){x.push(getVariable());}else {x = x.concat(getIdentOrInterpolation());}var token=tokens[startPos];return newNode(NodeType.PropertyType,x,token.ln,token.col);} /**
+	 * Check if token is a colon
+	 * @param {Number} i Token's index number
+	 * @returns {Number} `1` if token is a colon, otherwise `0`
+	 */function checkPropertyDelim(i){return i < tokensLength && tokens[i].type === TokenType.Colon?1:0;} /**
+	 * Get node with a colon
+	 * @returns {Array} `['propertyDelim']`
+	 */function getPropertyDelim(){var startPos=pos;pos++;var token=tokens[startPos];return newNode(NodeType.PropertyDelimType,':',token.ln,token.col);} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkPseudo(i){return checkPseudoe(i) || checkPseudoc(i);} /**
+	 * @returns {Array}
+	 */function getPseudo(){if(checkPseudoe(pos))return getPseudoe();if(checkPseudoc(pos))return getPseudoc();} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkPseudoe(i){var l;if(i >= tokensLength || tokens[i++].type !== TokenType.Colon || i >= tokensLength || tokens[i++].type !== TokenType.Colon)return 0;return (l = checkIdentOrInterpolation(i))?l + 2:0;} /**
+	 * @returns {Array}
+	 */function getPseudoe(){var startPos=pos;pos += 2;var x=getIdentOrInterpolation();var token=tokens[startPos];return newNode(NodeType.PseudoeType,x,token.ln,token.col);} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkPseudoc(i){var l;if(i >= tokensLength || tokens[i].type !== TokenType.Colon)return 0;if(l = checkPseudoClass3(i))tokens[i].pseudoClassType = 3;else if(l = checkPseudoClass4(i))tokens[i].pseudoClassType = 4;else if(l = checkPseudoClass5(i))tokens[i].pseudoClassType = 5;else if(l = checkPseudoClass1(i))tokens[i].pseudoClassType = 1;else if(l = checkPseudoClass2(i))tokens[i].pseudoClassType = 2;else if(l = checkPseudoClass6(i))tokens[i].pseudoClassType = 6;else return 0;return l;} /**
+	 * @returns {Array}
+	 */function getPseudoc(){var childType=tokens[pos].pseudoClassType;if(childType === 1)return getPseudoClass1();if(childType === 2)return getPseudoClass2();if(childType === 3)return getPseudoClass3();if(childType === 4)return getPseudoClass4();if(childType === 5)return getPseudoClass5();if(childType === 6)return getPseudoClass6();} /**
+	 * (-) `:not(panda)`
+	 */function checkPseudoClass1(i){var start=i; // Skip `:`.
+	i++;if(i >= tokensLength)return 0;var l=undefined;if(l = checkIdentOrInterpolation(i))i += l;else return 0;if(i >= tokensLength || tokens[i].type !== TokenType.LeftParenthesis)return 0;var right=tokens[i].right; // Skip `(`.
+	i++;if(l = checkSelectorsGroup(i))i += l;else return 0;if(i !== right)return 0;return right - start + 1;} /**
+	 * (-) `:not(panda)`
+	 */function getPseudoClass1(){var type=NodeType.PseudocType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[]; // Skip `:`.
+	pos++;content = content.concat(getIdentOrInterpolation());{var _type=NodeType.ArgumentsType;var _token=tokens[pos];var _line=_token.ln;var _column=_token.col; // Skip `(`.
+	pos++;var selectors=getSelectorsGroup();var end=getLastPosition(selectors,_line,_column,1);var args=newNode(_type,selectors,_line,_column,end);content.push(args); // Skip `)`.
+	pos++;}return newNode(type,content,line,column);} /**
+	 * (1) `:nth-child(odd)`
+	 * (2) `:nth-child(even)`
+	 * (3) `:lang(de-DE)`
+	 */function checkPseudoClass2(i){var start=i;var l=0; // Skip `:`.
+	i++;if(i >= tokensLength)return 0;if(l = checkIdentOrInterpolation(i))i += l;else return 0;if(i >= tokensLength || tokens[i].type !== TokenType.LeftParenthesis)return 0;var right=tokens[i].right; // Skip `(`.
+	i++;if(l = checkSC(i))i += l;if(l = checkIdentOrInterpolation(i))i += l;else return 0;if(l = checkSC(i))i += l;if(i !== right)return 0;return i - start + 1;}function getPseudoClass2(){var type=NodeType.PseudocType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[]; // Skip `:`.
+	pos++;content = content.concat(getIdentOrInterpolation());var l=tokens[pos].ln;var c=tokens[pos].col;var value=[]; // Skip `(`.
+	pos++;value = value.concat(getSC()).concat(getIdentOrInterpolation()).concat(getSC());var end=getLastPosition(value,l,c,1);var args=newNode(NodeType.ArgumentsType,value,l,c,end);content.push(args); // Skip `)`.
+	pos++;return newNode(type,content,line,column);} /**
+	 * (-) `:nth-child(-3n + 2)`
+	 */function checkPseudoClass3(i){var start=i;var l=0; // Skip `:`.
+	i++;if(i >= tokensLength)return 0;if(l = checkIdentOrInterpolation(i))i += l;else return 0;if(i >= tokensLength || tokens[i].type !== TokenType.LeftParenthesis)return 0;var right=tokens[i].right; // Skip `(`.
+	i++;if(l = checkSC(i))i += l;if(l = checkUnary(i))i += l;if(i >= tokensLength)return 0;if(tokens[i].type === TokenType.DecimalNumber)i++;if(i >= tokensLength)return 0;if(tokens[i].value === 'n')i++;else return 0;if(l = checkSC(i))i += l;if(i >= tokensLength)return 0;if(tokens[i].value === '+' || tokens[i].value === '-')i++;else return 0;if(l = checkSC(i))i += l;if(tokens[i].type === TokenType.DecimalNumber)i++;else return 0;if(l = checkSC(i))i += l;if(i !== right)return 0;return i - start + 1;}function getPseudoClass3(){var type=NodeType.PseudocType;var token=tokens[pos];var line=token.ln;var column=token.col; // Skip `:`.
+	pos++;var content=getIdentOrInterpolation();var l=tokens[pos].ln;var c=tokens[pos].col;var value=[]; // Skip `(`.
+	pos++;if(checkUnary(pos))value.push(getUnary());if(checkNumber(pos))value.push(getNumber());{var _l=tokens[pos].ln;var _c=tokens[pos].col;var _content=tokens[pos].value;var ident=newNode(NodeType.IdentType,_content,_l,_c);value.push(ident);pos++;}value = value.concat(getSC());if(checkUnary(pos))value.push(getUnary());value = value.concat(getSC());if(checkNumber(pos))value.push(getNumber());value = value.concat(getSC());var end=getLastPosition(value,l,c,1);var args=newNode(NodeType.ArgumentsType,value,l,c,end);content.push(args); // Skip `)`.
+	pos++;return newNode(type,content,line,column);} /**
+	 * (-) `:nth-child(-3n)`
+	 */function checkPseudoClass4(i){var start=i;var l=0; // Skip `:`.
+	i++;if(i >= tokensLength)return 0;if(l = checkIdentOrInterpolation(i))i += l;else return 0;if(i >= tokensLength)return 0;if(tokens[i].type !== TokenType.LeftParenthesis)return 0;var right=tokens[i].right; // Skip `(`.
+	i++;if(l = checkSC(i))i += l;if(l = checkUnary(i))i += l;if(tokens[i].type === TokenType.DecimalNumber)i++;if(tokens[i].value === 'n')i++;else return 0;if(l = checkSC(i))i += l;if(i !== right)return 0;return i - start + 1;}function getPseudoClass4(){var type=NodeType.PseudocType;var token=tokens[pos];var line=token.ln;var column=token.col; // Skip `:`.
+	pos++;var content=getIdentOrInterpolation();var l=tokens[pos].ln;var c=tokens[pos].col;var value=[]; // Skip `(`.
+	pos++;value = value.concat(getSC());if(checkUnary(pos))value.push(getUnary());if(checkNumber(pos))value.push(getNumber());if(checkIdent(pos))value.push(getIdent());value = value.concat(getSC());var end=getLastPosition(value,l,c,1);var args=newNode(NodeType.ArgumentsType,value,l,c,end);content.push(args); // Skip `)`.
+	pos++;return newNode(type,content,line,column);} /**
+	 * (-) `:nth-child(+8)`
+	 */function checkPseudoClass5(i){var start=i;var l=0; // Skip `:`.
+	i++;if(i >= tokensLength)return 0;if(l = checkIdentOrInterpolation(i))i += l;else return 0;if(i >= tokensLength)return 0;if(tokens[i].type !== TokenType.LeftParenthesis)return 0;var right=tokens[i].right; // Skip `(`.
+	i++;if(l = checkSC(i))i += l;if(l = checkUnary(i))i += l;if(tokens[i].type === TokenType.DecimalNumber)i++;else return 0;if(l = checkSC(i))i += l;if(i !== right)return 0;return i - start + 1;}function getPseudoClass5(){var type=NodeType.PseudocType;var token=tokens[pos];var line=token.ln;var column=token.col; // Skip `:`.
+	pos++;var content=getIdentOrInterpolation();var l=tokens[pos].ln;var c=tokens[pos].col;var value=[]; // Skip `(`.
+	pos++;if(checkUnary(pos))value.push(getUnary());if(checkNumber(pos))value.push(getNumber());value = value.concat(getSC());var end=getLastPosition(value,l,c,1);var args=newNode(NodeType.ArgumentsType,value,l,c,end);content.push(args); // Skip `)`.
+	pos++;return newNode(type,content,line,column);} /**
+	 * (-) `:checked`
+	 */function checkPseudoClass6(i){var start=i;var l=0; // Skip `:`.
+	i++;if(i >= tokensLength)return 0;if(l = checkIdentOrInterpolation(i))i += l;else return 0;return i - start;}function getPseudoClass6(){var type=NodeType.PseudocType;var token=tokens[pos];var line=token.ln;var column=token.col; // Skip `:`.
+	pos++;var content=getIdentOrInterpolation();return newNode(type,content,line,column);} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkRuleset(i){var start=i;var l=undefined;if(i >= tokensLength)return 0;if(l = checkSelectorsGroup(i))i += l;else return 0;if(l = checkSC(i))i += l;if(l = checkBlock(i))i += l;else return 0;return i - start;}function getRuleset(){var type=NodeType.RulesetType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];content = content.concat(getSelectorsGroup());content = content.concat(getSC());content.push(getBlock());return newNode(type,content,line,column);} /**
+	 * Check if token is marked as a space (if it's a space or a tab
+	 *      or a line break).
+	 * @param {Number} i
+	 * @returns {Number} Number of spaces in a row starting with the given token.
+	 */function checkS(i){return i < tokensLength && tokens[i].ws?tokens[i].ws_last - i + 1:0;} /**
+	 * Get node with spaces
+	 * @returns {Array} `['s', x]` where `x` is a string containing spaces
+	 */function getS(){var startPos=pos;var x=joinValues(pos,tokens[pos].ws_last);pos = tokens[pos].ws_last + 1;var token=tokens[startPos];return newNode(NodeType.SType,x,token.ln,token.col);} /**
+	 * Check if token is a space or a comment.
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Number of similar (space or comment) tokens
+	 *      in a row starting with the given token.
+	 */function checkSC(i){if(i >= tokensLength)return 0;var l=undefined;var lsc=0;while(i < tokensLength) {if(!(l = checkS(i)) && !(l = checkCommentML(i)) && !(l = checkCommentSL(i)))break;i += l;lsc += l;}return lsc || 0;} /**
+	 * Get node with spaces and comments
+	 * @returns {Array} Array containing nodes with spaces (if there are any)
+	 *      and nodes with comments (if there are any):
+	 *      `[['s', x]*, ['comment', y]*]` where `x` is a string of spaces
+	 *      and `y` is a comment's text (without `/*` and `* /`).
+	 */function getSC(){var sc=[];if(pos >= tokensLength)return sc;while(pos < tokensLength) {if(checkS(pos))sc.push(getS());else if(checkCommentML(pos))sc.push(getCommentML());else if(checkCommentSL(pos))sc.push(getCommentSL());else break;}return sc;} /**
+	 * Check if token is part of a hexadecimal number (e.g. `#fff`) inside
+	 *      a simple selector
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkShash(i){var l;if(i >= tokensLength || tokens[i].type !== TokenType.NumberSign)return 0;return (l = checkIdentOrInterpolation(i + 1))?l + 1:0;} /**
+	 * Get node with a hexadecimal number (e.g. `#fff`) inside a simple
+	 *      selector
+	 * @returns {Array} `['shash', x]` where `x` is a hexadecimal number
+	 *      converted to string (without `#`, e.g. `fff`)
+	 */function getShash(){var startPos=pos;var token=tokens[startPos];pos++;var x=getIdentOrInterpolation();return newNode(NodeType.ShashType,x,token.ln,token.col);} /**
+	 * Check if token is part of a string (text wrapped in quotes)
+	 * @param {Number} i Token's index number
+	 * @returns {Number} `1` if token is part of a string, `0` if not
+	 */function checkString(i){return i < tokensLength && (tokens[i].type === TokenType.StringSQ || tokens[i].type === TokenType.StringDQ)?1:0;} /**
+	 * Get string's node
+	 * @returns {Array} `['string', x]` where `x` is a string (including
+	 *      quotes).
+	 */function getString(){var startPos=pos;var x=tokens[pos++].value;var token=tokens[startPos];return newNode(NodeType.StringType,x,token.ln,token.col);} /**
+	 * Validate stylesheet: it should consist of any number (0 or more) of
+	 * rulesets (sets of rules with selectors), @-rules, whitespaces or
+	 * comments.
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkStylesheet(i){var start=i;var l=undefined;while(i < tokensLength) {if(l = checkSC(i) || checkDeclaration(i) || checkDeclDelim(i) || checkInclude(i) || checkExtend(i) || checkMixin(i) || checkLoop(i) || checkConditionalStatement(i) || checkAtrule(i) || checkRuleset(i))i += l;else throwError(i);}return i - start;} /**
+	 * @returns {Array} `['stylesheet', x]` where `x` is all stylesheet's
+	 *      nodes.
+	 */function getStylesheet(){var startPos=pos;var x=[];while(pos < tokensLength) {if(checkSC(pos))x = x.concat(getSC());else if(checkRuleset(pos))x.push(getRuleset());else if(checkInclude(pos))x.push(getInclude());else if(checkExtend(pos))x.push(getExtend());else if(checkMixin(pos))x.push(getMixin());else if(checkLoop(pos))x.push(getLoop());else if(checkConditionalStatement(pos))x.push(getConditionalStatement());else if(checkAtrule(pos))x.push(getAtrule());else if(checkDeclaration(pos))x.push(getDeclaration());else if(checkDeclDelim(pos))x.push(getDeclDelim());else throwError();}var token=tokens[startPos];return newNode(NodeType.StylesheetType,x,token.ln,token.col);} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkTset(i){return checkVhash(i) || checkOperator(i) || checkAny(i) || checkSC(i) || checkInterpolation(i);} /**
+	 * @returns {Array}
+	 */function getTset(){if(checkVhash(pos))return getVhash();else if(checkOperator(pos))return getOperator();else if(checkAny(pos))return getAny();else if(checkSC(pos))return getSC();else if(checkInterpolation(pos))return getInterpolation();} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkTsets(i){var start=i;var l=undefined;if(i >= tokensLength)return 0;while(l = checkTset(i)) {i += l;}return i - start;} /**
+	 * @returns {Array}
+	 */function getTsets(){var x=[];var t=undefined;while(t = getTset()) {if(typeof t.content === 'string')x.push(t);else x = x.concat(t);}return x;} /**
+	 * Check if token is an unary (arithmetical) sign (`+` or `-`)
+	 * @param {Number} i Token's index number
+	 * @returns {Number} `1` if token is an unary sign, `0` if not
+	 */function checkUnary(i){return i < tokensLength && (tokens[i].type === TokenType.HyphenMinus || tokens[i].type === TokenType.PlusSign)?1:0;} /**
+	 * Get node with an unary (arithmetical) sign (`+` or `-`)
+	 * @returns {Array} `['unary', x]` where `x` is an unary sign
+	 *      converted to string.
+	 */function getUnary(){var startPos=pos;var x=tokens[pos++].value;var token=tokens[startPos];return newNode(NodeType.OperatorType,x,token.ln,token.col);} /**
+	 * Check if token is part of URI (e.g. `url('/css/styles.css')`)
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of URI
+	 */function checkUri(i){var start=i;if(i >= tokensLength || tokens[i++].value !== 'url' || i >= tokensLength || tokens[i].type !== TokenType.LeftParenthesis)return 0;return tokens[i].right - start + 1;} /**
+	 * Get node with URI
+	 * @returns {Array} `['uri', x]` where `x` is URI's nodes (without `url`
+	 *      and braces, e.g. `['string', ''/css/styles.css'']`).
+	 */function getUri(){var startPos=pos;var uriExcluding={};var uri=undefined;var token=undefined;var l=undefined;var raw=undefined;pos += 2;uriExcluding[TokenType.Space] = 1;uriExcluding[TokenType.Tab] = 1;uriExcluding[TokenType.Newline] = 1;uriExcluding[TokenType.LeftParenthesis] = 1;uriExcluding[TokenType.RightParenthesis] = 1;if(checkUriContent(pos)){uri = [].concat(getSC()).concat(getUriContent()).concat(getSC());}else {uri = [].concat(getSC());l = checkExcluding(uriExcluding,pos);token = tokens[pos];raw = newNode(NodeType.RawType,joinValues(pos,pos + l),token.ln,token.col);uri.push(raw);pos += l + 1;uri = uri.concat(getSC());}token = tokens[startPos];var line=token.ln;var column=token.col;var end=getLastPosition(uri,line,column,1);pos++;return newNode(NodeType.UriType,uri,token.ln,token.col,end);} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkUriContent(i){return checkUri1(i) || checkFunction(i);} /**
+	 * @returns {Array}
+	 */function getUriContent(){if(checkUri1(pos))return getString();else if(checkFunction(pos))return getFunction();} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkUri1(i){var start=i;var l=undefined;if(i >= tokensLength)return 0;if(l = checkSC(i))i += l;if(tokens[i].type !== TokenType.StringDQ && tokens[i].type !== TokenType.StringSQ)return 0;i++;if(l = checkSC(i))i += l;return i - start;} /**
+	 * Check if token is part of a value
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the value
+	 */function checkValue(i){var start=i;var l=undefined;var s=undefined;var _i=undefined;while(i < tokensLength) {if(checkDeclDelim(i))break;s = checkSC(i);_i = i + s;if(l = _checkValue(_i))i += l + s;if(!l || checkBlock(i - l))break;}return i - start;} /**
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function _checkValue(i){return checkInterpolation(i) || checkVariable(i) || checkVhash(i) || checkBlock(i) || checkAtkeyword(i) || checkOperator(i) || checkImportant(i) || checkGlobal(i) || checkDefault(i) || checkProgid(i) || checkAny(i);} /**
+	 * @returns {Array}
+	 */function getValue(){var startPos=pos;var x=[];var _pos=undefined;var s=undefined;while(pos < tokensLength) {s = checkSC(pos);_pos = pos + s;if(checkDeclDelim(_pos))break;if(!_checkValue(_pos))break;if(s)x = x.concat(getSC());x.push(_getValue());if(checkBlock(_pos))break;}var token=tokens[startPos];return newNode(NodeType.ValueType,x,token.ln,token.col);} /**
+	 * @returns {Array}
+	 */function _getValue(){if(checkInterpolation(pos))return getInterpolation();else if(checkVariable(pos))return getVariable();else if(checkVhash(pos))return getVhash();else if(checkBlock(pos))return getBlock();else if(checkAtkeyword(pos))return getAtkeyword();else if(checkOperator(pos))return getOperator();else if(checkImportant(pos))return getImportant();else if(checkGlobal(pos))return getGlobal();else if(checkDefault(pos))return getDefault();else if(checkProgid(pos))return getProgid();else if(checkAny(pos))return getAny();} /**
+	 * Check if token is part of a variable
+	 * @param {Number} i Token's index number
+	 * @returns {Number} Length of the variable
+	 */function checkVariable(i){var l;if(i >= tokensLength || tokens[i].type !== TokenType.DollarSign)return 0;return (l = checkIdent(i + 1))?l + 1:0;} /**
+	 * Get node with a variable
+	 * @returns {Array} `['variable', ['ident', x]]` where `x` is
+	 *      a variable name.
+	 */function getVariable(){var startPos=pos;var x=[];pos++;x.push(getIdent());var token=tokens[startPos];return newNode(NodeType.VariableType,x,token.ln,token.col);} /**
+	 * Check if token is part of a variables list (e.g. `$values...`).
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkVariablesList(i){var d=0; // Number of dots
+	var l=undefined;if(i >= tokensLength)return 0;if(l = checkVariable(i))i += l;else return 0;while(i < tokensLength && tokens[i].type === TokenType.FullStop) {d++;i++;}return d === 3?l + d:0;} /**
+	 * Get node with a variables list
+	 * @returns {Array} `['variableslist', ['variable', ['ident', x]]]` where
+	 *      `x` is a variable name.
+	 */function getVariablesList(){var startPos=pos;var x=getVariable();var token=tokens[startPos];var line=token.ln;var column=token.col;var end=getLastPosition([x],line,column,3);pos += 3;return newNode(NodeType.VariablesListType,[x],token.ln,token.col,end);} /**
+	 * Check if token is part of a hexadecimal number (e.g. `#fff`) inside
+	 *      some value
+	 * @param {Number} i Token's index number
+	 * @returns {Number}
+	 */function checkVhash(i){var l;if(i >= tokensLength || tokens[i].type !== TokenType.NumberSign)return 0;return (l = checkNmName2(i + 1))?l + 1:0;} /**
+	 * Get node with a hexadecimal number (e.g. `#fff`) inside some value
+	 * @returns {Array} `['vhash', x]` where `x` is a hexadecimal number
+	 *      converted to string (without `#`, e.g. `'fff'`).
+	 */function getVhash(){var startPos=pos;var x=undefined;var token=tokens[startPos];var line=token.ln;var column=token.col;pos++;x = getNmName2();var end=getLastPosition(x,line,column + 1);return newNode(NodeType.VhashType,x,token.ln,token.col,end);}module.exports = function(_tokens,context){tokens = _tokens;tokensLength = tokens.length;pos = 0;return contexts[context]();};function checkSelectorsGroup(i){if(i >= tokensLength)return 0;var start=i;var l=undefined;if(l = checkSelector(i))i += l;else return 0;while(i < tokensLength) {var sb=checkSC(i);var c=checkDelim(i + sb);if(!c)break;var sa=checkSC(i + sb + c);if(l = checkSelector(i + sb + c + sa))i += sb + c + sa + l;else break;}tokens[start].selectorsGroupEnd = i;return i - start;}function getSelectorsGroup(){var selectorsGroup=[];var selectorsGroupEnd=tokens[pos].selectorsGroupEnd;selectorsGroup.push(getSelector());while(pos < selectorsGroupEnd) {selectorsGroup = selectorsGroup.concat(getSC());selectorsGroup.push(getDelim());selectorsGroup = selectorsGroup.concat(getSC());selectorsGroup.push(getSelector());}return selectorsGroup;}function checkSelector(i){var l;if(l = checkSelector1(i))tokens[i].selectorType = 1;else if(l = checkSelector2(i))tokens[i].selectorType = 2;return l;}function getSelector(){var selectorType=tokens[pos].selectorType;if(selectorType === 1)return getSelector1();else return getSelector2();} /**
+	 * Checks for selector which starts with a compound selector.
+	 */function checkSelector1(i){if(i >= tokensLength)return 0;var start=i;var l=undefined;if(l = checkCompoundSelector(i))i += l;else return 0;while(i < tokensLength) {var s=checkSC(i);var c=checkCombinator(i + s);if(!s && !c)break;if(c){i += s + c;s = checkSC(i);}if(l = checkCompoundSelector(i + s))i += s + l;else break;}tokens[start].selectorEnd = i;return i - start;}function getSelector1(){var type=NodeType.SelectorType;var token=tokens[pos];var line=token.ln;var column=token.col;var selectorEnd=token.selectorEnd;var content=getCompoundSelector();while(pos < selectorEnd) {if(checkSC(pos))content = content.concat(getSC());else if(checkCombinator(pos))content.push(getCombinator());else if(checkCompoundSelector(pos))content = content.concat(getCompoundSelector());}return newNode(type,content,line,column);} /**
+	 * Checks for a selector that starts with a combinator.
+	 */function checkSelector2(i){if(i >= tokensLength)return 0;var start=i;var l=undefined;if(l = checkCombinator(i))i += l;else return 0;while(i < tokensLength) {var sb=checkSC(i);if(l = checkCompoundSelector(i + sb))i += sb + l;else break;var sa=checkSC(i);var c=checkCombinator(i + sa);if(!sa && !c)break;if(c){i += sa + c;}}tokens[start].selectorEnd = i;return i - start;}function getSelector2(){var type=NodeType.SelectorType;var token=tokens[pos];var line=token.ln;var column=token.col;var selectorEnd=token.selectorEnd;var content=[getCombinator()];while(pos < selectorEnd) {if(checkSC(pos))content = content.concat(getSC());else if(checkCombinator(pos))content.push(getCombinator());else if(checkCompoundSelector(pos))content = content.concat(getCompoundSelector());}return newNode(type,content,line,column);}function checkCompoundSelector(i){var l=undefined;if(l = checkCompoundSelector1(i)){tokens[i].compoundSelectorType = 1;}else if(l = checkCompoundSelector2(i)){tokens[i].compoundSelectorType = 2;}return l;}function getCompoundSelector(){var type=tokens[pos].compoundSelectorType;if(type === 1)return getCompoundSelector1();if(type === 2)return getCompoundSelector2();}function checkCompoundSelector1(i){if(i >= tokensLength)return 0;var start=i;var l=undefined;if(l = checkTypeSelector(i) || checkPlaceholder(i) || checkParentSelectorWithExtension(i))i += l;else return 0;while(i < tokensLength) {var _l2=checkShash(i) || checkClass(i) || checkAttributeSelector(i) || checkPseudo(i) || checkPlaceholder(i);if(_l2)i += _l2;else break;}tokens[start].compoundSelectorEnd = i;return i - start;}function getCompoundSelector1(){var sequence=[];var compoundSelectorEnd=tokens[pos].compoundSelectorEnd;if(checkTypeSelector(pos))sequence.push(getTypeSelector());else if(checkPlaceholder(pos))sequence.push(getPlaceholder());else if(checkParentSelectorWithExtension(pos))sequence = sequence.concat(getParentSelectorWithExtension());while(pos < compoundSelectorEnd) {if(checkShash(pos))sequence.push(getShash());else if(checkClass(pos))sequence.push(getClass());else if(checkAttributeSelector(pos))sequence.push(getAttributeSelector());else if(checkPseudo(pos))sequence.push(getPseudo());else if(checkPlaceholder(pos))sequence.push(getPlaceholder());}return sequence;}function checkCompoundSelector2(i){if(i >= tokensLength)return 0;var start=i;while(i < tokensLength) {var l=checkShash(i) || checkClass(i) || checkAttributeSelector(i) || checkPseudo(i) || checkPlaceholder(i);if(l)i += l;else break;}tokens[start].compoundSelectorEnd = i;return i - start;}function getCompoundSelector2(){var sequence=[];var compoundSelectorEnd=tokens[pos].compoundSelectorEnd;while(pos < compoundSelectorEnd) {if(checkShash(pos))sequence.push(getShash());else if(checkClass(pos))sequence.push(getClass());else if(checkAttributeSelector(pos))sequence.push(getAttributeSelector());else if(checkPseudo(pos))sequence.push(getPseudo());else if(checkPlaceholder(pos))sequence.push(getPlaceholder());}return sequence;}function checkTypeSelector(i){if(i >= tokensLength)return 0;var start=i;var l=undefined;if(l = checkNamePrefix(i))i += l;if(tokens[i].type === TokenType.Asterisk)i++;else if(l = checkIdentOrInterpolation(i))i += l;else return 0;return i - start;}function getTypeSelector(){var type=NodeType.TypeSelectorType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];if(checkNamePrefix(pos))content.push(getNamePrefix());if(checkIdentOrInterpolation(pos))content = content.concat(getIdentOrInterpolation());return newNode(type,content,line,column);}function checkAttributeSelector(i){var l=undefined;if(l = checkAttributeSelector1(i))tokens[i].attributeSelectorType = 1;else if(l = checkAttributeSelector2(i))tokens[i].attributeSelectorType = 2;return l;}function getAttributeSelector(){var type=tokens[pos].attributeSelectorType;if(type === 1)return getAttributeSelector1();else return getAttributeSelector2();} /**
+	 * (1) `[panda=nani]`
+	 * (2) `[panda='nani']`
+	 * (3) `[panda='nani' i]`
+	 *
+	 */function checkAttributeSelector1(i){var start=i;if(tokens[i].type === TokenType.LeftSquareBracket)i++;else return 0;var l=undefined;if(l = checkSC(i))i += l;if(l = checkAttributeName(i))i += l;else return 0;if(l = checkSC(i))i += l;if(l = checkAttributeMatch(i))i += l;else return 0;if(l = checkSC(i))i += l;if(l = checkAttributeValue(i))i += l;else return 0;if(l = checkSC(i))i += l;if(l = checkAttributeFlags(i)){i += l;if(l = checkSC(i))i += l;}if(tokens[i].type === TokenType.RightSquareBracket)i++;else return 0;return i - start;}function getAttributeSelector1(){var type=NodeType.AttributeSelectorType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[]; // Skip `[`.
+	pos++;content = content.concat(getSC());content.push(getAttributeName());content = content.concat(getSC());content.push(getAttributeMatch());content = content.concat(getSC());content.push(getAttributeValue());content = content.concat(getSC());if(checkAttributeFlags(pos)){content.push(getAttributeFlags());content = content.concat(getSC());} // Skip `]`.
+	pos++;var end=getLastPosition(content,line,column,1);return newNode(type,content,line,column,end);} /**
+	 * (1) `[panda]`
+	 */function checkAttributeSelector2(i){var start=i;if(tokens[i].type === TokenType.LeftSquareBracket)i++;else return 0;var l=undefined;if(l = checkSC(i))i += l;if(l = checkAttributeName(i))i += l;else return 0;if(l = checkSC(i))i += l;if(tokens[i].type === TokenType.RightSquareBracket)i++;else return 0;return i - start;}function getAttributeSelector2(){var type=NodeType.AttributeSelectorType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[]; // Skip `[`.
+	pos++;content = content.concat(getSC());content.push(getAttributeName());content = content.concat(getSC()); // Skip `]`.
+	pos++;var end=getLastPosition(content,line,column,1);return newNode(type,content,line,column,end);}function checkAttributeName(i){var start=i;var l=undefined;if(l = checkNamePrefix(i))i += l;if(l = checkIdentOrInterpolation(i))i += l;else return 0;return i - start;}function getAttributeName(){var type=NodeType.AttributeNameType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];if(checkNamePrefix(pos))content.push(getNamePrefix());content = content.concat(getIdentOrInterpolation());return newNode(type,content,line,column);}function checkAttributeMatch(i){var l=undefined;if(l = checkAttributeMatch1(i))tokens[i].attributeMatchType = 1;else if(l = checkAttributeMatch2(i))tokens[i].attributeMatchType = 2;return l;}function getAttributeMatch(){var type=tokens[pos].attributeMatchType;if(type === 1)return getAttributeMatch1();else return getAttributeMatch2();}function checkAttributeMatch1(i){var start=i;var type=tokens[i].type;if(type === TokenType.Tilde || type === TokenType.VerticalLine || type === TokenType.CircumflexAccent || type === TokenType.DollarSign || type === TokenType.Asterisk)i++;else return 0;if(tokens[i].type === TokenType.EqualsSign)i++;else return 0;return i - start;}function getAttributeMatch1(){var type=NodeType.AttributeMatchType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=tokens[pos].value + tokens[pos + 1].value;pos += 2;return newNode(type,content,line,column);}function checkAttributeMatch2(i){if(tokens[i].type === TokenType.EqualsSign)return 1;else return 0;}function getAttributeMatch2(){var type=NodeType.AttributeMatchType;var token=tokens[pos];var line=token.ln;var column=token.col;var content='=';pos++;return newNode(type,content,line,column);}function checkAttributeValue(i){return checkString(i) || checkIdentOrInterpolation(i);}function getAttributeValue(){var type=NodeType.AttributeValueType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];if(checkString(pos))content.push(getString());else content = content.concat(getIdentOrInterpolation());return newNode(type,content,line,column);}function checkAttributeFlags(i){return checkIdentOrInterpolation(i);}function getAttributeFlags(){var type=NodeType.AttributeFlagsType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=getIdentOrInterpolation();return newNode(type,content,line,column);}function checkNamePrefix(i){if(i >= tokensLength)return 0;var l=undefined;if(l = checkNamePrefix1(i))tokens[i].namePrefixType = 1;else if(l = checkNamePrefix2(i))tokens[i].namePrefixType = 2;return l;}function getNamePrefix(){var type=tokens[pos].namePrefixType;if(type === 1)return getNamePrefix1();else return getNamePrefix2();} /**
+	 * (1) `panda|`
+	 * (2) `panda<comment>|`
+	 */function checkNamePrefix1(i){var start=i;var l=undefined;if(l = checkNamespacePrefix(i))i += l;else return 0;if(l = checkCommentML(i))i += l;if(l = checkNamespaceSeparator(i))i += l;else return 0;return i - start;}function getNamePrefix1(){var type=NodeType.NamePrefixType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];content.push(getNamespacePrefix());if(checkCommentML(pos))content.push(getCommentML());content.push(getNamespaceSeparator());return newNode(type,content,line,column);} /**
+	 * (1) `|`
+	 */function checkNamePrefix2(i){return checkNamespaceSeparator(i);}function getNamePrefix2(){var type=NodeType.NamePrefixType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[getNamespaceSeparator()];return newNode(type,content,line,column);} /**
+	 * (1) `*`
+	 * (2) `panda`
+	 */function checkNamespacePrefix(i){if(i >= tokensLength)return 0;var l=undefined;if(tokens[i].type === TokenType.Asterisk)return 1;else if(l = checkIdentOrInterpolation(i))return l;else return 0;}function getNamespacePrefix(){var type=NodeType.NamespacePrefixType;var token=tokens[pos];var line=token.ln;var column=token.col;var content=[];if(checkIdentOrInterpolation(pos))content = content.concat(getIdentOrInterpolation());return newNode(type,content,line,column);} /**
+	 * (1) `|`
+	 */function checkNamespaceSeparator(i){if(i >= tokensLength)return 0;if(tokens[i].type === TokenType.VerticalLine)return 1;else return 0;}function getNamespaceSeparator(){var type=NodeType.NamespaceSeparatorType;var token=tokens[pos];var line=token.ln;var column=token.col;var content='|';pos++;return newNode(type,content,line,column);}
 
+/***/ },
+/* 14 */
+/***/ function(module, exports) {
 
-pushToken(TokenType.CommentSL,css.substring(start,pos--),col);
-col+=pos-start;
-}
+	'use strict';
 
+	module.exports = {
+	  ArgumentsType: 'arguments',
+	  AtkeywordType: 'atkeyword',
+	  AtruleType: 'atrule',
+	  AttributeSelectorType: 'attributeSelector',
+	  AttributeNameType: 'attributeName',
+	  AttributeFlagsType: 'attributeFlags',
+	  AttributeMatchType: 'attributeMatch',
+	  AttributeValueType: 'attributeValue',
+	  BlockType: 'block',
+	  BracketsType: 'brackets',
+	  ClassType: 'class',
+	  CombinatorType: 'combinator',
+	  CommentMLType: 'multilineComment',
+	  CommentSLType: 'singlelineComment',
+	  ConditionType: 'condition',
+	  ConditionalStatementType: 'conditionalStatement',
+	  DeclarationType: 'declaration',
+	  DeclDelimType: 'declarationDelimiter',
+	  DefaultType: 'default',
+	  DelimType: 'delimiter',
+	  DimensionType: 'dimension',
+	  EscapedStringType: 'escapedString',
+	  ExtendType: 'extend',
+	  ExpressionType: 'expression',
+	  FunctionType: 'function',
+	  GlobalType: 'global',
+	  IdentType: 'ident',
+	  ImportantType: 'important',
+	  IncludeType: 'include',
+	  InterpolationType: 'interpolation',
+	  InterpolatedVariableType: 'interpolatedVariable',
+	  KeyframesSelectorType: 'keyframesSelector',
+	  LoopType: 'loop',
+	  MixinType: 'mixin',
+	  NamePrefixType: 'namePrefix',
+	  NamespacePrefixType: 'namespacePrefix',
+	  NamespaceSeparatorType: 'namespaceSeparator',
+	  NumberType: 'number',
+	  OperatorType: 'operator',
+	  OptionalType: 'optional',
+	  ParenthesesType: 'parentheses',
+	  ParentSelectorType: 'parentSelector',
+	  ParentSelectorExtensionType: 'parentSelectorExtension',
+	  PercentageType: 'percentage',
+	  PlaceholderType: 'placeholder',
+	  ProgidType: 'progid',
+	  PropertyType: 'property',
+	  PropertyDelimType: 'propertyDelimiter',
+	  PseudocType: 'pseudoClass',
+	  PseudoeType: 'pseudoElement',
+	  RawType: 'raw',
+	  RulesetType: 'ruleset',
+	  SType: 'space',
+	  SelectorType: 'selector',
+	  ShashType: 'id',
+	  StringType: 'string',
+	  StylesheetType: 'stylesheet',
+	  TypeSelectorType: 'typeSelector',
+	  UriType: 'uri',
+	  ValueType: 'value',
+	  VariableType: 'variable',
+	  VariablesListType: 'variablesList',
+	  VhashType: 'color'
+	};
 
+/***/ },
+/* 15 */
+/***/ function(module, exports, __webpack_require__) {
 
+	'use strict';
 
+	module.exports = function (css, tabSize) {
+	  var TokenType = __webpack_require__(12);
 
+	  var tokens = [];
+	  var urlMode = false;
+	  var blockMode = 0;
+	  var c = undefined; // Current character
+	  var cn = undefined; // Next character
+	  var pos = 0;
+	  var tn = 0;
+	  var ln = 1;
+	  var col = 1;
 
+	  var Punctuation = {
+	    ' ': TokenType.Space,
+	    '\n': TokenType.Newline,
+	    '\r': TokenType.Newline,
+	    '\t': TokenType.Tab,
+	    '!': TokenType.ExclamationMark,
+	    '"': TokenType.QuotationMark,
+	    '#': TokenType.NumberSign,
+	    '$': TokenType.DollarSign,
+	    '%': TokenType.PercentSign,
+	    '&': TokenType.Ampersand,
+	    '\'': TokenType.Apostrophe,
+	    '(': TokenType.LeftParenthesis,
+	    ')': TokenType.RightParenthesis,
+	    '*': TokenType.Asterisk,
+	    '+': TokenType.PlusSign,
+	    ',': TokenType.Comma,
+	    '-': TokenType.HyphenMinus,
+	    '.': TokenType.FullStop,
+	    '/': TokenType.Solidus,
+	    ':': TokenType.Colon,
+	    ';': TokenType.Semicolon,
+	    '<': TokenType.LessThanSign,
+	    '=': TokenType.EqualsSign,
+	    '==': TokenType.EqualitySign,
+	    '!=': TokenType.InequalitySign,
+	    '>': TokenType.GreaterThanSign,
+	    '?': TokenType.QuestionMark,
+	    '@': TokenType.CommercialAt,
+	    '[': TokenType.LeftSquareBracket,
+	    ']': TokenType.RightSquareBracket,
+	    '^': TokenType.CircumflexAccent,
+	    '_': TokenType.LowLine,
+	    '{': TokenType.LeftCurlyBracket,
+	    '|': TokenType.VerticalLine,
+	    '}': TokenType.RightCurlyBracket,
+	    '~': TokenType.Tilde
+	  };
 
-function getTokens(css){
+	  /**
+	   * Add a token to the token list
+	   * @param {string} type
+	   * @param {string} value
+	   */
+	  function pushToken(type, value, column) {
+	    tokens.push({
+	      tn: tn++,
+	      ln: ln,
+	      col: column,
+	      type: type,
+	      value: value
+	    });
+	  }
 
-for(pos=0;pos<css.length;col++,pos++){
-c=css.charAt(pos);
-cn=css.charAt(pos+1);
+	  /**
+	   * Check if a character is a decimal digit
+	   * @param {string} c Character
+	   * @returns {boolean}
+	   */
+	  function isDecimalDigit(c) {
+	    return '0123456789'.indexOf(c) >= 0;
+	  }
 
+	  /**
+	   * Parse spaces
+	   * @param {string} css Unparsed part of CSS string
+	   */
+	  function parseSpaces(css) {
+	    var start = pos;
 
+	    // Read the string until we meet a non-space character:
+	    for (; pos < css.length; pos++) {
+	      if (css.charAt(pos) !== ' ') break;
+	    }
 
-if(c==='/'&&cn==='*'){
-parseMLComment(css);
-}else
+	    // Add a substring containing only spaces to tokens:
+	    pushToken(TokenType.Space, css.substring(start, pos--), col);
+	    col += pos - start;
+	  }
 
+	  /**
+	   * Parse a string within quotes
+	   * @param {string} css Unparsed part of CSS string
+	   * @param {string} q Quote (either `'` or `"`)
+	   */
+	  function parseString(css, q) {
+	    var start = pos;
 
-if(!urlMode&&c==='/'&&cn==='/'){
+	    // Read the string until we meet a matching quote:
+	    for (pos++; pos < css.length; pos++) {
+	      // Skip escaped quotes:
+	      if (css.charAt(pos) === '\\') pos++;else if (css.charAt(pos) === q) break;
+	    }
 
+	    // Add the string (including quotes) to tokens:
+	    var type = q === '"' ? TokenType.StringDQ : TokenType.StringSQ;
+	    pushToken(type, css.substring(start, pos + 1), col);
+	    col += pos - start;
+	  }
 
+	  /**
+	   * Parse numbers
+	   * @param {string} css Unparsed part of CSS string
+	   */
+	  function parseDecimalNumber(css) {
+	    var start = pos;
 
-parseSLComment(css);
-}else
+	    // Read the string until we meet a character that's not a digit:
+	    for (; pos < css.length; pos++) {
+	      if (!isDecimalDigit(css.charAt(pos))) break;
+	    }
 
+	    // Add the number to tokens:
+	    pushToken(TokenType.DecimalNumber, css.substring(start, pos--), col);
+	    col += pos - start;
+	  }
 
+	  /**
+	   * Parse identifier
+	   * @param {string} css Unparsed part of CSS string
+	   */
+	  function parseIdentifier(css) {
+	    var start = pos;
 
-if(c==='"'||c==="'"){
-parseString(css,c);
-}else
+	    // Skip all opening slashes:
+	    while (css.charAt(pos) === '/') pos++;
 
+	    // Read the string until we meet a punctuation mark:
+	    for (; pos < css.length; pos++) {
+	      // Skip all '\':
+	      if (css.charAt(pos) === '\\') pos++;else if (css.charAt(pos) in Punctuation) break;
+	    }
 
-if(c===' '){
-parseSpaces(css);
-}else
+	    var ident = css.substring(start, pos--);
 
+	    // Enter url mode if parsed substring is `url`:
+	    if (!urlMode && ident === 'url' && css.charAt(pos + 1) === '(') {
+	      urlMode = true;
+	    }
 
-if(c==='='&&cn==='='){
-parseEquality(css);
-}else
+	    // Add identifier to tokens:
+	    pushToken(TokenType.Identifier, ident, col);
+	    col += pos - start;
+	  }
 
+	  /**
+	   * Parse equality sign
+	   */
+	  function parseEquality() {
+	    pushToken(TokenType.EqualitySign, '==', col);
+	    pos++;
+	    col++;
+	  }
 
-if(c==='!'&&cn==='='){
-parseInequality(css);
-}else
+	  /**
+	   * Parse inequality sign
+	   */
+	  function parseInequality() {
+	    pushToken(TokenType.InequalitySign, '!=', col);
+	    pos++;
+	    col++;
+	  }
 
+	  /**
+	  * Parse a multiline comment
+	  * @param {string} css Unparsed part of CSS string
+	  */
+	  function parseMLComment(css) {
+	    var start = pos;
 
-if(c in Punctuation){
+	    // Read the string until we meet `*/`.
+	    // Since we already know first 2 characters (`/*`), start reading
+	    // from `pos + 2`:
+	    for (pos += 2; pos < css.length; pos++) {
+	      if (css.charAt(pos) === '*' && css.charAt(pos + 1) === '/') {
+	        pos++;
+	        break;
+	      }
+	    }
 
-if(c==='\r'&&cn==='\n'||c==='\n'){
+	    // Add full comment (including `/*` and `*/`) to the list of tokens:
+	    var comment = css.substring(start, pos + 1);
+	    pushToken(TokenType.CommentML, comment, col);
 
+	    var newlines = comment.split('\n');
+	    if (newlines.length > 1) {
+	      ln += newlines.length - 1;
+	      col = newlines[newlines.length - 1].length;
+	    } else {
+	      col += pos - start;
+	    }
+	  }
 
+	  /**
+	  * Parse a single line comment
+	  * @param {string} css Unparsed part of CSS string
+	  */
+	  function parseSLComment(css) {
+	    var start = pos;
 
+	    // Read the string until we meet line break.
+	    // Since we already know first 2 characters (`//`), start reading
+	    // from `pos + 2`:
+	    for (pos += 2; pos < css.length; pos++) {
+	      if (css.charAt(pos) === '\n' || css.charAt(pos) === '\r') {
+	        break;
+	      }
+	    }
 
-if(c==='\r'){
-pushToken(TokenType.Newline,'\r\n',col);
-pos++;
-}else if(c==='\n'){
+	    // Add comment (including `//` and line break) to the list of tokens:
+	    pushToken(TokenType.CommentSL, css.substring(start, pos--), col);
+	    col += pos - start;
+	  }
 
+	  /**
+	   * Convert a CSS string to a list of tokens
+	   * @param {string} css CSS string
+	   * @returns {Array} List of tokens
+	   * @private
+	   */
+	  function getTokens(css) {
+	    // Parse string, character by character:
+	    for (pos = 0; pos < css.length; col++, pos++) {
+	      c = css.charAt(pos);
+	      cn = css.charAt(pos + 1);
 
-pushToken(Punctuation[c],c,col);
-}
+	      // If we meet `/*`, it's a start of a multiline comment.
+	      // Parse following characters as a multiline comment:
+	      if (c === '/' && cn === '*') {
+	        parseMLComment(css);
+	      }
 
-ln++;
-col=0;
-}else if(c!=='\r'&&c!=='\n'){
+	      // If we meet `//` and it is not a part of url:
+	      else if (!urlMode && c === '/' && cn === '/') {
+	          // If we're currently inside a block, treat `//` as a start
+	          // of identifier. Else treat `//` as a start of a single-line
+	          // comment:
+	          parseSLComment(css);
+	        }
 
-pushToken(Punctuation[c],c,col);
-}
-if(c===')')urlMode=false;
-if(c==='{')blockMode++;
-if(c==='}')blockMode--;else
-if(c==='\t'&&tabSize>1)col+=tabSize-1;
-}else
+	        // If current character is a double or single quote, it's a start
+	        // of a string:
+	        else if (c === '"' || c === "'") {
+	            parseString(css, c);
+	          }
 
+	          // If current character is a space:
+	          else if (c === ' ') {
+	              parseSpaces(css);
+	            }
 
-if(isDecimalDigit(c)){
-parseDecimalNumber(css);
-}else
+	            // If current character is `=`, it must be combined with next `=`
+	            else if (c === '=' && cn === '=') {
+	                parseEquality(css);
+	              }
 
+	              // If we meet `!=`, this must be inequality
+	              else if (c === '!' && cn === '=') {
+	                  parseInequality(css);
+	                }
 
-{
-parseIdentifier(css);
-}
-}
+	                // If current character is a punctuation mark:
+	                else if (c in Punctuation) {
+	                    // Check for CRLF here or just LF
+	                    if (c === '\r' && cn === '\n' || c === '\n') {
+	                      // If \r we know the next character is \n due to statement above
+	                      // so we push a CRLF token type to the token list and importantly
+	                      // skip the next character so as not to double count newlines or
+	                      // columns etc
+	                      if (c === '\r') {
+	                        pushToken(TokenType.Newline, '\r\n', col);
+	                        pos++; // If CRLF skip the next character and push crlf token
+	                      } else if (c === '\n') {
+	                          // If just a LF newline and not part of CRLF newline we can just
+	                          // push punctuation as usual
+	                          pushToken(Punctuation[c], c, col);
+	                        }
 
-return tokens;
-}
+	                      ln++; // Go to next line
+	                      col = 0; // Reset the column count
+	                    } else if (c !== '\r' && c !== '\n') {
+	                        // Handle all other punctuation and add to list of tokens
+	                        pushToken(Punctuation[c], c, col);
+	                      } // Go to next line
+	                    if (c === ')') urlMode = false; // Exit url mode
+	                    if (c === '{') blockMode++; // Enter a block
+	                    if (c === '}') blockMode--; // Exit a block
+	                    else if (c === '\t' && tabSize > 1) col += tabSize - 1;
+	                  }
 
-return getTokens(css);
-};
+	                  // If current character is a decimal digit:
+	                  else if (isDecimalDigit(c)) {
+	                      parseDecimalNumber(css);
+	                    }
 
-},
+	                    // If current character is anything else:
+	                    else {
+	                        parseIdentifier(css);
+	                      }
+	    }
 
-function(module,exports,__webpack_require__){
+	    return tokens;
+	  }
 
-'use strict';
+	  return getTokens(css);
+	};
 
-var Node=__webpack_require__(1);
-var NodeTypes=__webpack_require__(14);
+/***/ },
+/* 16 */
+/***/ function(module, exports, __webpack_require__) {
 
-module.exports=function(){
-return new Node({
-type:NodeTypes.StylesheetType,
-content:[],
-start:[0,0],
-end:[0,0]});
+	'use strict';
 
-};
+	var Node = __webpack_require__(1);
+	var NodeTypes = __webpack_require__(14);
 
-}]);
+	module.exports = function () {
+	  return new Node({
+	    type: NodeTypes.StylesheetType,
+	    content: [],
+	    start: [0, 0],
+	    end: [0, 0]
+	  });
+	};
 
+/***/ }
+/******/ ])
 });
 ;
-},{}],216:[function(require,module,exports){
+},{}],245:[function(require,module,exports){
+/*
+ * Copyright (C) 2007 Apple Inc.  All rights reserved.
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ *     its contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-console=console;
-console.__originalAssert=console.assert;
-console.assert=function(value,message)
+// FIXME: This performance optimization should be moved to blink so that all developers could enjoy it.
+// console is retrieved with V8Window.getAttribute method which is slow. Here we copy it to a js variable for faster access.
+console = console;
+console.__originalAssert = console.assert;
+console.assert = function(value, message)
 {
-if(value)
-return;
-console.__originalAssert(value,message);
-};
+    if (value)
+        return;
+    console.__originalAssert(value, message);
+}
 
-
+/** @typedef {Array|NodeList|Arguments|{length: number}} */
 var ArrayLike;
 
-
-
-
-
-
-function mod(m,n)
+/**
+ * @param {number} m
+ * @param {number} n
+ * @return {number}
+ */
+function mod(m, n)
 {
-return(m%n+n)%n;
+    return ((m % n) + n) % n;
 }
 
-
-
-
-
-String.prototype.findAll=function(string)
+/**
+ * @param {string} string
+ * @return {!Array.<number>}
+ */
+String.prototype.findAll = function(string)
 {
-var matches=[];
-var i=this.indexOf(string);
-while(i!==-1){
-matches.push(i);
-i=this.indexOf(string,i+string.length);
-}
-return matches;
-};
-
-
-
-
-String.prototype.replaceControlCharacters=function()
-{
-
-
-return this.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u0080-\u009f]/g,"�");
-};
-
-
-
-
-String.prototype.isWhitespace=function()
-{
-return /^\s*$/.test(this);
-};
-
-
-
-
-String.prototype.computeLineEndings=function()
-{
-var endings=this.findAll("\n");
-endings.push(this.length);
-return endings;
-};
-
-
-
-
-
-String.prototype.escapeCharacters=function(chars)
-{
-var foundChar=false;
-for(var i=0;i<chars.length;++i){
-if(this.indexOf(chars.charAt(i))!==-1){
-foundChar=true;
-break;
-}
+    var matches = [];
+    var i = this.indexOf(string);
+    while (i !== -1) {
+        matches.push(i);
+        i = this.indexOf(string, i + string.length);
+    }
+    return matches;
 }
 
-if(!foundChar)
-return String(this);
-
-var result="";
-for(var i=0;i<this.length;++i){
-if(chars.indexOf(this.charAt(i))!==-1)
-result+="\\";
-result+=this.charAt(i);
+/**
+ * @return {string}
+ */
+String.prototype.replaceControlCharacters = function()
+{
+    // Replace C0 and C1 control character sets with printable character.
+    // Do not replace '\t', \n' and '\r'.
+    return this.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u0080-\u009f]/g, "�");
 }
 
-return result;
-};
-
-
-
-
-String.regexSpecialCharacters=function()
+/**
+ * @return {boolean}
+ */
+String.prototype.isWhitespace = function()
 {
-return"^[]{}()\\.^$*+?|-,";
-};
-
-
-
-
-String.prototype.escapeForRegExp=function()
-{
-return this.escapeCharacters(String.regexSpecialCharacters());
-};
-
-
-
-
-String.prototype.escapeHTML=function()
-{
-return this.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");
-};
-
-
-
-
-String.prototype.unescapeHTML=function()
-{
-return this.replace(/&lt;/g,"<").
-replace(/&gt;/g,">").
-replace(/&#58;/g,":").
-replace(/&quot;/g,"\"").
-replace(/&#60;/g,"<").
-replace(/&#62;/g,">").
-replace(/&amp;/g,"&");
-};
-
-
-
-
-String.prototype.collapseWhitespace=function()
-{
-return this.replace(/[\s\xA0]+/g," ");
-};
-
-
-
-
-
-String.prototype.trimMiddle=function(maxLength)
-{
-if(this.length<=maxLength)
-return String(this);
-var leftHalf=maxLength>>1;
-var rightHalf=maxLength-leftHalf-1;
-return this.substr(0,leftHalf)+"\u2026"+this.substr(this.length-rightHalf,rightHalf);
-};
-
-
-
-
-
-String.prototype.trimEnd=function(maxLength)
-{
-if(this.length<=maxLength)
-return String(this);
-return this.substr(0,maxLength-1)+"\u2026";
-};
-
-
-
-
-
-String.prototype.trimURL=function(baseURLDomain)
-{
-var result=this.replace(/^(https|http|file):\/\//i,"");
-if(baseURLDomain){
-if(result.toLowerCase().startsWith(baseURLDomain.toLowerCase()))
-result=result.substr(baseURLDomain.length);
-}
-return result;
-};
-
-
-
-
-String.prototype.toTitleCase=function()
-{
-return this.substring(0,1).toUpperCase()+this.substring(1);
-};
-
-
-
-
-
-String.prototype.compareTo=function(other)
-{
-if(this>other)
-return 1;
-if(this<other)
-return-1;
-return 0;
-};
-
-
-
-
-String.prototype.removeURLFragment=function()
-{
-var fragmentIndex=this.indexOf("#");
-if(fragmentIndex===-1)
-fragmentIndex=this.length;
-return this.substring(0,fragmentIndex);
-};
-
-
-
-
-
-String.hashCode=function(string)
-{
-if(!string)
-return 0;
-
-
-
-var p=(1<<30)*4-5;
-var z=0x5033d967;
-var z2=0x59d2f15d;
-var s=0;
-var zi=1;
-for(var i=0;i<string.length;i++){
-var xi=string.charCodeAt(i)*z2;
-s=(s+zi*xi)%p;
-zi=zi*z%p;
-}
-s=(s+zi*(p-1))%p;
-return Math.abs(s|0);
-};
-
-
-
-
-
-
-String.isDigitAt=function(string,index)
-{
-var c=string.charCodeAt(index);
-return 48<=c&&c<=57;
-};
-
-
-
-
-String.prototype.toBase64=function()
-{
-
-
-
-
-function encodeBits(b)
-{
-return b<26?b+65:b<52?b+71:b<62?b-4:b===62?43:b===63?47:65;
-}
-var encoder=new TextEncoder();
-var data=encoder.encode(this.toString());
-var n=data.length;
-var encoded="";
-if(n===0)
-return encoded;
-var shift;
-var v=0;
-for(var i=0;i<n;i++){
-shift=i%3;
-v|=data[i]<<(16>>>shift&24);
-if(shift===2){
-encoded+=String.fromCharCode(encodeBits(v>>>18&63),encodeBits(v>>>12&63),encodeBits(v>>>6&63),encodeBits(v&63));
-v=0;
-}
-}
-if(shift===0)
-encoded+=String.fromCharCode(encodeBits(v>>>18&63),encodeBits(v>>>12&63),61,61);else
-if(shift===1)
-encoded+=String.fromCharCode(encodeBits(v>>>18&63),encodeBits(v>>>12&63),encodeBits(v>>>6&63),61);
-return encoded;
-};
-
-
-
-
-
-
-String.naturalOrderComparator=function(a,b)
-{
-var chunk=/^\d+|^\D+/;
-var chunka,chunkb,anum,bnum;
-while(1){
-if(a){
-if(!b)
-return 1;
-}else{
-if(b)
-return-1;else
-
-return 0;
-}
-chunka=a.match(chunk)[0];
-chunkb=b.match(chunk)[0];
-anum=!isNaN(chunka);
-bnum=!isNaN(chunkb);
-if(anum&&!bnum)
-return-1;
-if(bnum&&!anum)
-return 1;
-if(anum&&bnum){
-var diff=chunka-chunkb;
-if(diff)
-return diff;
-if(chunka.length!==chunkb.length){
-if(!+chunka&&!+chunkb)
-return chunka.length-chunkb.length;else
-
-return chunkb.length-chunka.length;
-}
-}else if(chunka!==chunkb)
-return chunka<chunkb?-1:1;
-a=a.substring(chunka.length);
-b=b.substring(chunkb.length);
-}
-};
-
-
-
-
-
-
-String.caseInsensetiveComparator=function(a,b)
-{
-a=a.toUpperCase();
-b=b.toUpperCase();
-if(a===b)
-return 0;
-return a>b?1:-1;
-};
-
-
-
-
-
-
-
-Number.constrain=function(num,min,max)
-{
-if(num<min)
-num=min;else
-if(num>max)
-num=max;
-return num;
-};
-
-
-
-
-
-
-Number.gcd=function(a,b)
-{
-if(b===0)
-return a;else
-
-return Number.gcd(b,a%b);
-};
-
-
-
-
-
-Number.toFixedIfFloating=function(value)
-{
-if(!value||isNaN(value))
-return value;
-var number=Number(value);
-return number%1?number.toFixed(3):String(number);
-};
-
-
-
-
-Date.prototype.toISO8601Compact=function()
-{
-
-
-
-
-function leadZero(x)
-{
-return(x>9?"":"0")+x;
-}
-return this.getFullYear()+
-leadZero(this.getMonth()+1)+
-leadZero(this.getDate())+"T"+
-leadZero(this.getHours())+
-leadZero(this.getMinutes())+
-leadZero(this.getSeconds());
-};
-
-
-
-
-Date.prototype.toConsoleTime=function()
-{
-
-
-
-
-function leadZero2(x)
-{
-return(x>9?"":"0")+x;
+    return /^\s*$/.test(this);
 }
 
-
-
-
-
-function leadZero3(x)
+/**
+ * @return {!Array.<number>}
+ */
+String.prototype.computeLineEndings = function()
 {
-return"0".repeat(3-x.toString().length)+x;
+    var endings = this.findAll("\n");
+    endings.push(this.length);
+    return endings;
 }
 
-return this.getFullYear()+"-"+
-leadZero2(this.getMonth()+1)+"-"+
-leadZero2(this.getDate())+" "+
-leadZero2(this.getHours())+":"+
-leadZero2(this.getMinutes())+":"+
-leadZero2(this.getSeconds())+"."+
-leadZero3(this.getMilliseconds());
-};
-
-Object.defineProperty(Array.prototype,"remove",{
-
-
-
-
-
-
-
-value:function(value,firstOnly)
+/**
+ * @param {string} chars
+ * @return {string}
+ */
+String.prototype.escapeCharacters = function(chars)
 {
-var index=this.indexOf(value);
-if(index===-1)
-return false;
-if(firstOnly){
-this.splice(index,1);
-return true;
-}
-for(var i=index+1,n=this.length;i<n;++i){
-if(this[i]!==value)
-this[index++]=this[i];
-}
-this.length=index;
-return true;
-}});
+    var foundChar = false;
+    for (var i = 0; i < chars.length; ++i) {
+        if (this.indexOf(chars.charAt(i)) !== -1) {
+            foundChar = true;
+            break;
+        }
+    }
 
+    if (!foundChar)
+        return String(this);
 
-Object.defineProperty(Array.prototype,"pushAll",{
+    var result = "";
+    for (var i = 0; i < this.length; ++i) {
+        if (chars.indexOf(this.charAt(i)) !== -1)
+            result += "\\";
+        result += this.charAt(i);
+    }
 
-
-
-
-
-value:function(array)
-{
-Array.prototype.push.apply(this,array);
-}});
-
-
-Object.defineProperty(Array.prototype,"rotate",{
-
-
-
-
-
-
-value:function(index)
-{
-var result=[];
-for(var i=index;i<index+this.length;++i)
-result.push(this[i%this.length]);
-return result;
-}});
-
-
-Object.defineProperty(Array.prototype,"sortNumbers",{
-
-
-
-value:function()
-{
-
-
-
-
-
-function numericComparator(a,b)
-{
-return a-b;
+    return result;
 }
 
-this.sort(numericComparator);
-}});
-
-
-Object.defineProperty(Uint32Array.prototype,"sort",{
-value:Array.prototype.sort});
-
-
-(function(){
-var partition={
-
-
-
-
-
-
-
-value:function(comparator,left,right,pivotIndex)
+/**
+ * @return {string}
+ */
+String.regexSpecialCharacters = function()
 {
-function swap(array,i1,i2)
-{
-var temp=array[i1];
-array[i1]=array[i2];
-array[i2]=temp;
+    return "^[]{}()\\.^$*+?|-,";
 }
 
-var pivotValue=this[pivotIndex];
-swap(this,right,pivotIndex);
-var storeIndex=left;
-for(var i=left;i<right;++i){
-if(comparator(this[i],pivotValue)<0){
-swap(this,storeIndex,i);
-++storeIndex;
-}
-}
-swap(this,right,storeIndex);
-return storeIndex;
-}};
-
-Object.defineProperty(Array.prototype,"partition",partition);
-Object.defineProperty(Uint32Array.prototype,"partition",partition);
-
-var sortRange={
-
-
-
-
-
-
-
-
-
-value:function(comparator,leftBound,rightBound,sortWindowLeft,sortWindowRight)
+/**
+ * @return {string}
+ */
+String.prototype.escapeForRegExp = function()
 {
-function quickSortRange(array,comparator,left,right,sortWindowLeft,sortWindowRight)
-{
-if(right<=left)
-return;
-var pivotIndex=Math.floor(Math.random()*(right-left))+left;
-var pivotNewIndex=array.partition(comparator,left,right,pivotIndex);
-if(sortWindowLeft<pivotNewIndex)
-quickSortRange(array,comparator,left,pivotNewIndex-1,sortWindowLeft,sortWindowRight);
-if(pivotNewIndex<sortWindowRight)
-quickSortRange(array,comparator,pivotNewIndex+1,right,sortWindowLeft,sortWindowRight);
+    return this.escapeCharacters(String.regexSpecialCharacters());
 }
-if(leftBound===0&&rightBound===this.length-1&&sortWindowLeft===0&&sortWindowRight>=rightBound)
-this.sort(comparator);else
 
-quickSortRange(this,comparator,leftBound,rightBound,sortWindowLeft,sortWindowRight);
-return this;
-}};
+/**
+ * @return {string}
+ */
+String.prototype.escapeHTML = function()
+{
+    return this.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;"); // " doublequotes just for editor
+}
 
-Object.defineProperty(Array.prototype,"sortRange",sortRange);
-Object.defineProperty(Uint32Array.prototype,"sortRange",sortRange);
+/**
+ * @return {string}
+ */
+String.prototype.unescapeHTML = function()
+{
+    return this.replace(/&lt;/g, "<")
+        .replace(/&gt;/g, ">")
+        .replace(/&#58;/g, ":")
+        .replace(/&quot;/g, "\"")
+        .replace(/&#60;/g, "<")
+        .replace(/&#62;/g, ">")
+        .replace(/&amp;/g, "&");
+}
+
+/**
+ * @return {string}
+ */
+String.prototype.collapseWhitespace = function()
+{
+    return this.replace(/[\s\xA0]+/g, " ");
+}
+
+/**
+ * @param {number} maxLength
+ * @return {string}
+ */
+String.prototype.trimMiddle = function(maxLength)
+{
+    if (this.length <= maxLength)
+        return String(this);
+    var leftHalf = maxLength >> 1;
+    var rightHalf = maxLength - leftHalf - 1;
+    return this.substr(0, leftHalf) + "\u2026" + this.substr(this.length - rightHalf, rightHalf);
+}
+
+/**
+ * @param {number} maxLength
+ * @return {string}
+ */
+String.prototype.trimEnd = function(maxLength)
+{
+    if (this.length <= maxLength)
+        return String(this);
+    return this.substr(0, maxLength - 1) + "\u2026";
+}
+
+/**
+ * @param {?string=} baseURLDomain
+ * @return {string}
+ */
+String.prototype.trimURL = function(baseURLDomain)
+{
+    var result = this.replace(/^(https|http|file):\/\//i, "");
+    if (baseURLDomain) {
+        if (result.toLowerCase().startsWith(baseURLDomain.toLowerCase()))
+            result = result.substr(baseURLDomain.length);
+    }
+    return result;
+}
+
+/**
+ * @return {string}
+ */
+String.prototype.toTitleCase = function()
+{
+    return this.substring(0, 1).toUpperCase() + this.substring(1);
+}
+
+/**
+ * @param {string} other
+ * @return {number}
+ */
+String.prototype.compareTo = function(other)
+{
+    if (this > other)
+        return 1;
+    if (this < other)
+        return -1;
+    return 0;
+}
+
+/**
+ * @return {string}
+ */
+String.prototype.removeURLFragment = function()
+{
+    var fragmentIndex = this.indexOf("#");
+    if (fragmentIndex === -1)
+        fragmentIndex = this.length;
+    return this.substring(0, fragmentIndex);
+}
+
+/**
+ * @param {string|undefined} string
+ * @return {number}
+ */
+String.hashCode = function(string)
+{
+    if (!string)
+        return 0;
+    // Hash algorithm for substrings is described in "Über die Komplexität der Multiplikation in
+    // eingeschränkten Branchingprogrammmodellen" by Woelfe.
+    // http://opendatastructures.org/versions/edition-0.1d/ods-java/node33.html#SECTION00832000000000000000
+    var p = ((1 << 30) * 4 - 5); // prime: 2^32 - 5
+    var z = 0x5033d967;          // 32 bits from random.org
+    var z2 = 0x59d2f15d;         // random odd 32 bit number
+    var s = 0;
+    var zi = 1;
+    for (var i = 0; i < string.length; i++) {
+        var xi = string.charCodeAt(i) * z2;
+        s = (s + zi * xi) % p;
+        zi = (zi * z) % p;
+    }
+    s = (s + zi * (p - 1)) % p;
+    return Math.abs(s | 0);
+}
+
+/**
+ * @param {string} string
+ * @param {number} index
+ * @return {boolean}
+ */
+String.isDigitAt = function(string, index)
+{
+    var c = string.charCodeAt(index);
+    return (48 <= c && c <= 57);
+}
+
+/**
+ * @return {string}
+ */
+String.prototype.toBase64 = function()
+{
+    /**
+     * @param {number} b
+     * @return {number}
+     */
+    function encodeBits(b)
+    {
+        return b < 26 ? b + 65 : b < 52 ? b + 71 : b < 62 ? b - 4 : b === 62 ? 43 : b === 63 ? 47 : 65;
+    }
+    var encoder = new TextEncoder();
+    var data = encoder.encode(this.toString());
+    var n = data.length;
+    var encoded = "";
+    if (n === 0)
+        return encoded;
+    var shift;
+    var v = 0;
+    for (var i = 0; i < n; i++) {
+        shift = i % 3;
+        v |= data[i] << (16 >>> shift & 24);
+        if (shift === 2) {
+            encoded += String.fromCharCode(encodeBits(v >>> 18 & 63), encodeBits(v >>> 12 & 63), encodeBits(v >>> 6 & 63), encodeBits(v & 63));
+            v = 0;
+        }
+    }
+    if (shift === 0)
+        encoded += String.fromCharCode(encodeBits(v >>> 18 & 63), encodeBits(v >>> 12 & 63), 61, 61);
+    else if (shift === 1)
+        encoded += String.fromCharCode(encodeBits(v >>> 18 & 63), encodeBits(v >>> 12 & 63), encodeBits(v >>> 6 & 63), 61);
+    return encoded;
+}
+
+/**
+ * @param {string} a
+ * @param {string} b
+ * @return {number}
+ */
+String.naturalOrderComparator = function(a, b)
+{
+    var chunk = /^\d+|^\D+/;
+    var chunka, chunkb, anum, bnum;
+    while (1) {
+        if (a) {
+            if (!b)
+                return 1;
+        } else {
+            if (b)
+                return -1;
+            else
+                return 0;
+        }
+        chunka = a.match(chunk)[0];
+        chunkb = b.match(chunk)[0];
+        anum = !isNaN(chunka);
+        bnum = !isNaN(chunkb);
+        if (anum && !bnum)
+            return -1;
+        if (bnum && !anum)
+            return 1;
+        if (anum && bnum) {
+            var diff = chunka - chunkb;
+            if (diff)
+                return diff;
+            if (chunka.length !== chunkb.length) {
+                if (!+chunka && !+chunkb) // chunks are strings of all 0s (special case)
+                    return chunka.length - chunkb.length;
+                else
+                    return chunkb.length - chunka.length;
+            }
+        } else if (chunka !== chunkb)
+            return (chunka < chunkb) ? -1 : 1;
+        a = a.substring(chunka.length);
+        b = b.substring(chunkb.length);
+    }
+}
+
+/**
+ * @param {string} a
+ * @param {string} b
+ * @return {number}
+ */
+String.caseInsensetiveComparator = function(a, b)
+{
+    a = a.toUpperCase();
+    b = b.toUpperCase();
+    if (a === b)
+        return 0;
+    return a > b ? 1 : -1;
+}
+
+/**
+ * @param {number} num
+ * @param {number} min
+ * @param {number} max
+ * @return {number}
+ */
+Number.constrain = function(num, min, max)
+{
+    if (num < min)
+        num = min;
+    else if (num > max)
+        num = max;
+    return num;
+}
+
+/**
+ * @param {number} a
+ * @param {number} b
+ * @return {number}
+ */
+Number.gcd = function(a, b)
+{
+    if (b === 0)
+        return a;
+    else
+        return Number.gcd(b, a % b);
+}
+
+/**
+ * @param {string} value
+ * @return {string}
+ */
+Number.toFixedIfFloating = function(value)
+{
+    if (!value || isNaN(value))
+        return value;
+    var number = Number(value);
+    return number % 1 ? number.toFixed(3) : String(number);
+}
+
+/**
+ * @return {string}
+ */
+Date.prototype.toISO8601Compact = function()
+{
+    /**
+     * @param {number} x
+     * @return {string}
+     */
+    function leadZero(x)
+    {
+        return (x > 9 ? "" : "0") + x;
+    }
+    return this.getFullYear() +
+           leadZero(this.getMonth() + 1) +
+           leadZero(this.getDate()) + "T" +
+           leadZero(this.getHours()) +
+           leadZero(this.getMinutes()) +
+           leadZero(this.getSeconds());
+}
+
+/**
+ * @return {string}
+ */
+Date.prototype.toConsoleTime = function()
+{
+    /**
+     * @param {number} x
+     * @return {string}
+     */
+    function leadZero2(x)
+    {
+        return (x > 9 ? "" : "0") + x;
+    }
+
+    /**
+     * @param {number} x
+     * @return {string}
+     */
+    function leadZero3(x)
+    {
+        return "0".repeat(3 - x.toString().length) + x;
+    }
+
+    return this.getFullYear() + "-" +
+           leadZero2(this.getMonth() + 1) + "-" +
+           leadZero2(this.getDate()) + " " +
+           leadZero2(this.getHours()) + ":" +
+           leadZero2(this.getMinutes()) + ":" +
+           leadZero2(this.getSeconds()) + "." +
+           leadZero3(this.getMilliseconds());
+}
+
+Object.defineProperty(Array.prototype, "remove", {
+    /**
+     * @param {!T} value
+     * @param {boolean=} firstOnly
+     * @return {boolean}
+     * @this {Array.<!T>}
+     * @template T
+     */
+    value: function(value, firstOnly)
+    {
+        var index = this.indexOf(value);
+        if (index === -1)
+            return false;
+        if (firstOnly) {
+            this.splice(index, 1);
+            return true;
+        }
+        for (var i = index + 1, n = this.length; i < n; ++i) {
+            if (this[i] !== value)
+                this[index++] = this[i];
+        }
+        this.length = index;
+        return true;
+    }
+});
+
+Object.defineProperty(Array.prototype, "pushAll", {
+    /**
+     * @param {!Array.<!T>} array
+     * @this {Array.<!T>}
+     * @template T
+     */
+    value: function(array)
+    {
+        Array.prototype.push.apply(this, array);
+    }
+});
+
+Object.defineProperty(Array.prototype, "rotate", {
+    /**
+     * @param {number} index
+     * @return {!Array.<!T>}
+     * @this {Array.<!T>}
+     * @template T
+     */
+    value: function(index)
+    {
+        var result = [];
+        for (var i = index; i < index + this.length; ++i)
+            result.push(this[i % this.length]);
+        return result;
+    }
+});
+
+Object.defineProperty(Array.prototype, "sortNumbers", {
+    /**
+     * @this {Array.<number>}
+     */
+    value: function()
+    {
+        /**
+         * @param {number} a
+         * @param {number} b
+         * @return {number}
+         */
+        function numericComparator(a, b)
+        {
+            return a - b;
+        }
+
+        this.sort(numericComparator);
+    }
+});
+
+Object.defineProperty(Uint32Array.prototype, "sort", {
+    value: Array.prototype.sort
+});
+
+(function() {
+    var partition = {
+        /**
+         * @this {Array.<number>}
+         * @param {function(number, number): number} comparator
+         * @param {number} left
+         * @param {number} right
+         * @param {number} pivotIndex
+         */
+        value: function(comparator, left, right, pivotIndex)
+        {
+            function swap(array, i1, i2)
+            {
+                var temp = array[i1];
+                array[i1] = array[i2];
+                array[i2] = temp;
+            }
+
+            var pivotValue = this[pivotIndex];
+            swap(this, right, pivotIndex);
+            var storeIndex = left;
+            for (var i = left; i < right; ++i) {
+                if (comparator(this[i], pivotValue) < 0) {
+                    swap(this, storeIndex, i);
+                    ++storeIndex;
+                }
+            }
+            swap(this, right, storeIndex);
+            return storeIndex;
+        }
+    };
+    Object.defineProperty(Array.prototype, "partition", partition);
+    Object.defineProperty(Uint32Array.prototype, "partition", partition);
+
+    var sortRange = {
+        /**
+         * @param {function(number, number): number} comparator
+         * @param {number} leftBound
+         * @param {number} rightBound
+         * @param {number} sortWindowLeft
+         * @param {number} sortWindowRight
+         * @return {!Array.<number>}
+         * @this {Array.<number>}
+         */
+        value: function(comparator, leftBound, rightBound, sortWindowLeft, sortWindowRight)
+        {
+            function quickSortRange(array, comparator, left, right, sortWindowLeft, sortWindowRight)
+            {
+                if (right <= left)
+                    return;
+                var pivotIndex = Math.floor(Math.random() * (right - left)) + left;
+                var pivotNewIndex = array.partition(comparator, left, right, pivotIndex);
+                if (sortWindowLeft < pivotNewIndex)
+                    quickSortRange(array, comparator, left, pivotNewIndex - 1, sortWindowLeft, sortWindowRight);
+                if (pivotNewIndex < sortWindowRight)
+                    quickSortRange(array, comparator, pivotNewIndex + 1, right, sortWindowLeft, sortWindowRight);
+            }
+            if (leftBound === 0 && rightBound === (this.length - 1) && sortWindowLeft === 0 && sortWindowRight >= rightBound)
+                this.sort(comparator);
+            else
+                quickSortRange(this, comparator, leftBound, rightBound, sortWindowLeft, sortWindowRight);
+            return this;
+        }
+    }
+    Object.defineProperty(Array.prototype, "sortRange", sortRange);
+    Object.defineProperty(Uint32Array.prototype, "sortRange", sortRange);
 })();
 
-Object.defineProperty(Array.prototype,"stableSort",{
+Object.defineProperty(Array.prototype, "stableSort", {
+    /**
+     * @param {function(?T, ?T): number=} comparator
+     * @return {!Array.<?T>}
+     * @this {Array.<?T>}
+     * @template T
+     */
+    value: function(comparator)
+    {
+        function defaultComparator(a, b)
+        {
+            return a < b ? -1 : (a > b ? 1 : 0);
+        }
+        comparator = comparator || defaultComparator;
 
+        var indices = new Array(this.length);
+        for (var i = 0; i < this.length; ++i)
+            indices[i] = i;
+        var self = this;
+        /**
+         * @param {number} a
+         * @param {number} b
+         * @return {number}
+         */
+        function indexComparator(a, b)
+        {
+            var result = comparator(self[a], self[b]);
+            return result ? result : a - b;
+        }
+        indices.sort(indexComparator);
 
+        for (var i = 0; i < this.length; ++i) {
+            if (indices[i] < 0 || i === indices[i])
+                continue;
+            var cyclical = i;
+            var saved = this[i];
+            while (true) {
+                var next = indices[cyclical];
+                indices[cyclical] = -1;
+                if (next === i) {
+                    this[cyclical] = saved;
+                    break;
+                } else {
+                    this[cyclical] = this[next];
+                    cyclical = next;
+                }
+            }
+        }
+        return this;
+    }
+});
 
+Object.defineProperty(Array.prototype, "qselect", {
+    /**
+     * @param {number} k
+     * @param {function(number, number): number=} comparator
+     * @return {number|undefined}
+     * @this {Array.<number>}
+     */
+    value: function(k, comparator)
+    {
+        if (k < 0 || k >= this.length)
+            return;
+        if (!comparator)
+            comparator = function(a, b) { return a - b; }
 
+        var low = 0;
+        var high = this.length - 1;
+        for (;;) {
+            var pivotPosition = this.partition(comparator, low, high, Math.floor((high + low) / 2));
+            if (pivotPosition === k)
+                return this[k];
+            else if (pivotPosition > k)
+                high = pivotPosition - 1;
+            else
+                low = pivotPosition + 1;
+        }
+    }
+});
 
+Object.defineProperty(Array.prototype, "lowerBound", {
+    /**
+     * Return index of the leftmost element that is equal or greater
+     * than the specimen object. If there's no such element (i.e. all
+     * elements are smaller than the specimen) returns right bound.
+     * The function works for sorted array.
+     * When specified, |left| (inclusive) and |right| (exclusive) indices
+     * define the search window.
+     *
+     * @param {!T} object
+     * @param {function(!T,!S):number=} comparator
+     * @param {number=} left
+     * @param {number=} right
+     * @return {number}
+     * @this {Array.<!S>}
+     * @template T,S
+     */
+    value: function(object, comparator, left, right)
+    {
+        function defaultComparator(a, b)
+        {
+            return a < b ? -1 : (a > b ? 1 : 0);
+        }
+        comparator = comparator || defaultComparator;
+        var l = left || 0;
+        var r = right !== undefined ? right : this.length;
+        while (l < r) {
+            var m = (l + r) >> 1;
+            if (comparator(object, this[m]) > 0)
+                l = m + 1;
+            else
+                r = m;
+        }
+        return r;
+    }
+});
 
-value:function(comparator)
-{
-function defaultComparator(a,b)
-{
-return a<b?-1:a>b?1:0;
-}
-comparator=comparator||defaultComparator;
+Object.defineProperty(Array.prototype, "upperBound", {
+    /**
+     * Return index of the leftmost element that is greater
+     * than the specimen object. If there's no such element (i.e. all
+     * elements are smaller or equal to the specimen) returns right bound.
+     * The function works for sorted array.
+     * When specified, |left| (inclusive) and |right| (exclusive) indices
+     * define the search window.
+     *
+     * @param {!T} object
+     * @param {function(!T,!S):number=} comparator
+     * @param {number=} left
+     * @param {number=} right
+     * @return {number}
+     * @this {Array.<!S>}
+     * @template T,S
+     */
+    value: function(object, comparator, left, right)
+    {
+        function defaultComparator(a, b)
+        {
+            return a < b ? -1 : (a > b ? 1 : 0);
+        }
+        comparator = comparator || defaultComparator;
+        var l = left || 0;
+        var r = right !== undefined ? right : this.length;
+        while (l < r) {
+            var m = (l + r) >> 1;
+            if (comparator(object, this[m]) >= 0)
+                l = m + 1;
+            else
+                r = m;
+        }
+        return r;
+    }
+});
 
-var indices=new Array(this.length);
-for(var i=0;i<this.length;++i)
-indices[i]=i;
-var self=this;
+Object.defineProperty(Uint32Array.prototype, "lowerBound", {
+    value: Array.prototype.lowerBound
+});
 
+Object.defineProperty(Uint32Array.prototype, "upperBound", {
+    value: Array.prototype.upperBound
+});
 
+Object.defineProperty(Float64Array.prototype, "lowerBound", {
+    value: Array.prototype.lowerBound
+});
 
+Object.defineProperty(Array.prototype, "binaryIndexOf", {
+    /**
+     * @param {!T} value
+     * @param {function(!T,!S):number} comparator
+     * @return {number}
+     * @this {Array.<!S>}
+     * @template T,S
+     */
+    value: function(value, comparator)
+    {
+        var index = this.lowerBound(value, comparator);
+        return index < this.length && comparator(value, this[index]) === 0 ? index : -1;
+    }
+});
 
-
-function indexComparator(a,b)
-{
-var result=comparator(self[a],self[b]);
-return result?result:a-b;
-}
-indices.sort(indexComparator);
-
-for(var i=0;i<this.length;++i){
-if(indices[i]<0||i===indices[i])
-continue;
-var cyclical=i;
-var saved=this[i];
-while(true){
-var next=indices[cyclical];
-indices[cyclical]=-1;
-if(next===i){
-this[cyclical]=saved;
-break;
-}else{
-this[cyclical]=this[next];
-cyclical=next;
-}
-}
-}
-return this;
-}});
-
-
-Object.defineProperty(Array.prototype,"qselect",{
-
-
-
-
-
-
-value:function(k,comparator)
-{
-if(k<0||k>=this.length)
-return;
-if(!comparator)
-comparator=function(a,b){return a-b;};
-
-var low=0;
-var high=this.length-1;
-for(;;){
-var pivotPosition=this.partition(comparator,low,high,Math.floor((high+low)/2));
-if(pivotPosition===k)
-return this[k];else
-if(pivotPosition>k)
-high=pivotPosition-1;else
-
-low=pivotPosition+1;
-}
-}});
-
-
-Object.defineProperty(Array.prototype,"lowerBound",{
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-value:function(object,comparator,left,right)
-{
-function defaultComparator(a,b)
-{
-return a<b?-1:a>b?1:0;
-}
-comparator=comparator||defaultComparator;
-var l=left||0;
-var r=right!==undefined?right:this.length;
-while(l<r){
-var m=l+r>>1;
-if(comparator(object,this[m])>0)
-l=m+1;else
-
-r=m;
-}
-return r;
-}});
-
-
-Object.defineProperty(Array.prototype,"upperBound",{
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-value:function(object,comparator,left,right)
-{
-function defaultComparator(a,b)
-{
-return a<b?-1:a>b?1:0;
-}
-comparator=comparator||defaultComparator;
-var l=left||0;
-var r=right!==undefined?right:this.length;
-while(l<r){
-var m=l+r>>1;
-if(comparator(object,this[m])>=0)
-l=m+1;else
-
-r=m;
-}
-return r;
-}});
-
-
-Object.defineProperty(Uint32Array.prototype,"lowerBound",{
-value:Array.prototype.lowerBound});
-
-
-Object.defineProperty(Uint32Array.prototype,"upperBound",{
-value:Array.prototype.upperBound});
-
-
-Object.defineProperty(Float64Array.prototype,"lowerBound",{
-value:Array.prototype.lowerBound});
-
-
-Object.defineProperty(Array.prototype,"binaryIndexOf",{
-
-
-
-
-
-
-
-value:function(value,comparator)
-{
-var index=this.lowerBound(value,comparator);
-return index<this.length&&comparator(value,this[index])===0?index:-1;
-}});
-
-
-Object.defineProperty(Array.prototype,"select",{
-
-
-
-
-
-
-value:function(field)
-{
-var result=new Array(this.length);
-for(var i=0;i<this.length;++i)
-result[i]=this[i][field];
-return result;
-}});
-
-
-Object.defineProperty(Array.prototype,"peekLast",{
-
-
-
-
-
-value:function()
-{
-return this[this.length-1];
-}});
+Object.defineProperty(Array.prototype, "select", {
+    /**
+     * @param {string} field
+     * @return {!Array.<!T>}
+     * @this {Array.<!Object.<string,!T>>}
+     * @template T
+     */
+    value: function(field)
+    {
+        var result = new Array(this.length);
+        for (var i = 0; i < this.length; ++i)
+            result[i] = this[i][field];
+        return result;
+    }
+});
 
+Object.defineProperty(Array.prototype, "peekLast", {
+    /**
+     * @return {!T|undefined}
+     * @this {Array.<!T>}
+     * @template T
+     */
+    value: function()
+    {
+        return this[this.length - 1];
+    }
+});
 
 (function(){
+    /**
+     * @param {!Array.<T>} array1
+     * @param {!Array.<T>} array2
+     * @param {function(T,T):number} comparator
+     * @param {boolean} mergeNotIntersect
+     * @return {!Array.<T>}
+     * @template T
+     */
+    function mergeOrIntersect(array1, array2, comparator, mergeNotIntersect)
+    {
+        var result = [];
+        var i = 0;
+        var j = 0;
+        while (i < array1.length && j < array2.length) {
+            var compareValue = comparator(array1[i], array2[j]);
+            if (mergeNotIntersect || !compareValue)
+                result.push(compareValue <= 0 ? array1[i] : array2[j]);
+            if (compareValue <= 0)
+                i++;
+            if (compareValue >= 0)
+                j++;
+        }
+        if (mergeNotIntersect) {
+            while (i < array1.length)
+                result.push(array1[i++]);
+            while (j < array2.length)
+                result.push(array2[j++]);
+        }
+        return result;
+    }
 
+    Object.defineProperty(Array.prototype, "intersectOrdered", {
+        /**
+         * @param {!Array.<T>} array
+         * @param {function(T,T):number} comparator
+         * @return {!Array.<T>}
+         * @this {!Array.<T>}
+         * @template T
+         */
+        value: function(array, comparator)
+        {
+            return mergeOrIntersect(this, array, comparator, false);
+        }
+    });
 
-
-
-
-
-
-
-function mergeOrIntersect(array1,array2,comparator,mergeNotIntersect)
-{
-var result=[];
-var i=0;
-var j=0;
-while(i<array1.length&&j<array2.length){
-var compareValue=comparator(array1[i],array2[j]);
-if(mergeNotIntersect||!compareValue)
-result.push(compareValue<=0?array1[i]:array2[j]);
-if(compareValue<=0)
-i++;
-if(compareValue>=0)
-j++;
-}
-if(mergeNotIntersect){
-while(i<array1.length)
-result.push(array1[i++]);
-while(j<array2.length)
-result.push(array2[j++]);
-}
-return result;
-}
-
-Object.defineProperty(Array.prototype,"intersectOrdered",{
-
-
-
-
-
-
-
-value:function(array,comparator)
-{
-return mergeOrIntersect(this,array,comparator,false);
-}});
-
-
-Object.defineProperty(Array.prototype,"mergeOrdered",{
-
-
-
-
-
-
-
-value:function(array,comparator)
-{
-return mergeOrIntersect(this,array,comparator,true);
-}});
-
+    Object.defineProperty(Array.prototype, "mergeOrdered", {
+        /**
+         * @param {!Array.<T>} array
+         * @param {function(T,T):number} comparator
+         * @return {!Array.<T>}
+         * @this {!Array.<T>}
+         * @template T
+         */
+        value: function(array, comparator)
+        {
+            return mergeOrIntersect(this, array, comparator, true);
+        }
+    });
 })();
 
-
-
-
-
-
-String.sprintf=function(format,var_arg)
+/**
+ * @param {string} format
+ * @param {...*} var_arg
+ * @return {string}
+ */
+String.sprintf = function(format, var_arg)
 {
-return String.vsprintf(format,Array.prototype.slice.call(arguments,1));
-};
+    return String.vsprintf(format, Array.prototype.slice.call(arguments, 1));
+}
 
-
-
-
-
-
-String.tokenizeFormatString=function(format,formatters)
+/**
+ * @param {string} format
+ * @param {!Object.<string, function(string, ...):*>} formatters
+ * @return {!Array.<!Object>}
+ */
+String.tokenizeFormatString = function(format, formatters)
 {
-var tokens=[];
-var substitutionIndex=0;
+    var tokens = [];
+    var substitutionIndex = 0;
 
-function addStringToken(str)
+    function addStringToken(str)
+    {
+        if (tokens.length && tokens[tokens.length - 1].type === "string")
+            tokens[tokens.length - 1].value += str;
+        else
+            tokens.push({ type: "string", value: str });
+    }
+
+    function addSpecifierToken(specifier, precision, substitutionIndex)
+    {
+        tokens.push({ type: "specifier", specifier: specifier, precision: precision, substitutionIndex: substitutionIndex });
+    }
+
+    var index = 0;
+    for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) {
+        if (format.length === index)  // unescaped % sign at the end of the format string.
+            break;
+        addStringToken(format.substring(index, precentIndex));
+        index = precentIndex + 1;
+
+        if (format[index] === "%") {
+            // %% escape sequence.
+            addStringToken("%");
+            ++index;
+            continue;
+        }
+
+        if (String.isDigitAt(format, index)) {
+            // The first character is a number, it might be a substitution index.
+            var number = parseInt(format.substring(index), 10);
+            while (String.isDigitAt(format, index))
+                ++index;
+
+            // If the number is greater than zero and ends with a "$",
+            // then this is a substitution index.
+            if (number > 0 && format[index] === "$") {
+                substitutionIndex = (number - 1);
+                ++index;
+            }
+        }
+
+        var precision = -1;
+        if (format[index] === ".") {
+            // This is a precision specifier. If no digit follows the ".",
+            // then the precision should be zero.
+            ++index;
+            precision = parseInt(format.substring(index), 10);
+            if (isNaN(precision))
+                precision = 0;
+
+            while (String.isDigitAt(format, index))
+                ++index;
+        }
+
+        if (!(format[index] in formatters)) {
+            addStringToken(format.substring(precentIndex, index + 1));
+            ++index;
+            continue;
+        }
+
+        addSpecifierToken(format[index], precision, substitutionIndex);
+
+        ++substitutionIndex;
+        ++index;
+    }
+
+    addStringToken(format.substring(index));
+
+    return tokens;
+}
+
+String.standardFormatters = {
+    /**
+     * @return {number}
+     */
+    d: function(substitution)
+    {
+        return !isNaN(substitution) ? substitution : 0;
+    },
+
+    /**
+     * @return {number}
+     */
+    f: function(substitution, token)
+    {
+        if (substitution && token.precision > -1)
+            substitution = substitution.toFixed(token.precision);
+        return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(0).toFixed(token.precision) : 0);
+    },
+
+    /**
+     * @return {string}
+     */
+    s: function(substitution)
+    {
+        return substitution;
+    }
+}
+
+/**
+ * @param {string} format
+ * @param {!Array.<*>} substitutions
+ * @return {string}
+ */
+String.vsprintf = function(format, substitutions)
 {
-if(tokens.length&&tokens[tokens.length-1].type==="string")
-tokens[tokens.length-1].value+=str;else
-
-tokens.push({type:"string",value:str});
+    return String.format(format, substitutions, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult;
 }
 
-function addSpecifierToken(specifier,precision,substitutionIndex)
+/**
+ * @param {string} format
+ * @param {?ArrayLike} substitutions
+ * @param {!Object.<string, function(string, ...):Q>} formatters
+ * @param {!T} initialValue
+ * @param {function(T, Q): T|undefined} append
+ * @param {!Array.<!Object>=} tokenizedFormat
+ * @return {!{formattedResult: T, unusedSubstitutions: ?ArrayLike}};
+ * @template T, Q
+ */
+String.format = function(format, substitutions, formatters, initialValue, append, tokenizedFormat)
 {
-tokens.push({type:"specifier",specifier:specifier,precision:precision,substitutionIndex:substitutionIndex});
+    if (!format || !substitutions || !substitutions.length)
+        return { formattedResult: append(initialValue, format), unusedSubstitutions: substitutions };
+
+    function prettyFunctionName()
+    {
+        return "String.format(\"" + format + "\", \"" + Array.prototype.join.call(substitutions, "\", \"") + "\")";
+    }
+
+    function warn(msg)
+    {
+        console.warn(prettyFunctionName() + ": " + msg);
+    }
+
+    function error(msg)
+    {
+        console.error(prettyFunctionName() + ": " + msg);
+    }
+
+    var result = initialValue;
+    var tokens = tokenizedFormat || String.tokenizeFormatString(format, formatters);
+    var usedSubstitutionIndexes = {};
+
+    for (var i = 0; i < tokens.length; ++i) {
+        var token = tokens[i];
+
+        if (token.type === "string") {
+            result = append(result, token.value);
+            continue;
+        }
+
+        if (token.type !== "specifier") {
+            error("Unknown token type \"" + token.type + "\" found.");
+            continue;
+        }
+
+        if (token.substitutionIndex >= substitutions.length) {
+            // If there are not enough substitutions for the current substitutionIndex
+            // just output the format specifier literally and move on.
+            error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (token.substitutionIndex + 1) + ", so substitution was skipped.");
+            result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier);
+            continue;
+        }
+
+        usedSubstitutionIndexes[token.substitutionIndex] = true;
+
+        if (!(token.specifier in formatters)) {
+            // Encountered an unsupported format character, treat as a string.
+            warn("unsupported format character \u201C" + token.specifier + "\u201D. Treating as a string.");
+            result = append(result, substitutions[token.substitutionIndex]);
+            continue;
+        }
+
+        result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token));
+    }
+
+    var unusedSubstitutions = [];
+    for (var i = 0; i < substitutions.length; ++i) {
+        if (i in usedSubstitutionIndexes)
+            continue;
+        unusedSubstitutions.push(substitutions[i]);
+    }
+
+    return { formattedResult: result, unusedSubstitutions: unusedSubstitutions };
 }
 
-var index=0;
-for(var precentIndex=format.indexOf("%",index);precentIndex!==-1;precentIndex=format.indexOf("%",index)){
-if(format.length===index)
-break;
-addStringToken(format.substring(index,precentIndex));
-index=precentIndex+1;
-
-if(format[index]==="%"){
-
-addStringToken("%");
-++index;
-continue;
-}
-
-if(String.isDigitAt(format,index)){
-
-var number=parseInt(format.substring(index),10);
-while(String.isDigitAt(format,index))
-++index;
-
-
-
-if(number>0&&format[index]==="$"){
-substitutionIndex=number-1;
-++index;
-}
-}
-
-var precision=-1;
-if(format[index]==="."){
-
-
-++index;
-precision=parseInt(format.substring(index),10);
-if(isNaN(precision))
-precision=0;
-
-while(String.isDigitAt(format,index))
-++index;
-}
-
-if(!(format[index]in formatters)){
-addStringToken(format.substring(precentIndex,index+1));
-++index;
-continue;
-}
-
-addSpecifierToken(format[index],precision,substitutionIndex);
-
-++substitutionIndex;
-++index;
-}
-
-addStringToken(format.substring(index));
-
-return tokens;
-};
-
-String.standardFormatters={
-
-
-
-d:function(substitution)
+/**
+ * @param {string} query
+ * @param {boolean} caseSensitive
+ * @param {boolean} isRegex
+ * @return {!RegExp}
+ */
+function createSearchRegex(query, caseSensitive, isRegex)
 {
-return!isNaN(substitution)?substitution:0;
-},
+    var regexFlags = caseSensitive ? "g" : "gi";
+    var regexObject;
 
+    if (isRegex) {
+        try {
+            regexObject = new RegExp(query, regexFlags);
+        } catch (e) {
+            // Silent catch.
+        }
+    }
 
+    if (!regexObject)
+        regexObject = createPlainTextSearchRegex(query, regexFlags);
 
+    return regexObject;
+}
 
-f:function(substitution,token)
+/**
+ * @param {string} query
+ * @param {string=} flags
+ * @return {!RegExp}
+ */
+function createPlainTextSearchRegex(query, flags)
 {
-if(substitution&&token.precision>-1)
-substitution=substitution.toFixed(token.precision);
-return!isNaN(substitution)?substitution:token.precision>-1?Number(0).toFixed(token.precision):0;
-},
+    // This should be kept the same as the one in StringUtil.cpp.
+    var regexSpecialCharacters = String.regexSpecialCharacters();
+    var regex = "";
+    for (var i = 0; i < query.length; ++i) {
+        var c = query.charAt(i);
+        if (regexSpecialCharacters.indexOf(c) !== -1)
+            regex += "\\";
+        regex += c;
+    }
+    return new RegExp(regex, flags || "");
+}
 
-
-
-
-s:function(substitution)
+/**
+ * @param {!RegExp} regex
+ * @param {string} content
+ * @return {number}
+ */
+function countRegexMatches(regex, content)
 {
-return substitution;
-}};
-
-
-
-
-
-
-
-String.vsprintf=function(format,substitutions)
-{
-return String.format(format,substitutions,String.standardFormatters,"",function(a,b){return a+b;}).formattedResult;
-};
-
-
-
-
-
-
-
-
-
-
-
-String.format=function(format,substitutions,formatters,initialValue,append,tokenizedFormat)
-{
-if(!format||!substitutions||!substitutions.length)
-return{formattedResult:append(initialValue,format),unusedSubstitutions:substitutions};
-
-function prettyFunctionName()
-{
-return"String.format(\""+format+"\", \""+Array.prototype.join.call(substitutions,"\", \"")+"\")";
+    var text = content;
+    var result = 0;
+    var match;
+    while (text && (match = regex.exec(text))) {
+        if (match[0].length > 0)
+            ++result;
+        text = text.substring(match.index + 1);
+    }
+    return result;
 }
 
-function warn(msg)
-{
-console.warn(prettyFunctionName()+": "+msg);
-}
-
-function error(msg)
-{
-console.error(prettyFunctionName()+": "+msg);
-}
-
-var result=initialValue;
-var tokens=tokenizedFormat||String.tokenizeFormatString(format,formatters);
-var usedSubstitutionIndexes={};
-
-for(var i=0;i<tokens.length;++i){
-var token=tokens[i];
-
-if(token.type==="string"){
-result=append(result,token.value);
-continue;
-}
-
-if(token.type!=="specifier"){
-error("Unknown token type \""+token.type+"\" found.");
-continue;
-}
-
-if(token.substitutionIndex>=substitutions.length){
-
-
-error("not enough substitution arguments. Had "+substitutions.length+" but needed "+(token.substitutionIndex+1)+", so substitution was skipped.");
-result=append(result,"%"+(token.precision>-1?token.precision:"")+token.specifier);
-continue;
-}
-
-usedSubstitutionIndexes[token.substitutionIndex]=true;
-
-if(!(token.specifier in formatters)){
-
-warn("unsupported format character \u201C"+token.specifier+"\u201D. Treating as a string.");
-result=append(result,substitutions[token.substitutionIndex]);
-continue;
-}
-
-result=append(result,formatters[token.specifier](substitutions[token.substitutionIndex],token));
-}
-
-var unusedSubstitutions=[];
-for(var i=0;i<substitutions.length;++i){
-if(i in usedSubstitutionIndexes)
-continue;
-unusedSubstitutions.push(substitutions[i]);
-}
-
-return{formattedResult:result,unusedSubstitutions:unusedSubstitutions};
-};
-
-
-
-
-
-
-
-function createSearchRegex(query,caseSensitive,isRegex)
-{
-var regexFlags=caseSensitive?"g":"gi";
-var regexObject;
-
-if(isRegex){
-try{
-regexObject=new RegExp(query,regexFlags);
-}catch(e){
-
-}
-}
-
-if(!regexObject)
-regexObject=createPlainTextSearchRegex(query,regexFlags);
-
-return regexObject;
-}
-
-
-
-
-
-
-function createPlainTextSearchRegex(query,flags)
-{
-
-var regexSpecialCharacters=String.regexSpecialCharacters();
-var regex="";
-for(var i=0;i<query.length;++i){
-var c=query.charAt(i);
-if(regexSpecialCharacters.indexOf(c)!==-1)
-regex+="\\";
-regex+=c;
-}
-return new RegExp(regex,flags||"");
-}
-
-
-
-
-
-
-function countRegexMatches(regex,content)
-{
-var text=content;
-var result=0;
-var match;
-while(text&&(match=regex.exec(text))){
-if(match[0].length>0)
-++result;
-text=text.substring(match.index+1);
-}
-return result;
-}
-
-
-
-
-
+/**
+ * @param {number} spacesCount
+ * @return {string}
+ */
 function spacesPadding(spacesCount)
 {
-return"\u00a0".repeat(spacesCount);
+    return "\u00a0".repeat(spacesCount);
 }
 
-
-
-
-
-
-function numberToStringWithSpacesPadding(value,symbolsCount)
+/**
+ * @param {number} value
+ * @param {number} symbolsCount
+ * @return {string}
+ */
+function numberToStringWithSpacesPadding(value, symbolsCount)
 {
-var numberString=value.toString();
-var paddingLength=Math.max(0,symbolsCount-numberString.length);
-return spacesPadding(paddingLength)+numberString;
+    var numberString = value.toString();
+    var paddingLength = Math.max(0, symbolsCount - numberString.length);
+    return spacesPadding(paddingLength) + numberString;
 }
 
-
-
-
-
-Set.prototype.valuesArray=function()
+/**
+ * @return {!Array.<T>}
+ * @template T
+ */
+Set.prototype.valuesArray = function()
 {
-return Array.from(this.values());
-};
-
-
-
-
-
-Set.prototype.addAll=function(iterable)
-{
-for(var e of iterable)
-this.add(e);
-};
-
-
-
-
-
-
-Set.prototype.containsAll=function(iterable)
-{
-for(var e of iterable){
-if(!this.has(e))
-return false;
+    return Array.from(this.values());
 }
-return true;
-};
 
-
-
-
-
-Map.prototype.remove=function(key)
+/**
+ * @param {!Iterable<T>|!Array<!T>} iterable
+ * @template T
+ */
+Set.prototype.addAll = function(iterable)
 {
-var value=this.get(key);
-this.delete(key);
-return value;
-};
-
-
-
-
-Map.prototype.valuesArray=function()
-{
-return Array.from(this.values());
-};
-
-
-
-
-Map.prototype.keysArray=function()
-{
-return Array.from(this.keys());
-};
-
-
-
-
-Map.prototype.inverse=function()
-{
-var result=new Multimap();
-for(var key of this.keys()){
-var value=this.get(key);
-result.set(value,key);
+    for (var e of iterable)
+        this.add(e);
 }
-return result;
-};
 
-
-
-
-
-var Multimap=function()
+/**
+ * @param {!Iterable<T>|!Array<!T>} iterable
+ * @return {boolean}
+ * @template T
+ */
+Set.prototype.containsAll = function(iterable)
 {
-
-this._map=new Map();
-};
-
-Multimap.prototype={
-
-
-
-
-set:function(key,value)
-{
-var set=this._map.get(key);
-if(!set){
-set=new Set();
-this._map.set(key,set);
+    for (var e of iterable) {
+        if (!this.has(e))
+            return false;
+    }
+    return true;
 }
-set.add(value);
-},
 
-
-
-
-
-get:function(key)
+/**
+ * @return {T}
+ * @template T
+ */
+Map.prototype.remove = function(key)
 {
-var result=this._map.get(key);
-if(!result)
-result=new Set();
-return result;
-},
+    var value = this.get(key);
+    this.delete(key);
+    return value;
+}
 
-
-
-
-
-has:function(key)
+/**
+ * @return {!Array<!VALUE>}
+ */
+Map.prototype.valuesArray = function()
 {
-return this._map.has(key);
-},
+    return Array.from(this.values());
+}
 
-
-
-
-
-
-hasValue:function(key,value)
+/**
+ * @return {!Array<!KEY>}
+ */
+Map.prototype.keysArray = function()
 {
-var set=this._map.get(key);
-if(!set)
-return false;
-return set.has(value);
-},
+    return Array.from(this.keys());
+}
 
-
-
-
-get size()
+/**
+ * @return {!Multimap<!KEY, !VALUE>}
+ */
+Map.prototype.inverse = function()
 {
-return this._map.size;
-},
+    var result = new Multimap();
+    for (var key of this.keys()) {
+        var value = this.get(key);
+        result.set(value, key);
+    }
+    return result;
+}
 
-
-
-
-
-remove:function(key,value)
+/**
+ * @constructor
+ * @template K, V
+ */
+var Multimap = function()
 {
-var values=this.get(key);
-values.delete(value);
-if(!values.size)
-this._map.delete(key);
-},
+    /** @type {!Map.<K, !Set.<!V>>} */
+    this._map = new Map();
+}
 
+Multimap.prototype = {
+    /**
+     * @param {K} key
+     * @param {V} value
+     */
+    set: function(key, value)
+    {
+        var set = this._map.get(key);
+        if (!set) {
+            set = new Set();
+            this._map.set(key, set);
+        }
+        set.add(value);
+    },
 
+    /**
+     * @param {K} key
+     * @return {!Set.<!V>}
+     */
+    get: function(key)
+    {
+        var result = this._map.get(key);
+        if (!result)
+            result = new Set();
+        return result;
+    },
 
+    /**
+     * @param {K} key
+     * @return {boolean}
+     */
+    has: function(key)
+    {
+        return this._map.has(key);
+    },
 
-removeAll:function(key)
-{
-this._map.delete(key);
-},
+    /**
+     * @param {K} key
+     * @param {V} value
+     * @return {boolean}
+     */
+    hasValue: function(key, value)
+    {
+        var set = this._map.get(key);
+        if (!set)
+            return false;
+        return set.has(value);
+    },
 
+    /**
+     * @return {number}
+     */
+    get size()
+    {
+        return this._map.size;
+    },
 
+    /**
+     * @param {K} key
+     * @param {V} value
+     */
+    remove: function(key, value)
+    {
+        var values = this.get(key);
+        values.delete(value);
+        if (!values.size)
+            this._map.delete(key);
+    },
 
+    /**
+     * @param {K} key
+     */
+    removeAll: function(key)
+    {
+        this._map.delete(key);
+    },
 
-keysArray:function()
-{
-return this._map.keysArray();
-},
+    /**
+     * @return {!Array.<K>}
+     */
+    keysArray: function()
+    {
+        return this._map.keysArray();
+    },
 
+    /**
+     * @return {!Array.<!V>}
+     */
+    valuesArray: function()
+    {
+        var result = [];
+        var keys = this.keysArray();
+        for (var i = 0; i < keys.length; ++i)
+            result.pushAll(this.get(keys[i]).valuesArray());
+        return result;
+    },
 
+    clear: function()
+    {
+        this._map.clear();
+    }
+}
 
-
-valuesArray:function()
-{
-var result=[];
-var keys=this.keysArray();
-for(var i=0;i<keys.length;++i)
-result.pushAll(this.get(keys[i]).valuesArray());
-return result;
-},
-
-clear:function()
-{
-this._map.clear();
-}};
-
-
-
-
-
-
+/**
+ * @param {string} url
+ * @return {!Promise.<string>}
+ */
 function loadXHR(url)
 {
-return new Promise(load);
+    return new Promise(load);
 
-function load(successCallback,failureCallback)
-{
-function onReadyStateChanged()
-{
-if(xhr.readyState!==XMLHttpRequest.DONE)
-return;
-if(xhr.status!==200){
-xhr.onreadystatechange=null;
-failureCallback(new Error(xhr.status));
-return;
-}
-xhr.onreadystatechange=null;
-successCallback(xhr.responseText);
+    function load(successCallback, failureCallback)
+    {
+        function onReadyStateChanged()
+        {
+            if (xhr.readyState !== XMLHttpRequest.DONE)
+                return;
+            if (xhr.status !== 200) {
+                xhr.onreadystatechange = null;
+                failureCallback(new Error(xhr.status));
+                return;
+            }
+            xhr.onreadystatechange = null;
+            successCallback(xhr.responseText);
+        }
+
+        var xhr = new XMLHttpRequest();
+        xhr.withCredentials = false;
+        xhr.open("GET", url, true);
+        xhr.onreadystatechange = onReadyStateChanged;
+        xhr.send(null);
+    }
 }
 
-var xhr=new XMLHttpRequest();
-xhr.withCredentials=false;
-xhr.open("GET",url,true);
-xhr.onreadystatechange=onReadyStateChanged;
-xhr.send(null);
-}
-}
-
-
-
-
+/**
+ * @constructor
+ */
 function CallbackBarrier()
 {
-this._pendingIncomingCallbacksCount=0;
+    this._pendingIncomingCallbacksCount = 0;
 }
 
-CallbackBarrier.prototype={
+CallbackBarrier.prototype = {
+    /**
+     * @param {function(...)=} userCallback
+     * @return {function(...)}
+     */
+    createCallback: function(userCallback)
+    {
+        console.assert(!this._outgoingCallback, "CallbackBarrier.createCallback() is called after CallbackBarrier.callWhenDone()");
+        ++this._pendingIncomingCallbacksCount;
+        return this._incomingCallback.bind(this, userCallback);
+    },
 
+    /**
+     * @param {function()} callback
+     */
+    callWhenDone: function(callback)
+    {
+        console.assert(!this._outgoingCallback, "CallbackBarrier.callWhenDone() is called multiple times");
+        this._outgoingCallback = callback;
+        if (!this._pendingIncomingCallbacksCount)
+            this._outgoingCallback();
+    },
 
+    /**
+     * @return {!Promise.<undefined>}
+     */
+    donePromise: function()
+    {
+        return new Promise(promiseConstructor.bind(this));
 
+        /**
+         * @param {function()} success
+         * @this {CallbackBarrier}
+         */
+        function promiseConstructor(success)
+        {
+            this.callWhenDone(success);
+        }
+    },
 
-createCallback:function(userCallback)
-{
-console.assert(!this._outgoingCallback,"CallbackBarrier.createCallback() is called after CallbackBarrier.callWhenDone()");
-++this._pendingIncomingCallbacksCount;
-return this._incomingCallback.bind(this,userCallback);
-},
-
-
-
-
-callWhenDone:function(callback)
-{
-console.assert(!this._outgoingCallback,"CallbackBarrier.callWhenDone() is called multiple times");
-this._outgoingCallback=callback;
-if(!this._pendingIncomingCallbacksCount)
-this._outgoingCallback();
-},
-
-
-
-
-donePromise:function()
-{
-return new Promise(promiseConstructor.bind(this));
-
-
-
-
-
-function promiseConstructor(success)
-{
-this.callWhenDone(success);
+    /**
+     * @param {function(...)=} userCallback
+     */
+    _incomingCallback: function(userCallback)
+    {
+        console.assert(this._pendingIncomingCallbacksCount > 0);
+        if (userCallback) {
+            var args = Array.prototype.slice.call(arguments, 1);
+            userCallback.apply(null, args);
+        }
+        if (!--this._pendingIncomingCallbacksCount && this._outgoingCallback)
+            this._outgoingCallback();
+    }
 }
-},
 
-
-
-
-_incomingCallback:function(userCallback)
-{
-console.assert(this._pendingIncomingCallbacksCount>0);
-if(userCallback){
-var args=Array.prototype.slice.call(arguments,1);
-userCallback.apply(null,args);
-}
-if(! --this._pendingIncomingCallbacksCount&&this._outgoingCallback)
-this._outgoingCallback();
-}};
-
-
-
-
-
+/**
+ * @param {*} value
+ */
 function suppressUnused(value)
 {
 }
 
-
-
-
-
-self.setImmediate=function(callback)
+/**
+ * @param {function()} callback
+ * @return {number}
+ */
+self.setImmediate = function(callback)
 {
-Promise.resolve().then(callback);
-return 0;
-};
+    Promise.resolve().then(callback);
+    return 0;
+}
 
-
-
-
-
-
-Promise.prototype.spread=function(callback)
+/**
+ * @param {function(...?)} callback
+ * @return {!Promise.<T>}
+ * @template T
+ */
+Promise.prototype.spread = function(callback)
 {
-return this.then(spreadPromise);
+    return this.then(spreadPromise);
 
-function spreadPromise(arg)
+    function spreadPromise(arg)
+    {
+        return callback.apply(null, arg);
+    }
+}
+
+/**
+ * @param {T} defaultValue
+ * @return {!Promise.<T>}
+ * @template T
+ */
+Promise.prototype.catchException = function(defaultValue) {
+    return this.catch(function(error) {
+        console.error(error);
+        return defaultValue;
+    });
+}
+
+/**
+ * @param {!Map<number, ?>} other
+ * @param {function(!VALUE,?):boolean} isEqual
+ * @return {!{removed: !Array<!VALUE>, added: !Array<?>, equal: !Array<!VALUE>}}
+ * @this {Map<number, VALUE>}
+ */
+Map.prototype.diff = function(other, isEqual)
 {
-return callback.apply(null,arg);
+    var leftKeys = this.keysArray();
+    var rightKeys = other.keysArray();
+    leftKeys.sort((a, b) => a - b);
+    rightKeys.sort((a, b) => a - b);
+
+    var removed = [];
+    var added = [];
+    var equal = [];
+    var leftIndex = 0;
+    var rightIndex = 0;
+    while (leftIndex < leftKeys.length && rightIndex < rightKeys.length) {
+        var leftKey = leftKeys[leftIndex];
+        var rightKey = rightKeys[rightIndex];
+        if (leftKey === rightKey && isEqual(this.get(leftKey), other.get(rightKey))) {
+            equal.push(this.get(leftKey));
+            ++leftIndex;
+            ++rightIndex;
+            continue;
+        }
+        if (leftKey <= rightKey) {
+            removed.push(this.get(leftKey));
+            ++leftIndex;
+            continue;
+        }
+        added.push(other.get(rightKey));
+        ++rightIndex;
+    }
+    while (leftIndex < leftKeys.length) {
+        var leftKey = leftKeys[leftIndex++];
+        removed.push(this.get(leftKey));
+    }
+    while (rightIndex < rightKeys.length) {
+        var rightKey = rightKeys[rightIndex++];
+        added.push(other.get(rightKey));
+    }
+    return {
+        added: added,
+        removed: removed,
+        equal: equal
+    }
 }
-};
 
+},{}],246:[function(require,module,exports){
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
 
-
-
-
-
-Promise.prototype.catchException=function(defaultValue){
-return this.catch(function(error){
-console.error(error);
-return defaultValue;
-});
-};
-
-
-
-
-
-
-
-Map.prototype.diff=function(other,isEqual)
+/**
+ * @constructor
+ * @extends {WebInspector.ProfileNode}
+ * @param {!ProfilerAgent.ProfileNode} node
+ * @param {number} sampleTime
+ */
+WebInspector.CPUProfileNode = function(node, sampleTime)
 {
-var leftKeys=this.keysArray();
-var rightKeys=other.keysArray();
-leftKeys.sort((a,b)=>a-b);
-rightKeys.sort((a,b)=>a-b);
-
-var removed=[];
-var added=[];
-var equal=[];
-var leftIndex=0;
-var rightIndex=0;
-while(leftIndex<leftKeys.length&&rightIndex<rightKeys.length){
-var leftKey=leftKeys[leftIndex];
-var rightKey=rightKeys[rightIndex];
-if(leftKey===rightKey&&isEqual(this.get(leftKey),other.get(rightKey))){
-equal.push(this.get(leftKey));
-++leftIndex;
-++rightIndex;
-continue;
+    var callFrame = node.callFrame || /** @type {!RuntimeAgent.CallFrame} */ ({
+        // Backward compatibility for old SamplingHeapProfileNode format.
+        functionName: node["functionName"],
+        scriptId: node["scriptId"],
+        url: node["url"],
+        lineNumber: node["lineNumber"] - 1,
+        columnNumber: node["columnNumber"] - 1
+    });
+    WebInspector.ProfileNode.call(this, callFrame);
+    this.id = node.id;
+    this.self = node.hitCount * sampleTime;
+    this.positionTicks = node.positionTicks;
+    // Compatibility: legacy backends could provide "no reason" for optimized functions.
+    this.deoptReason = node.deoptReason && node.deoptReason !== "no reason" ? node.deoptReason : null;
 }
-if(leftKey<=rightKey){
-removed.push(this.get(leftKey));
-++leftIndex;
-continue;
+
+WebInspector.CPUProfileNode.prototype = {
+    __proto__: WebInspector.ProfileNode.prototype
 }
-added.push(other.get(rightKey));
-++rightIndex;
-}
-while(leftIndex<leftKeys.length){
-var leftKey=leftKeys[leftIndex++];
-removed.push(this.get(leftKey));
-}
-while(rightIndex<rightKeys.length){
-var rightKey=rightKeys[rightIndex++];
-added.push(other.get(rightKey));
-}
-return{
-added:added,
-removed:removed,
-equal:equal};
 
-};
-
-},{}],217:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-WebInspector.CPUProfileNode=function(node,sampleTime)
+/**
+ * @constructor
+ * @extends {WebInspector.ProfileTreeModel}
+ * @param {!ProfilerAgent.Profile} profile
+ */
+WebInspector.CPUProfileDataModel = function(profile)
 {
-var callFrame=node.callFrame||{
+    var isLegacyFormat = !!profile["head"];
+    if (isLegacyFormat) {
+        // Legacy format contains raw timestamps and start/stop times are in seconds.
+        this.profileStartTime = profile.startTime * 1000;
+        this.profileEndTime = profile.endTime * 1000;
+        this.timestamps = profile.timestamps;
+        this._compatibilityConversionHeadToNodes(profile);
+    } else {
+        // Current format encodes timestamps as deltas. Start/stop times are in microseconds.
+        this.profileStartTime = profile.startTime / 1000;
+        this.profileEndTime = profile.endTime / 1000;
+        this.timestamps = this._convertTimeDeltas(profile);
+    }
+    this.samples = profile.samples;
+    this.totalHitCount = 0;
+    this.profileHead = this._translateProfileTree(profile.nodes);
+    WebInspector.ProfileTreeModel.call(this, this.profileHead);
+    this._extractMetaNodes();
+    if (this.samples) {
+        this._buildIdToNodeMap();
+        this._sortSamples();
+        this._normalizeTimestamps();
+    }
+}
 
-functionName:node["functionName"],
-scriptId:node["scriptId"],
-url:node["url"],
-lineNumber:node["lineNumber"]-1,
-columnNumber:node["columnNumber"]-1};
+WebInspector.CPUProfileDataModel.prototype = {
+    /**
+     * @param {!ProfilerAgent.Profile} profile
+     */
+    _compatibilityConversionHeadToNodes: function(profile)
+    {
+        if (!profile.head || profile.nodes)
+            return;
+        /** @type {!Array<!ProfilerAgent.ProfileNode>} */
+        var nodes = [];
+        convertNodesTree(profile.head);
+        profile.nodes = nodes;
+        delete profile.head;
+        /**
+         * @param {!ProfilerAgent.ProfileNode} node
+         * @return {number}
+         */
+        function convertNodesTree(node)
+        {
+            nodes.push(node);
+            node.children = (/** @type {!Array<!ProfilerAgent.ProfileNode>} */(node.children)).map(convertNodesTree);
+            return node.id;
+        }
+    },
 
-WebInspector.ProfileNode.call(this,callFrame);
-this.id=node.id;
-this.self=node.hitCount*sampleTime;
-this.positionTicks=node.positionTicks;
+    /**
+     * @param {!ProfilerAgent.Profile} profile
+     * @return {?Array<number>}
+     */
+    _convertTimeDeltas: function(profile)
+    {
+        if (!profile.timeDeltas)
+            return null;
+        var lastTimeUsec = profile.startTime;
+        var timestamps = new Array(profile.timeDeltas.length);
+        for (var i = 0; i < timestamps.length; ++i) {
+            lastTimeUsec += profile.timeDeltas[i];
+            timestamps[i] = lastTimeUsec;
+        }
+        return timestamps;
+    },
 
-this.deoptReason=node.deoptReason&&node.deoptReason!=="no reason"?node.deoptReason:null;
-};
+    /**
+     * @param {!Array<!ProfilerAgent.ProfileNode>} nodes
+     * @return {!WebInspector.CPUProfileNode}
+     */
+    _translateProfileTree: function(nodes)
+    {
+        /**
+         * @param {!ProfilerAgent.ProfileNode} node
+         * @return {boolean}
+         */
+        function isNativeNode(node)
+        {
+            if (node.callFrame)
+                return !!node.callFrame.url && node.callFrame.url.startsWith("native ");
+            return !!node.url && node.url.startsWith("native ");
+        }
+        /**
+         * @param {!Array<!ProfilerAgent.ProfileNode>} nodes
+         */
+        function buildChildrenFromParents(nodes)
+        {
+            if (nodes[0].children)
+                return;
+            nodes[0].children = [];
+            for (var i = 1; i < nodes.length; ++i) {
+                var node = nodes[i];
+                var parentNode = nodeByIdMap.get(node.parent);
+                if (parentNode.children)
+                    parentNode.children.push(node.id);
+                else
+                    parentNode.children = [node.id];
+            }
+        }
+        /** @type {!Map<number, !ProfilerAgent.ProfileNode>} */
+        var nodeByIdMap = new Map();
+        for (var i = 0; i < nodes.length; ++i) {
+            var node = nodes[i];
+            nodeByIdMap.set(node.id, node);
+        }
+        buildChildrenFromParents(nodes);
+        this.totalHitCount = nodes.reduce((acc, node) => acc + node.hitCount, 0);
+        var sampleTime = (this.profileEndTime - this.profileStartTime) / this.totalHitCount;
+        var keepNatives = !!WebInspector.moduleSetting("showNativeFunctionsInJSProfile").get();
+        var root = nodes[0];
+        /** @type {!Map<number, number>} */
+        var idMap = new Map([[root.id, root.id]]);
+        var resultRoot = new WebInspector.CPUProfileNode(root, sampleTime);
+        var parentNodeStack = root.children.map(() => resultRoot);
+        var sourceNodeStack = root.children.map(id => nodeByIdMap.get(id));
+        while (sourceNodeStack.length) {
+            var parentNode = parentNodeStack.pop();
+            var sourceNode = sourceNodeStack.pop();
+            if (!sourceNode.children)
+                sourceNode.children = [];
+            var targetNode = new WebInspector.CPUProfileNode(sourceNode, sampleTime);
+            if (keepNatives || !isNativeNode(sourceNode)) {
+                parentNode.children.push(targetNode);
+                parentNode = targetNode;
+            } else {
+                parentNode.self += targetNode.self;
+            }
+            idMap.set(sourceNode.id, parentNode.id);
+            parentNodeStack.push.apply(parentNodeStack, sourceNode.children.map(() => parentNode));
+            sourceNodeStack.push.apply(sourceNodeStack, sourceNode.children.map(id => nodeByIdMap.get(id)));
+        }
+        if (this.samples)
+            this.samples = this.samples.map(id => idMap.get(id));
+        return resultRoot;
+    },
 
-WebInspector.CPUProfileNode.prototype={
-__proto__:WebInspector.ProfileNode.prototype};
+    _sortSamples: function()
+    {
+        var timestamps = this.timestamps;
+        if (!timestamps)
+            return;
+        var samples = this.samples;
+        var indices = timestamps.map((x, index) => index);
+        indices.sort((a, b) => timestamps[a] - timestamps[b]);
+        for (var i = 0; i < timestamps.length; ++i) {
+            var index = indices[i];
+            if (index === i)
+                continue;
+            // Move items in a cycle.
+            var savedTimestamp = timestamps[i];
+            var savedSample = samples[i];
+            var currentIndex = i;
+            while (index !== i) {
+                samples[currentIndex] = samples[index];
+                timestamps[currentIndex] = timestamps[index];
+                currentIndex = index;
+                index = indices[index];
+                indices[currentIndex] = currentIndex;
+            }
+            samples[currentIndex] = savedSample;
+            timestamps[currentIndex] = savedTimestamp;
+        }
+    },
 
+    _normalizeTimestamps: function()
+    {
+        var timestamps = this.timestamps;
+        if (!timestamps) {
+            // Support loading old CPU profiles that are missing timestamps.
+            // Derive timestamps from profile start and stop times.
+            var profileStartTime = this.profileStartTime;
+            var interval = (this.profileEndTime - profileStartTime) / this.samples.length;
+            timestamps = new Float64Array(this.samples.length + 1);
+            for (var i = 0; i < timestamps.length; ++i)
+                timestamps[i] = profileStartTime + i * interval;
+            this.timestamps = timestamps;
+            return;
+        }
 
+        // Convert samples from usec to msec
+        for (var i = 0; i < timestamps.length; ++i)
+            timestamps[i] /= 1000;
+        var averageSample = (timestamps.peekLast() - timestamps[0]) / (timestamps.length - 1);
+        // Add an extra timestamp used to calculate the last sample duration.
+        this.timestamps.push(timestamps.peekLast() + averageSample);
+        this.profileStartTime = timestamps[0];
+        this.profileEndTime = timestamps.peekLast();
+    },
 
+    _buildIdToNodeMap: function()
+    {
+        /** @type {!Map<number, !WebInspector.CPUProfileNode>} */
+        this._idToNode = new Map();
+        var idToNode = this._idToNode;
+        var stack = [this.profileHead];
+        while (stack.length) {
+            var node = stack.pop();
+            idToNode.set(node.id, node);
+            stack.push.apply(stack, node.children);
+        }
+    },
 
+    _extractMetaNodes: function()
+    {
+        var topLevelNodes = this.profileHead.children;
+        for (var i = 0; i < topLevelNodes.length && !(this.gcNode && this.programNode && this.idleNode); i++) {
+            var node = topLevelNodes[i];
+            if (node.functionName === "(garbage collector)")
+                this.gcNode = node;
+            else if (node.functionName === "(program)")
+                this.programNode = node;
+            else if (node.functionName === "(idle)")
+                this.idleNode = node;
+        }
+    },
 
+    /**
+     * @param {function(number, !WebInspector.CPUProfileNode, number)} openFrameCallback
+     * @param {function(number, !WebInspector.CPUProfileNode, number, number, number)} closeFrameCallback
+     * @param {number=} startTime
+     * @param {number=} stopTime
+     */
+    forEachFrame: function(openFrameCallback, closeFrameCallback, startTime, stopTime)
+    {
+        if (!this.profileHead || !this.samples)
+            return;
 
+        startTime = startTime || 0;
+        stopTime = stopTime || Infinity;
+        var samples = this.samples;
+        var timestamps = this.timestamps;
+        var idToNode = this._idToNode;
+        var gcNode = this.gcNode;
+        var samplesCount = samples.length;
+        var startIndex = timestamps.lowerBound(startTime);
+        var stackTop = 0;
+        var stackNodes = [];
+        var prevId = this.profileHead.id;
+        var sampleTime = timestamps[samplesCount];
+        var gcParentNode = null;
 
-WebInspector.CPUProfileDataModel=function(profile)
+        if (!this._stackStartTimes)
+            this._stackStartTimes = new Float64Array(this.maxDepth + 2);
+        var stackStartTimes = this._stackStartTimes;
+        if (!this._stackChildrenDuration)
+            this._stackChildrenDuration = new Float64Array(this.maxDepth + 2);
+        var stackChildrenDuration = this._stackChildrenDuration;
+
+        for (var sampleIndex = startIndex; sampleIndex < samplesCount; sampleIndex++) {
+            sampleTime = timestamps[sampleIndex];
+            if (sampleTime >= stopTime)
+                break;
+            var id = samples[sampleIndex];
+            if (id === prevId)
+                continue;
+            var node = idToNode.get(id);
+            var prevNode = idToNode.get(prevId);
+
+            if (node === gcNode) {
+                // GC samples have no stack, so we just put GC node on top of the last recorded sample.
+                gcParentNode = prevNode;
+                openFrameCallback(gcParentNode.depth + 1, gcNode, sampleTime);
+                stackStartTimes[++stackTop] = sampleTime;
+                stackChildrenDuration[stackTop] = 0;
+                prevId = id;
+                continue;
+            }
+            if (prevNode === gcNode) {
+                // end of GC frame
+                var start = stackStartTimes[stackTop];
+                var duration = sampleTime - start;
+                stackChildrenDuration[stackTop - 1] += duration;
+                closeFrameCallback(gcParentNode.depth + 1, gcNode, start, duration, duration - stackChildrenDuration[stackTop]);
+                --stackTop;
+                prevNode = gcParentNode;
+                prevId = prevNode.id;
+                gcParentNode = null;
+            }
+
+            while (node.depth > prevNode.depth) {
+                stackNodes.push(node);
+                node = node.parent;
+            }
+
+            // Go down to the LCA and close current intervals.
+            while (prevNode !== node) {
+                var start = stackStartTimes[stackTop];
+                var duration = sampleTime - start;
+                stackChildrenDuration[stackTop - 1] += duration;
+                closeFrameCallback(prevNode.depth, /** @type {!WebInspector.CPUProfileNode} */(prevNode), start, duration, duration - stackChildrenDuration[stackTop]);
+                --stackTop;
+                if (node.depth === prevNode.depth) {
+                    stackNodes.push(node);
+                    node = node.parent;
+                }
+                prevNode = prevNode.parent;
+            }
+
+            // Go up the nodes stack and open new intervals.
+            while (stackNodes.length) {
+                node = stackNodes.pop();
+                openFrameCallback(node.depth, node, sampleTime);
+                stackStartTimes[++stackTop] = sampleTime;
+                stackChildrenDuration[stackTop] = 0;
+            }
+
+            prevId = id;
+        }
+
+        if (idToNode.get(prevId) === gcNode) {
+            var start = stackStartTimes[stackTop];
+            var duration = sampleTime - start;
+            stackChildrenDuration[stackTop - 1] += duration;
+            closeFrameCallback(gcParentNode.depth + 1, node, start, duration, duration - stackChildrenDuration[stackTop]);
+            --stackTop;
+        }
+
+        for (var node = idToNode.get(prevId); node.parent; node = node.parent) {
+            var start = stackStartTimes[stackTop];
+            var duration = sampleTime - start;
+            stackChildrenDuration[stackTop - 1] += duration;
+            closeFrameCallback(node.depth, /** @type {!WebInspector.CPUProfileNode} */(node), start, duration, duration - stackChildrenDuration[stackTop]);
+            --stackTop;
+        }
+    },
+
+    /**
+     * @param {number} index
+     * @return {?WebInspector.CPUProfileNode}
+     */
+    nodeByIndex: function(index)
+    {
+        return this._idToNode.get(this.samples[index]) || null;
+    },
+
+    __proto__: WebInspector.ProfileTreeModel.prototype
+}
+
+},{}],247:[function(require,module,exports){
+/*
+ * Copyright (C) 2011 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @constructor
+ * @extends {WebInspector.SDKModel}
+ * @param {!WebInspector.Target} target
+ */
+WebInspector.NetworkManager = function(target)
 {
-var isLegacyFormat=!!profile["head"];
-if(isLegacyFormat){
+    WebInspector.SDKModel.call(this, WebInspector.NetworkManager, target);
+    this._dispatcher = new WebInspector.NetworkDispatcher(this);
+    this._target = target;
+    this._networkAgent = target.networkAgent();
+    target.registerNetworkDispatcher(this._dispatcher);
+    if (WebInspector.moduleSetting("cacheDisabled").get())
+        this._networkAgent.setCacheDisabled(true);
+    if (WebInspector.moduleSetting("monitoringXHREnabled").get())
+        this._networkAgent.setMonitoringXHREnabled(true);
 
-this.profileStartTime=profile.startTime*1000;
-this.profileEndTime=profile.endTime*1000;
-this.timestamps=profile.timestamps;
-this._compatibilityConversionHeadToNodes(profile);
-}else{
+    // Limit buffer when talking to a remote device.
+    if (Runtime.queryParam("remoteFrontend") || Runtime.queryParam("ws"))
+        this._networkAgent.enable(10000000, 5000000);
+    else
+        this._networkAgent.enable();
 
-this.profileStartTime=profile.startTime/1000;
-this.profileEndTime=profile.endTime/1000;
-this.timestamps=this._convertTimeDeltas(profile);
+    this._bypassServiceWorkerSetting = WebInspector.settings.createSetting("bypassServiceWorker", false);
+    if (this._bypassServiceWorkerSetting.get())
+        this._bypassServiceWorkerChanged();
+    this._bypassServiceWorkerSetting.addChangeListener(this._bypassServiceWorkerChanged, this);
+
+    WebInspector.moduleSetting("cacheDisabled").addChangeListener(this._cacheDisabledSettingChanged, this);
 }
-this.samples=profile.samples;
-this.totalHitCount=0;
-this.profileHead=this._translateProfileTree(profile.nodes);
-WebInspector.ProfileTreeModel.call(this,this.profileHead);
-this._extractMetaNodes();
-if(this.samples){
-this._buildIdToNodeMap();
-this._sortSamples();
-this._normalizeTimestamps();
+
+/** @enum {symbol} */
+WebInspector.NetworkManager.Events = {
+    RequestStarted: Symbol("RequestStarted"),
+    RequestUpdated: Symbol("RequestUpdated"),
+    RequestFinished: Symbol("RequestFinished"),
+    RequestUpdateDropped: Symbol("RequestUpdateDropped"),
+    ResponseReceived: Symbol("ResponseReceived")
 }
-};
 
-WebInspector.CPUProfileDataModel.prototype={
+WebInspector.NetworkManager._MIMETypes = {
+    "text/html":                   {"document": true},
+    "text/xml":                    {"document": true},
+    "text/plain":                  {"document": true},
+    "application/xhtml+xml":       {"document": true},
+    "image/svg+xml":               {"document": true},
+    "text/css":                    {"stylesheet": true},
+    "text/xsl":                    {"stylesheet": true},
+    "text/vtt":                    {"texttrack": true},
+}
 
-
-
-_compatibilityConversionHeadToNodes:function(profile)
+/**
+ * @param {!WebInspector.Target} target
+ * @return {?WebInspector.NetworkManager}
+ */
+WebInspector.NetworkManager.fromTarget = function(target)
 {
-if(!profile.head||profile.nodes)
-return;
-
-var nodes=[];
-convertNodesTree(profile.head);
-profile.nodes=nodes;
-delete profile.head;
-
-
-
-
-function convertNodesTree(node)
-{
-nodes.push(node);
-node.children=node.children.map(convertNodesTree);
-return node.id;
-}
-},
-
-
-
-
-
-_convertTimeDeltas:function(profile)
-{
-if(!profile.timeDeltas)
-return null;
-var lastTimeUsec=profile.startTime;
-var timestamps=new Array(profile.timeDeltas.length);
-for(var i=0;i<timestamps.length;++i){
-lastTimeUsec+=profile.timeDeltas[i];
-timestamps[i]=lastTimeUsec;
-}
-return timestamps;
-},
-
-
-
-
-
-_translateProfileTree:function(nodes)
-{
-
-
-
-
-function isNativeNode(node)
-{
-if(node.callFrame)
-return!!node.callFrame.url&&node.callFrame.url.startsWith("native ");
-return!!node.url&&node.url.startsWith("native ");
+    return /** @type {?WebInspector.NetworkManager} */ (target.model(WebInspector.NetworkManager));
 }
 
-
-
-function buildChildrenFromParents(nodes)
-{
-if(nodes[0].children)
-return;
-nodes[0].children=[];
-for(var i=1;i<nodes.length;++i){
-var node=nodes[i];
-var parentNode=nodeByIdMap.get(node.parent);
-if(parentNode.children)
-parentNode.children.push(node.id);else
-
-parentNode.children=[node.id];
-}
-}
-
-var nodeByIdMap=new Map();
-for(var i=0;i<nodes.length;++i){
-var node=nodes[i];
-nodeByIdMap.set(node.id,node);
-}
-buildChildrenFromParents(nodes);
-this.totalHitCount=nodes.reduce((acc,node)=>acc+node.hitCount,0);
-var sampleTime=(this.profileEndTime-this.profileStartTime)/this.totalHitCount;
-var keepNatives=!!WebInspector.moduleSetting("showNativeFunctionsInJSProfile").get();
-var root=nodes[0];
-
-var idMap=new Map([[root.id,root.id]]);
-var resultRoot=new WebInspector.CPUProfileNode(root,sampleTime);
-var parentNodeStack=root.children.map(()=>resultRoot);
-var sourceNodeStack=root.children.map(id=>nodeByIdMap.get(id));
-while(sourceNodeStack.length){
-var parentNode=parentNodeStack.pop();
-var sourceNode=sourceNodeStack.pop();
-if(!sourceNode.children)
-sourceNode.children=[];
-var targetNode=new WebInspector.CPUProfileNode(sourceNode,sampleTime);
-if(keepNatives||!isNativeNode(sourceNode)){
-parentNode.children.push(targetNode);
-parentNode=targetNode;
-}else{
-parentNode.self+=targetNode.self;
-}
-idMap.set(sourceNode.id,parentNode.id);
-parentNodeStack.push.apply(parentNodeStack,sourceNode.children.map(()=>parentNode));
-sourceNodeStack.push.apply(sourceNodeStack,sourceNode.children.map(id=>nodeByIdMap.get(id)));
-}
-if(this.samples)
-this.samples=this.samples.map(id=>idMap.get(id));
-return resultRoot;
-},
-
-_sortSamples:function()
-{
-var timestamps=this.timestamps;
-if(!timestamps)
-return;
-var samples=this.samples;
-var indices=timestamps.map((x,index)=>index);
-indices.sort((a,b)=>timestamps[a]-timestamps[b]);
-for(var i=0;i<timestamps.length;++i){
-var index=indices[i];
-if(index===i)
-continue;
-
-var savedTimestamp=timestamps[i];
-var savedSample=samples[i];
-var currentIndex=i;
-while(index!==i){
-samples[currentIndex]=samples[index];
-timestamps[currentIndex]=timestamps[index];
-currentIndex=index;
-index=indices[index];
-indices[currentIndex]=currentIndex;
-}
-samples[currentIndex]=savedSample;
-timestamps[currentIndex]=savedTimestamp;
-}
-},
-
-_normalizeTimestamps:function()
-{
-var timestamps=this.timestamps;
-if(!timestamps){
-
-
-var profileStartTime=this.profileStartTime;
-var interval=(this.profileEndTime-profileStartTime)/this.samples.length;
-timestamps=new Float64Array(this.samples.length+1);
-for(var i=0;i<timestamps.length;++i)
-timestamps[i]=profileStartTime+i*interval;
-this.timestamps=timestamps;
-return;
-}
-
-
-for(var i=0;i<timestamps.length;++i)
-timestamps[i]/=1000;
-var averageSample=(timestamps.peekLast()-timestamps[0])/(timestamps.length-1);
-
-this.timestamps.push(timestamps.peekLast()+averageSample);
-this.profileStartTime=timestamps[0];
-this.profileEndTime=timestamps.peekLast();
-},
-
-_buildIdToNodeMap:function()
-{
-
-this._idToNode=new Map();
-var idToNode=this._idToNode;
-var stack=[this.profileHead];
-while(stack.length){
-var node=stack.pop();
-idToNode.set(node.id,node);
-stack.push.apply(stack,node.children);
-}
-},
-
-_extractMetaNodes:function()
-{
-var topLevelNodes=this.profileHead.children;
-for(var i=0;i<topLevelNodes.length&&!(this.gcNode&&this.programNode&&this.idleNode);i++){
-var node=topLevelNodes[i];
-if(node.functionName==="(garbage collector)")
-this.gcNode=node;else
-if(node.functionName==="(program)")
-this.programNode=node;else
-if(node.functionName==="(idle)")
-this.idleNode=node;
-}
-},
-
-
-
-
-
-
-
-forEachFrame:function(openFrameCallback,closeFrameCallback,startTime,stopTime)
-{
-if(!this.profileHead||!this.samples)
-return;
-
-startTime=startTime||0;
-stopTime=stopTime||Infinity;
-var samples=this.samples;
-var timestamps=this.timestamps;
-var idToNode=this._idToNode;
-var gcNode=this.gcNode;
-var samplesCount=samples.length;
-var startIndex=timestamps.lowerBound(startTime);
-var stackTop=0;
-var stackNodes=[];
-var prevId=this.profileHead.id;
-var sampleTime=timestamps[samplesCount];
-var gcParentNode=null;
-
-if(!this._stackStartTimes)
-this._stackStartTimes=new Float64Array(this.maxDepth+2);
-var stackStartTimes=this._stackStartTimes;
-if(!this._stackChildrenDuration)
-this._stackChildrenDuration=new Float64Array(this.maxDepth+2);
-var stackChildrenDuration=this._stackChildrenDuration;
-
-for(var sampleIndex=startIndex;sampleIndex<samplesCount;sampleIndex++){
-sampleTime=timestamps[sampleIndex];
-if(sampleTime>=stopTime)
-break;
-var id=samples[sampleIndex];
-if(id===prevId)
-continue;
-var node=idToNode.get(id);
-var prevNode=idToNode.get(prevId);
-
-if(node===gcNode){
-
-gcParentNode=prevNode;
-openFrameCallback(gcParentNode.depth+1,gcNode,sampleTime);
-stackStartTimes[++stackTop]=sampleTime;
-stackChildrenDuration[stackTop]=0;
-prevId=id;
-continue;
-}
-if(prevNode===gcNode){
-
-var start=stackStartTimes[stackTop];
-var duration=sampleTime-start;
-stackChildrenDuration[stackTop-1]+=duration;
-closeFrameCallback(gcParentNode.depth+1,gcNode,start,duration,duration-stackChildrenDuration[stackTop]);
---stackTop;
-prevNode=gcParentNode;
-prevId=prevNode.id;
-gcParentNode=null;
-}
-
-while(node.depth>prevNode.depth){
-stackNodes.push(node);
-node=node.parent;
-}
-
-
-while(prevNode!==node){
-var start=stackStartTimes[stackTop];
-var duration=sampleTime-start;
-stackChildrenDuration[stackTop-1]+=duration;
-closeFrameCallback(prevNode.depth,prevNode,start,duration,duration-stackChildrenDuration[stackTop]);
---stackTop;
-if(node.depth===prevNode.depth){
-stackNodes.push(node);
-node=node.parent;
-}
-prevNode=prevNode.parent;
-}
-
-
-while(stackNodes.length){
-node=stackNodes.pop();
-openFrameCallback(node.depth,node,sampleTime);
-stackStartTimes[++stackTop]=sampleTime;
-stackChildrenDuration[stackTop]=0;
-}
-
-prevId=id;
-}
-
-if(idToNode.get(prevId)===gcNode){
-var start=stackStartTimes[stackTop];
-var duration=sampleTime-start;
-stackChildrenDuration[stackTop-1]+=duration;
-closeFrameCallback(gcParentNode.depth+1,node,start,duration,duration-stackChildrenDuration[stackTop]);
---stackTop;
-}
-
-for(var node=idToNode.get(prevId);node.parent;node=node.parent){
-var start=stackStartTimes[stackTop];
-var duration=sampleTime-start;
-stackChildrenDuration[stackTop-1]+=duration;
-closeFrameCallback(node.depth,node,start,duration,duration-stackChildrenDuration[stackTop]);
---stackTop;
-}
-},
-
-
-
-
-
-nodeByIndex:function(index)
-{
-return this._idToNode.get(this.samples[index])||null;
-},
-
-__proto__:WebInspector.ProfileTreeModel.prototype};
-
-
-},{}],218:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-WebInspector.NetworkManager=function(target)
-{
-WebInspector.SDKModel.call(this,WebInspector.NetworkManager,target);
-this._dispatcher=new WebInspector.NetworkDispatcher(this);
-this._target=target;
-this._networkAgent=target.networkAgent();
-target.registerNetworkDispatcher(this._dispatcher);
-if(WebInspector.moduleSetting("cacheDisabled").get())
-this._networkAgent.setCacheDisabled(true);
-if(WebInspector.moduleSetting("monitoringXHREnabled").get())
-this._networkAgent.setMonitoringXHREnabled(true);
-
-
-if(Runtime.queryParam("remoteFrontend")||Runtime.queryParam("ws"))
-this._networkAgent.enable(10000000,5000000);else
-
-this._networkAgent.enable();
-
-this._bypassServiceWorkerSetting=WebInspector.settings.createSetting("bypassServiceWorker",false);
-if(this._bypassServiceWorkerSetting.get())
-this._bypassServiceWorkerChanged();
-this._bypassServiceWorkerSetting.addChangeListener(this._bypassServiceWorkerChanged,this);
-
-WebInspector.moduleSetting("cacheDisabled").addChangeListener(this._cacheDisabledSettingChanged,this);
-};
-
-
-WebInspector.NetworkManager.Events={
-RequestStarted:Symbol("RequestStarted"),
-RequestUpdated:Symbol("RequestUpdated"),
-RequestFinished:Symbol("RequestFinished"),
-RequestUpdateDropped:Symbol("RequestUpdateDropped"),
-ResponseReceived:Symbol("ResponseReceived")};
-
-
-WebInspector.NetworkManager._MIMETypes={
-"text/html":{"document":true},
-"text/xml":{"document":true},
-"text/plain":{"document":true},
-"application/xhtml+xml":{"document":true},
-"image/svg+xml":{"document":true},
-"text/css":{"stylesheet":true},
-"text/xsl":{"stylesheet":true},
-"text/vtt":{"texttrack":true}};
-
-
-
-
-
-
-WebInspector.NetworkManager.fromTarget=function(target)
-{
-return target.model(WebInspector.NetworkManager);
-};
-
-
+/** @typedef {{download: number, upload: number, latency: number, title: string}} */
 WebInspector.NetworkManager.Conditions;
+/** @type {!WebInspector.NetworkManager.Conditions} */
+WebInspector.NetworkManager.NoThrottlingConditions = {title: WebInspector.UIString("No throttling"), download: -1, upload: -1, latency: 0};
+/** @type {!WebInspector.NetworkManager.Conditions} */
+WebInspector.NetworkManager.OfflineConditions = {title: WebInspector.UIString("Offline"), download: 0, upload: 0, latency: 0};
 
-WebInspector.NetworkManager.NoThrottlingConditions={title:WebInspector.UIString("No throttling"),download:-1,upload:-1,latency:0};
-
-WebInspector.NetworkManager.OfflineConditions={title:WebInspector.UIString("Offline"),download:0,upload:0,latency:0};
-
-
-
-
-
-
-WebInspector.NetworkManager._connectionType=function(conditions)
+/**
+ * @param {!WebInspector.NetworkManager.Conditions} conditions
+ * @return {!NetworkAgent.ConnectionType}
+ * TODO(allada): this belongs to NetworkConditionsSelector, which should hardcode/guess it.
+ */
+WebInspector.NetworkManager._connectionType = function(conditions)
 {
-if(!conditions.download&&!conditions.upload)
-return NetworkAgent.ConnectionType.None;
-var types=WebInspector.NetworkManager._connectionTypes;
-if(!types){
-WebInspector.NetworkManager._connectionTypes=[];
-types=WebInspector.NetworkManager._connectionTypes;
-types.push(["2g",NetworkAgent.ConnectionType.Cellular2g]);
-types.push(["3g",NetworkAgent.ConnectionType.Cellular3g]);
-types.push(["4g",NetworkAgent.ConnectionType.Cellular4g]);
-types.push(["bluetooth",NetworkAgent.ConnectionType.Bluetooth]);
-types.push(["wifi",NetworkAgent.ConnectionType.Wifi]);
-types.push(["wimax",NetworkAgent.ConnectionType.Wimax]);
-}
-for(var type of types){
-if(conditions.title.toLowerCase().indexOf(type[0])!==-1)
-return type[1];
-}
-return NetworkAgent.ConnectionType.Other;
-};
-
-WebInspector.NetworkManager.prototype={
-
-
-
-
-inflightRequestForURL:function(url)
-{
-return this._dispatcher._inflightRequestsByURL[url];
-},
-
-
-
-
-_cacheDisabledSettingChanged:function(event)
-{
-var enabled=event.data;
-this._networkAgent.setCacheDisabled(enabled);
-},
-
-dispose:function()
-{
-WebInspector.moduleSetting("cacheDisabled").removeChangeListener(this._cacheDisabledSettingChanged,this);
-},
-
-
-
-
-bypassServiceWorkerSetting:function()
-{
-return this._bypassServiceWorkerSetting;
-},
-
-_bypassServiceWorkerChanged:function()
-{
-this._networkAgent.setBypassServiceWorker(this._bypassServiceWorkerSetting.get());
-},
-
-__proto__:WebInspector.SDKModel.prototype};
-
-
-
-
-
-
-WebInspector.NetworkDispatcher=function(manager)
-{
-this._manager=manager;
-this._inflightRequestsById={};
-this._inflightRequestsByURL={};
-};
-
-WebInspector.NetworkDispatcher.prototype={
-
-
-
-
-_headersMapToHeadersArray:function(headersMap)
-{
-var result=[];
-for(var name in headersMap){
-var values=headersMap[name].split("\n");
-for(var i=0;i<values.length;++i)
-result.push({name:name,value:values[i]});
-}
-return result;
-},
-
-
-
-
-
-_updateNetworkRequestWithRequest:function(networkRequest,request)
-{
-networkRequest.requestMethod=request.method;
-networkRequest.setRequestHeaders(this._headersMapToHeadersArray(request.headers));
-networkRequest.requestFormData=request.postData;
-networkRequest.setInitialPriority(request.initialPriority);
-networkRequest.mixedContentType=request.mixedContentType||NetworkAgent.RequestMixedContentType.None;
-},
-
-
-
-
-
-_updateNetworkRequestWithResponse:function(networkRequest,response)
-{
-if(response.url&&networkRequest.url!==response.url)
-networkRequest.url=response.url;
-networkRequest.mimeType=response.mimeType;
-networkRequest.statusCode=response.status;
-networkRequest.statusText=response.statusText;
-networkRequest.responseHeaders=this._headersMapToHeadersArray(response.headers);
-if(response.encodedDataLength>=0)
-networkRequest.setTransferSize(response.encodedDataLength);
-if(response.headersText)
-networkRequest.responseHeadersText=response.headersText;
-if(response.requestHeaders){
-networkRequest.setRequestHeaders(this._headersMapToHeadersArray(response.requestHeaders));
-networkRequest.setRequestHeadersText(response.requestHeadersText||"");
+    if (!conditions.download && !conditions.upload)
+        return NetworkAgent.ConnectionType.None;
+    var types = WebInspector.NetworkManager._connectionTypes;
+    if (!types) {
+        WebInspector.NetworkManager._connectionTypes = [];
+        types = WebInspector.NetworkManager._connectionTypes;
+        types.push(["2g", NetworkAgent.ConnectionType.Cellular2g]);
+        types.push(["3g", NetworkAgent.ConnectionType.Cellular3g]);
+        types.push(["4g", NetworkAgent.ConnectionType.Cellular4g]);
+        types.push(["bluetooth", NetworkAgent.ConnectionType.Bluetooth]);
+        types.push(["wifi", NetworkAgent.ConnectionType.Wifi]);
+        types.push(["wimax", NetworkAgent.ConnectionType.Wimax]);
+    }
+    for (var type of types) {
+        if (conditions.title.toLowerCase().indexOf(type[0]) !== -1)
+            return type[1];
+    }
+    return NetworkAgent.ConnectionType.Other;
 }
 
-networkRequest.connectionReused=response.connectionReused;
-networkRequest.connectionId=String(response.connectionId);
-if(response.remoteIPAddress)
-networkRequest.setRemoteAddress(response.remoteIPAddress,response.remotePort||-1);
+WebInspector.NetworkManager.prototype = {
+    /**
+     * @param {string} url
+     * @return {!WebInspector.NetworkRequest}
+     */
+    inflightRequestForURL: function(url)
+    {
+        return this._dispatcher._inflightRequestsByURL[url];
+    },
 
-if(response.fromServiceWorker)
-networkRequest.fetchedViaServiceWorker=true;
+    /**
+     * @param {!WebInspector.Event} event
+     */
+    _cacheDisabledSettingChanged: function(event)
+    {
+        var enabled = /** @type {boolean} */ (event.data);
+        this._networkAgent.setCacheDisabled(enabled);
+    },
 
-if(response.fromDiskCache)
-networkRequest.setFromDiskCache();
-networkRequest.timing=response.timing;
+    dispose: function()
+    {
+        WebInspector.moduleSetting("cacheDisabled").removeChangeListener(this._cacheDisabledSettingChanged, this);
+    },
 
-networkRequest.protocol=response.protocol;
+    /**
+     * @return {!WebInspector.Setting}
+     */
+    bypassServiceWorkerSetting: function()
+    {
+        return this._bypassServiceWorkerSetting;
+    },
 
-networkRequest.setSecurityState(response.securityState);
+    _bypassServiceWorkerChanged: function()
+    {
+        this._networkAgent.setBypassServiceWorker(this._bypassServiceWorkerSetting.get());
+    },
 
-if(!this._mimeTypeIsConsistentWithType(networkRequest)){
-var consoleModel=this._manager._target.consoleModel;
-consoleModel.addMessage(new WebInspector.ConsoleMessage(consoleModel.target(),WebInspector.ConsoleMessage.MessageSource.Network,
-WebInspector.ConsoleMessage.MessageLevel.Log,
-WebInspector.UIString("Resource interpreted as %s but transferred with MIME type %s: \"%s\".",networkRequest.resourceType().title(),networkRequest.mimeType,networkRequest.url),
-WebInspector.ConsoleMessage.MessageType.Log,
-"",
-0,
-0,
-networkRequest.requestId));
+    __proto__: WebInspector.SDKModel.prototype
 }
 
-if(response.securityDetails)
-networkRequest.setSecurityDetails(response.securityDetails);
-},
-
-
-
-
-
-_mimeTypeIsConsistentWithType:function(networkRequest)
+/**
+ * @constructor
+ * @implements {NetworkAgent.Dispatcher}
+ */
+WebInspector.NetworkDispatcher = function(manager)
 {
-
-
-
-
-
-
-if(networkRequest.hasErrorStatusCode()||networkRequest.statusCode===304||networkRequest.statusCode===204)
-return true;
-
-var resourceType=networkRequest.resourceType();
-if(resourceType!==WebInspector.resourceTypes.Stylesheet&&
-resourceType!==WebInspector.resourceTypes.Document&&
-resourceType!==WebInspector.resourceTypes.TextTrack){
-return true;
+    this._manager = manager;
+    this._inflightRequestsById = {};
+    this._inflightRequestsByURL = {};
 }
 
-if(!networkRequest.mimeType)
-return true;
+WebInspector.NetworkDispatcher.prototype = {
+    /**
+     * @param {!NetworkAgent.Headers} headersMap
+     * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
+     */
+    _headersMapToHeadersArray: function(headersMap)
+    {
+        var result = [];
+        for (var name in headersMap) {
+            var values = headersMap[name].split("\n");
+            for (var i = 0; i < values.length; ++i)
+                result.push({name: name, value: values[i]});
+        }
+        return result;
+    },
 
-if(networkRequest.mimeType in WebInspector.NetworkManager._MIMETypes)
-return resourceType.name()in WebInspector.NetworkManager._MIMETypes[networkRequest.mimeType];
+    /**
+     * @param {!WebInspector.NetworkRequest} networkRequest
+     * @param {!NetworkAgent.Request} request
+     */
+    _updateNetworkRequestWithRequest: function(networkRequest, request)
+    {
+        networkRequest.requestMethod = request.method;
+        networkRequest.setRequestHeaders(this._headersMapToHeadersArray(request.headers));
+        networkRequest.requestFormData = request.postData;
+        networkRequest.setInitialPriority(request.initialPriority);
+        networkRequest.mixedContentType = request.mixedContentType || NetworkAgent.RequestMixedContentType.None;
+    },
 
-return false;
-},
+    /**
+     * @param {!WebInspector.NetworkRequest} networkRequest
+     * @param {!NetworkAgent.Response=} response
+     */
+    _updateNetworkRequestWithResponse: function(networkRequest, response)
+    {
+        if (response.url && networkRequest.url !== response.url)
+            networkRequest.url = response.url;
+        networkRequest.mimeType = response.mimeType;
+        networkRequest.statusCode = response.status;
+        networkRequest.statusText = response.statusText;
+        networkRequest.responseHeaders = this._headersMapToHeadersArray(response.headers);
+        if (response.encodedDataLength >= 0)
+            networkRequest.setTransferSize(response.encodedDataLength);
+        if (response.headersText)
+            networkRequest.responseHeadersText = response.headersText;
+        if (response.requestHeaders) {
+            networkRequest.setRequestHeaders(this._headersMapToHeadersArray(response.requestHeaders));
+            networkRequest.setRequestHeadersText(response.requestHeadersText || "");
+        }
 
+        networkRequest.connectionReused = response.connectionReused;
+        networkRequest.connectionId = String(response.connectionId);
+        if (response.remoteIPAddress)
+            networkRequest.setRemoteAddress(response.remoteIPAddress, response.remotePort || -1);
 
+        if (response.fromServiceWorker)
+            networkRequest.fetchedViaServiceWorker = true;
 
+        if (response.fromDiskCache)
+            networkRequest.setFromDiskCache();
+        networkRequest.timing = response.timing;
 
+        networkRequest.protocol = response.protocol;
 
+        networkRequest.setSecurityState(response.securityState);
 
+        if (!this._mimeTypeIsConsistentWithType(networkRequest)) {
+            var consoleModel = this._manager._target.consoleModel;
+            consoleModel.addMessage(new WebInspector.ConsoleMessage(consoleModel.target(), WebInspector.ConsoleMessage.MessageSource.Network,
+                WebInspector.ConsoleMessage.MessageLevel.Log,
+                WebInspector.UIString("Resource interpreted as %s but transferred with MIME type %s: \"%s\".", networkRequest.resourceType().title(), networkRequest.mimeType, networkRequest.url),
+                WebInspector.ConsoleMessage.MessageType.Log,
+                "",
+                0,
+                0,
+                networkRequest.requestId));
+        }
 
-resourceChangedPriority:function(requestId,newPriority,timestamp)
-{
-var networkRequest=this._inflightRequestsById[requestId];
-if(networkRequest)
-networkRequest.setPriority(newPriority);
-},
+        if (response.securityDetails)
+            networkRequest.setSecurityDetails(response.securityDetails);
+    },
 
+    /**
+     * @param {!WebInspector.NetworkRequest} networkRequest
+     * @return {boolean}
+     */
+    _mimeTypeIsConsistentWithType: function(networkRequest)
+    {
+        // If status is an error, content is likely to be of an inconsistent type,
+        // as it's going to be an error message. We do not want to emit a warning
+        // for this, though, as this will already be reported as resource loading failure.
+        // Also, if a URL like http://localhost/wiki/load.php?debug=true&lang=en produces text/css and gets reloaded,
+        // it is 304 Not Modified and its guessed mime-type is text/php, which is wrong.
+        // Don't check for mime-types in 304-resources.
+        if (networkRequest.hasErrorStatusCode() || networkRequest.statusCode === 304 || networkRequest.statusCode === 204)
+            return true;
 
+        var resourceType = networkRequest.resourceType();
+        if (resourceType !== WebInspector.resourceTypes.Stylesheet &&
+            resourceType !== WebInspector.resourceTypes.Document &&
+            resourceType !== WebInspector.resourceTypes.TextTrack) {
+            return true;
+        }
 
+        if (!networkRequest.mimeType)
+            return true; // Might be not known for cached resources with null responses.
 
+        if (networkRequest.mimeType in WebInspector.NetworkManager._MIMETypes)
+            return resourceType.name() in WebInspector.NetworkManager._MIMETypes[networkRequest.mimeType];
 
+        return false;
+    },
 
+    /**
+     * @override
+     * @param {!NetworkAgent.RequestId} requestId
+     * @param {!NetworkAgent.ResourcePriority} newPriority
+     * @param {!NetworkAgent.Timestamp} timestamp
+     */
+    resourceChangedPriority: function(requestId, newPriority, timestamp)
+    {
+        var networkRequest = this._inflightRequestsById[requestId];
+        if (networkRequest)
+            networkRequest.setPriority(newPriority);
+    },
 
+    /**
+     * @override
+     * @param {!NetworkAgent.RequestId} requestId
+     * @param {!PageAgent.FrameId} frameId
+     * @param {!NetworkAgent.LoaderId} loaderId
+     * @param {string} documentURL
+     * @param {!NetworkAgent.Request} request
+     * @param {!NetworkAgent.Timestamp} time
+     * @param {!NetworkAgent.Timestamp} wallTime
+     * @param {!NetworkAgent.Initiator} initiator
+     * @param {!NetworkAgent.Response=} redirectResponse
+     * @param {!PageAgent.ResourceType=} resourceType
+     */
+    requestWillBeSent: function(requestId, frameId, loaderId, documentURL, request, time, wallTime, initiator, redirectResponse, resourceType)
+    {
+        var networkRequest = this._inflightRequestsById[requestId];
+        if (networkRequest) {
+            // FIXME: move this check to the backend.
+            if (!redirectResponse)
+                return;
+            this.responseReceived(requestId, frameId, loaderId, time, PageAgent.ResourceType.Other, redirectResponse);
+            networkRequest = this._appendRedirect(requestId, time, request.url);
+        } else
+            networkRequest = this._createNetworkRequest(requestId, frameId, loaderId, request.url, documentURL, initiator);
+        networkRequest.hasNetworkData = true;
+        this._updateNetworkRequestWithRequest(networkRequest, request);
+        networkRequest.setIssueTime(time, wallTime);
+        networkRequest.setResourceType(WebInspector.resourceTypes[resourceType]);
 
+        this._startNetworkRequest(networkRequest);
+    },
 
+    /**
+     * @override
+     * @param {!NetworkAgent.RequestId} requestId
+     */
+    requestServedFromCache: function(requestId)
+    {
+        var networkRequest = this._inflightRequestsById[requestId];
+        if (!networkRequest)
+            return;
 
+        networkRequest.setFromMemoryCache();
+    },
 
+    /**
+     * @override
+     * @param {!NetworkAgent.RequestId} requestId
+     * @param {!PageAgent.FrameId} frameId
+     * @param {!NetworkAgent.LoaderId} loaderId
+     * @param {!NetworkAgent.Timestamp} time
+     * @param {!PageAgent.ResourceType} resourceType
+     * @param {!NetworkAgent.Response} response
+     */
+    responseReceived: function(requestId, frameId, loaderId, time, resourceType, response)
+    {
+        var networkRequest = this._inflightRequestsById[requestId];
+        if (!networkRequest) {
+            // We missed the requestWillBeSent.
+            var eventData = {};
+            eventData.url = response.url;
+            eventData.frameId = frameId;
+            eventData.loaderId = loaderId;
+            eventData.resourceType = resourceType;
+            eventData.mimeType = response.mimeType;
+            this._manager.dispatchEventToListeners(WebInspector.NetworkManager.Events.RequestUpdateDropped, eventData);
+            return;
+        }
 
+        networkRequest.responseReceivedTime = time;
+        networkRequest.setResourceType(WebInspector.resourceTypes[resourceType]);
 
+        this._updateNetworkRequestWithResponse(networkRequest, response);
 
-requestWillBeSent:function(requestId,frameId,loaderId,documentURL,request,time,wallTime,initiator,redirectResponse,resourceType)
-{
-var networkRequest=this._inflightRequestsById[requestId];
-if(networkRequest){
+        this._updateNetworkRequest(networkRequest);
+        this._manager.dispatchEventToListeners(WebInspector.NetworkManager.Events.ResponseReceived, networkRequest);
+    },
 
-if(!redirectResponse)
-return;
-this.responseReceived(requestId,frameId,loaderId,time,PageAgent.ResourceType.Other,redirectResponse);
-networkRequest=this._appendRedirect(requestId,time,request.url);
-}else
-networkRequest=this._createNetworkRequest(requestId,frameId,loaderId,request.url,documentURL,initiator);
-networkRequest.hasNetworkData=true;
-this._updateNetworkRequestWithRequest(networkRequest,request);
-networkRequest.setIssueTime(time,wallTime);
-networkRequest.setResourceType(WebInspector.resourceTypes[resourceType]);
+    /**
+     * @override
+     * @param {!NetworkAgent.RequestId} requestId
+     * @param {!NetworkAgent.Timestamp} time
+     * @param {number} dataLength
+     * @param {number} encodedDataLength
+     */
+    dataReceived: function(requestId, time, dataLength, encodedDataLength)
+    {
+        var networkRequest = this._inflightRequestsById[requestId];
+        if (!networkRequest)
+            return;
 
-this._startNetworkRequest(networkRequest);
-},
+        networkRequest.resourceSize += dataLength;
+        if (encodedDataLength !== -1)
+            networkRequest.increaseTransferSize(encodedDataLength);
+        networkRequest.endTime = time;
 
+        this._updateNetworkRequest(networkRequest);
+    },
 
+    /**
+     * @override
+     * @param {!NetworkAgent.RequestId} requestId
+     * @param {!NetworkAgent.Timestamp} finishTime
+     * @param {number} encodedDataLength
+     */
+    loadingFinished: function(requestId, finishTime, encodedDataLength)
+    {
+        var networkRequest = this._inflightRequestsById[requestId];
+        if (!networkRequest)
+            return;
+        this._finishNetworkRequest(networkRequest, finishTime, encodedDataLength);
+    },
 
+    /**
+     * @override
+     * @param {!NetworkAgent.RequestId} requestId
+     * @param {!NetworkAgent.Timestamp} time
+     * @param {!PageAgent.ResourceType} resourceType
+     * @param {string} localizedDescription
+     * @param {boolean=} canceled
+     * @param {!NetworkAgent.BlockedReason=} blockedReason
+     */
+    loadingFailed: function(requestId, time, resourceType, localizedDescription, canceled, blockedReason)
+    {
+        var networkRequest = this._inflightRequestsById[requestId];
+        if (!networkRequest)
+            return;
 
+        networkRequest.failed = true;
+        networkRequest.setResourceType(WebInspector.resourceTypes[resourceType]);
+        networkRequest.canceled = canceled;
+        if (blockedReason) {
+            networkRequest.setBlockedReason(blockedReason);
+            if (blockedReason === NetworkAgent.BlockedReason.Inspector) {
+                var consoleModel = this._manager._target.consoleModel;
+                consoleModel.addMessage(new WebInspector.ConsoleMessage(consoleModel.target(), WebInspector.ConsoleMessage.MessageSource.Network,
+                    WebInspector.ConsoleMessage.MessageLevel.Warning,
+                    WebInspector.UIString("Request was blocked by DevTools: \"%s\".", networkRequest.url),
+                    WebInspector.ConsoleMessage.MessageType.Log,
+                    "",
+                    0,
+                    0,
+                    networkRequest.requestId));
+            }
+        }
+        networkRequest.localizedFailDescription = localizedDescription;
+        this._finishNetworkRequest(networkRequest, time, -1);
+    },
 
-requestServedFromCache:function(requestId)
-{
-var networkRequest=this._inflightRequestsById[requestId];
-if(!networkRequest)
-return;
+    /**
+     * @override
+     * @param {!NetworkAgent.RequestId} requestId
+     * @param {string} requestURL
+     * @param {!NetworkAgent.Initiator=} initiator
+     */
+    webSocketCreated: function(requestId, requestURL, initiator)
+    {
+        var networkRequest = new WebInspector.NetworkRequest(this._manager._target, requestId, requestURL, "", "", "", initiator || null);
+        networkRequest.setResourceType(WebInspector.resourceTypes.WebSocket);
+        this._startNetworkRequest(networkRequest);
+    },
 
-networkRequest.setFromMemoryCache();
-},
+    /**
+     * @override
+     * @param {!NetworkAgent.RequestId} requestId
+     * @param {!NetworkAgent.Timestamp} time
+     * @param {!NetworkAgent.Timestamp} wallTime
+     * @param {!NetworkAgent.WebSocketRequest} request
+     */
+    webSocketWillSendHandshakeRequest: function(requestId, time, wallTime, request)
+    {
+        var networkRequest = this._inflightRequestsById[requestId];
+        if (!networkRequest)
+            return;
 
+        networkRequest.requestMethod = "GET";
+        networkRequest.setRequestHeaders(this._headersMapToHeadersArray(request.headers));
+        networkRequest.setIssueTime(time, wallTime);
 
+        this._updateNetworkRequest(networkRequest);
+    },
 
+    /**
+     * @override
+     * @param {!NetworkAgent.RequestId} requestId
+     * @param {!NetworkAgent.Timestamp} time
+     * @param {!NetworkAgent.WebSocketResponse} response
+     */
+    webSocketHandshakeResponseReceived: function(requestId, time, response)
+    {
+        var networkRequest = this._inflightRequestsById[requestId];
+        if (!networkRequest)
+            return;
 
+        networkRequest.statusCode = response.status;
+        networkRequest.statusText = response.statusText;
+        networkRequest.responseHeaders = this._headersMapToHeadersArray(response.headers);
+        networkRequest.responseHeadersText = response.headersText;
+        if (response.requestHeaders)
+            networkRequest.setRequestHeaders(this._headersMapToHeadersArray(response.requestHeaders));
+        if (response.requestHeadersText)
+            networkRequest.setRequestHeadersText(response.requestHeadersText);
+        networkRequest.responseReceivedTime = time;
+        networkRequest.protocol = "websocket";
 
+        this._updateNetworkRequest(networkRequest);
+    },
 
+    /**
+     * @override
+     * @param {!NetworkAgent.RequestId} requestId
+     * @param {!NetworkAgent.Timestamp} time
+     * @param {!NetworkAgent.WebSocketFrame} response
+     */
+    webSocketFrameReceived: function(requestId, time, response)
+    {
+        var networkRequest = this._inflightRequestsById[requestId];
+        if (!networkRequest)
+            return;
 
+        networkRequest.addFrame(response, time);
+        networkRequest.responseReceivedTime = time;
 
+        this._updateNetworkRequest(networkRequest);
+    },
 
+    /**
+     * @override
+     * @param {!NetworkAgent.RequestId} requestId
+     * @param {!NetworkAgent.Timestamp} time
+     * @param {!NetworkAgent.WebSocketFrame} response
+     */
+    webSocketFrameSent: function(requestId, time, response)
+    {
+        var networkRequest = this._inflightRequestsById[requestId];
+        if (!networkRequest)
+            return;
 
-responseReceived:function(requestId,frameId,loaderId,time,resourceType,response)
-{
-var networkRequest=this._inflightRequestsById[requestId];
-if(!networkRequest){
+        networkRequest.addFrame(response, time, true);
+        networkRequest.responseReceivedTime = time;
 
-var eventData={};
-eventData.url=response.url;
-eventData.frameId=frameId;
-eventData.loaderId=loaderId;
-eventData.resourceType=resourceType;
-eventData.mimeType=response.mimeType;
-this._manager.dispatchEventToListeners(WebInspector.NetworkManager.Events.RequestUpdateDropped,eventData);
-return;
+        this._updateNetworkRequest(networkRequest);
+    },
+
+    /**
+     * @override
+     * @param {!NetworkAgent.RequestId} requestId
+     * @param {!NetworkAgent.Timestamp} time
+     * @param {string} errorMessage
+     */
+    webSocketFrameError: function(requestId, time, errorMessage)
+    {
+        var networkRequest = this._inflightRequestsById[requestId];
+        if (!networkRequest)
+            return;
+
+        networkRequest.addFrameError(errorMessage, time);
+        networkRequest.responseReceivedTime = time;
+
+        this._updateNetworkRequest(networkRequest);
+    },
+
+    /**
+     * @override
+     * @param {!NetworkAgent.RequestId} requestId
+     * @param {!NetworkAgent.Timestamp} time
+     */
+    webSocketClosed: function(requestId, time)
+    {
+        var networkRequest = this._inflightRequestsById[requestId];
+        if (!networkRequest)
+            return;
+        this._finishNetworkRequest(networkRequest, time, -1);
+    },
+
+    /**
+     * @override
+     * @param {!NetworkAgent.RequestId} requestId
+     * @param {!NetworkAgent.Timestamp} time
+     * @param {string} eventName
+     * @param {string} eventId
+     * @param {string} data
+     */
+    eventSourceMessageReceived: function(requestId, time, eventName, eventId, data)
+    {
+        var networkRequest = this._inflightRequestsById[requestId];
+        if (!networkRequest)
+            return;
+        networkRequest.addEventSourceMessage(time, eventName, eventId, data);
+    },
+
+    /**
+     * @param {!NetworkAgent.RequestId} requestId
+     * @param {!NetworkAgent.Timestamp} time
+     * @param {string} redirectURL
+     * @return {!WebInspector.NetworkRequest}
+     */
+    _appendRedirect: function(requestId, time, redirectURL)
+    {
+        var originalNetworkRequest = this._inflightRequestsById[requestId];
+        var previousRedirects = originalNetworkRequest.redirects || [];
+        originalNetworkRequest.requestId = requestId + ":redirected." + previousRedirects.length;
+        delete originalNetworkRequest.redirects;
+        if (previousRedirects.length > 0)
+            originalNetworkRequest.redirectSource = previousRedirects[previousRedirects.length - 1];
+        this._finishNetworkRequest(originalNetworkRequest, time, -1);
+        var newNetworkRequest = this._createNetworkRequest(requestId, originalNetworkRequest.frameId, originalNetworkRequest.loaderId,
+             redirectURL, originalNetworkRequest.documentURL, originalNetworkRequest.initiator());
+        newNetworkRequest.redirects = previousRedirects.concat(originalNetworkRequest);
+        return newNetworkRequest;
+    },
+
+    /**
+     * @param {!WebInspector.NetworkRequest} networkRequest
+     */
+    _startNetworkRequest: function(networkRequest)
+    {
+        this._inflightRequestsById[networkRequest.requestId] = networkRequest;
+        this._inflightRequestsByURL[networkRequest.url] = networkRequest;
+        this._dispatchEventToListeners(WebInspector.NetworkManager.Events.RequestStarted, networkRequest);
+    },
+
+    /**
+     * @param {!WebInspector.NetworkRequest} networkRequest
+     */
+    _updateNetworkRequest: function(networkRequest)
+    {
+        this._dispatchEventToListeners(WebInspector.NetworkManager.Events.RequestUpdated, networkRequest);
+    },
+
+    /**
+     * @param {!WebInspector.NetworkRequest} networkRequest
+     * @param {!NetworkAgent.Timestamp} finishTime
+     * @param {number} encodedDataLength
+     */
+    _finishNetworkRequest: function(networkRequest, finishTime, encodedDataLength)
+    {
+        networkRequest.endTime = finishTime;
+        networkRequest.finished = true;
+        if (encodedDataLength >= 0)
+            networkRequest.setTransferSize(encodedDataLength);
+        this._dispatchEventToListeners(WebInspector.NetworkManager.Events.RequestFinished, networkRequest);
+        delete this._inflightRequestsById[networkRequest.requestId];
+        delete this._inflightRequestsByURL[networkRequest.url];
+    },
+
+    /**
+     * @param {string} eventType
+     * @param {!WebInspector.NetworkRequest} networkRequest
+     */
+    _dispatchEventToListeners: function(eventType, networkRequest)
+    {
+        this._manager.dispatchEventToListeners(eventType, networkRequest);
+    },
+
+    /**
+     * @param {!NetworkAgent.RequestId} requestId
+     * @param {string} frameId
+     * @param {!NetworkAgent.LoaderId} loaderId
+     * @param {string} url
+     * @param {string} documentURL
+     * @param {?NetworkAgent.Initiator} initiator
+     */
+    _createNetworkRequest: function(requestId, frameId, loaderId, url, documentURL, initiator)
+    {
+        return new WebInspector.NetworkRequest(this._manager._target, requestId, url, documentURL, frameId, loaderId, initiator);
+    }
 }
 
-networkRequest.responseReceivedTime=time;
-networkRequest.setResourceType(WebInspector.resourceTypes[resourceType]);
 
-this._updateNetworkRequestWithResponse(networkRequest,response);
-
-this._updateNetworkRequest(networkRequest);
-this._manager.dispatchEventToListeners(WebInspector.NetworkManager.Events.ResponseReceived,networkRequest);
-},
-
-
-
-
-
-
-
-
-dataReceived:function(requestId,time,dataLength,encodedDataLength)
+/**
+ * @constructor
+ * @extends {WebInspector.Object}
+ * @implements {WebInspector.TargetManager.Observer}
+ */
+WebInspector.MultitargetNetworkManager = function()
 {
-var networkRequest=this._inflightRequestsById[requestId];
-if(!networkRequest)
-return;
+    WebInspector.Object.call(this);
+    WebInspector.targetManager.observeTargets(this);
 
-networkRequest.resourceSize+=dataLength;
-if(encodedDataLength!==-1)
-networkRequest.increaseTransferSize(encodedDataLength);
-networkRequest.endTime=time;
+    /** @type {!Set<string>} */
+    this._blockedURLs = new Set();
+    this._blockedSetting = WebInspector.moduleSetting("blockedURLs");
+    this._blockedSetting.addChangeListener(this._updateBlockedURLs, this);
+    this._blockedSetting.set([]);
+    this._updateBlockedURLs();
 
-this._updateNetworkRequest(networkRequest);
-},
-
-
-
-
-
-
-
-loadingFinished:function(requestId,finishTime,encodedDataLength)
-{
-var networkRequest=this._inflightRequestsById[requestId];
-if(!networkRequest)
-return;
-this._finishNetworkRequest(networkRequest,finishTime,encodedDataLength);
-},
-
-
-
-
-
-
-
-
-
-
-loadingFailed:function(requestId,time,resourceType,localizedDescription,canceled,blockedReason)
-{
-var networkRequest=this._inflightRequestsById[requestId];
-if(!networkRequest)
-return;
-
-networkRequest.failed=true;
-networkRequest.setResourceType(WebInspector.resourceTypes[resourceType]);
-networkRequest.canceled=canceled;
-if(blockedReason){
-networkRequest.setBlockedReason(blockedReason);
-if(blockedReason===NetworkAgent.BlockedReason.Inspector){
-var consoleModel=this._manager._target.consoleModel;
-consoleModel.addMessage(new WebInspector.ConsoleMessage(consoleModel.target(),WebInspector.ConsoleMessage.MessageSource.Network,
-WebInspector.ConsoleMessage.MessageLevel.Warning,
-WebInspector.UIString("Request was blocked by DevTools: \"%s\".",networkRequest.url),
-WebInspector.ConsoleMessage.MessageType.Log,
-"",
-0,
-0,
-networkRequest.requestId));
+    this._userAgentOverride = "";
+    /** @type {!Set<!Protocol.NetworkAgent>} */
+    this._agents = new Set();
+    /** @type {!WebInspector.NetworkManager.Conditions} */
+    this._networkConditions = WebInspector.NetworkManager.NoThrottlingConditions;
 }
+
+/** @enum {symbol} */
+WebInspector.MultitargetNetworkManager.Events = {
+    ConditionsChanged: Symbol("ConditionsChanged"),
+    UserAgentChanged: Symbol("UserAgentChanged")
 }
-networkRequest.localizedFailDescription=localizedDescription;
-this._finishNetworkRequest(networkRequest,time,-1);
-},
 
-
-
-
-
-
-
-webSocketCreated:function(requestId,requestURL,initiator)
+/**
+ * @param {string} uaString
+ * @return {string}
+ */
+WebInspector.MultitargetNetworkManager.patchUserAgentWithChromeVersion = function(uaString)
 {
-var networkRequest=new WebInspector.NetworkRequest(this._manager._target,requestId,requestURL,"","","",initiator||null);
-networkRequest.setResourceType(WebInspector.resourceTypes.WebSocket);
-this._startNetworkRequest(networkRequest);
-},
-
-
-
-
-
-
-
-
-webSocketWillSendHandshakeRequest:function(requestId,time,wallTime,request)
-{
-var networkRequest=this._inflightRequestsById[requestId];
-if(!networkRequest)
-return;
-
-networkRequest.requestMethod="GET";
-networkRequest.setRequestHeaders(this._headersMapToHeadersArray(request.headers));
-networkRequest.setIssueTime(time,wallTime);
-
-this._updateNetworkRequest(networkRequest);
-},
-
-
-
-
-
-
-
-webSocketHandshakeResponseReceived:function(requestId,time,response)
-{
-var networkRequest=this._inflightRequestsById[requestId];
-if(!networkRequest)
-return;
-
-networkRequest.statusCode=response.status;
-networkRequest.statusText=response.statusText;
-networkRequest.responseHeaders=this._headersMapToHeadersArray(response.headers);
-networkRequest.responseHeadersText=response.headersText;
-if(response.requestHeaders)
-networkRequest.setRequestHeaders(this._headersMapToHeadersArray(response.requestHeaders));
-if(response.requestHeadersText)
-networkRequest.setRequestHeadersText(response.requestHeadersText);
-networkRequest.responseReceivedTime=time;
-networkRequest.protocol="websocket";
-
-this._updateNetworkRequest(networkRequest);
-},
-
-
-
-
-
-
-
-webSocketFrameReceived:function(requestId,time,response)
-{
-var networkRequest=this._inflightRequestsById[requestId];
-if(!networkRequest)
-return;
-
-networkRequest.addFrame(response,time);
-networkRequest.responseReceivedTime=time;
-
-this._updateNetworkRequest(networkRequest);
-},
-
-
-
-
-
-
-
-webSocketFrameSent:function(requestId,time,response)
-{
-var networkRequest=this._inflightRequestsById[requestId];
-if(!networkRequest)
-return;
-
-networkRequest.addFrame(response,time,true);
-networkRequest.responseReceivedTime=time;
-
-this._updateNetworkRequest(networkRequest);
-},
-
-
-
-
-
-
-
-webSocketFrameError:function(requestId,time,errorMessage)
-{
-var networkRequest=this._inflightRequestsById[requestId];
-if(!networkRequest)
-return;
-
-networkRequest.addFrameError(errorMessage,time);
-networkRequest.responseReceivedTime=time;
-
-this._updateNetworkRequest(networkRequest);
-},
-
-
-
-
-
-
-webSocketClosed:function(requestId,time)
-{
-var networkRequest=this._inflightRequestsById[requestId];
-if(!networkRequest)
-return;
-this._finishNetworkRequest(networkRequest,time,-1);
-},
-
-
-
-
-
-
-
-
-
-eventSourceMessageReceived:function(requestId,time,eventName,eventId,data)
-{
-var networkRequest=this._inflightRequestsById[requestId];
-if(!networkRequest)
-return;
-networkRequest.addEventSourceMessage(time,eventName,eventId,data);
-},
-
-
-
-
-
-
-
-_appendRedirect:function(requestId,time,redirectURL)
-{
-var originalNetworkRequest=this._inflightRequestsById[requestId];
-var previousRedirects=originalNetworkRequest.redirects||[];
-originalNetworkRequest.requestId=requestId+":redirected."+previousRedirects.length;
-delete originalNetworkRequest.redirects;
-if(previousRedirects.length>0)
-originalNetworkRequest.redirectSource=previousRedirects[previousRedirects.length-1];
-this._finishNetworkRequest(originalNetworkRequest,time,-1);
-var newNetworkRequest=this._createNetworkRequest(requestId,originalNetworkRequest.frameId,originalNetworkRequest.loaderId,
-redirectURL,originalNetworkRequest.documentURL,originalNetworkRequest.initiator());
-newNetworkRequest.redirects=previousRedirects.concat(originalNetworkRequest);
-return newNetworkRequest;
-},
-
-
-
-
-_startNetworkRequest:function(networkRequest)
-{
-this._inflightRequestsById[networkRequest.requestId]=networkRequest;
-this._inflightRequestsByURL[networkRequest.url]=networkRequest;
-this._dispatchEventToListeners(WebInspector.NetworkManager.Events.RequestStarted,networkRequest);
-},
-
-
-
-
-_updateNetworkRequest:function(networkRequest)
-{
-this._dispatchEventToListeners(WebInspector.NetworkManager.Events.RequestUpdated,networkRequest);
-},
-
-
-
-
-
-
-_finishNetworkRequest:function(networkRequest,finishTime,encodedDataLength)
-{
-networkRequest.endTime=finishTime;
-networkRequest.finished=true;
-if(encodedDataLength>=0)
-networkRequest.setTransferSize(encodedDataLength);
-this._dispatchEventToListeners(WebInspector.NetworkManager.Events.RequestFinished,networkRequest);
-delete this._inflightRequestsById[networkRequest.requestId];
-delete this._inflightRequestsByURL[networkRequest.url];
-},
-
-
-
-
-
-_dispatchEventToListeners:function(eventType,networkRequest)
-{
-this._manager.dispatchEventToListeners(eventType,networkRequest);
-},
-
-
-
-
-
-
-
-
-
-_createNetworkRequest:function(requestId,frameId,loaderId,url,documentURL,initiator)
-{
-return new WebInspector.NetworkRequest(this._manager._target,requestId,url,documentURL,frameId,loaderId,initiator);
-}};
-
-
-
-
-
-
-
-
-WebInspector.MultitargetNetworkManager=function()
-{
-WebInspector.Object.call(this);
-WebInspector.targetManager.observeTargets(this);
-
-
-this._blockedURLs=new Set();
-this._blockedSetting=WebInspector.moduleSetting("blockedURLs");
-this._blockedSetting.addChangeListener(this._updateBlockedURLs,this);
-this._blockedSetting.set([]);
-this._updateBlockedURLs();
-
-this._userAgentOverride="";
-
-this._agents=new Set();
-
-this._networkConditions=WebInspector.NetworkManager.NoThrottlingConditions;
-};
-
-
-WebInspector.MultitargetNetworkManager.Events={
-ConditionsChanged:Symbol("ConditionsChanged"),
-UserAgentChanged:Symbol("UserAgentChanged")};
-
-
-
-
-
-
-WebInspector.MultitargetNetworkManager.patchUserAgentWithChromeVersion=function(uaString)
-{
-
-var chromeRegex=new RegExp("(?:^|\\W)Chrome/(\\S+)");
-var chromeMatch=navigator.userAgent.match(chromeRegex);
-if(chromeMatch&&chromeMatch.length>1)
-return String.sprintf(uaString,chromeMatch[1]);
-return uaString;
-};
-
-WebInspector.MultitargetNetworkManager.prototype={
-
-
-
-
-targetAdded:function(target)
-{
-var networkAgent=target.networkAgent();
-if(this._extraHeaders)
-networkAgent.setExtraHTTPHeaders(this._extraHeaders);
-if(this._currentUserAgent())
-networkAgent.setUserAgentOverride(this._currentUserAgent());
-for(var url of this._blockedURLs)
-networkAgent.addBlockedURL(url);
-this._agents.add(networkAgent);
-if(this.isThrottling())
-this._updateNetworkConditions(networkAgent);
-},
-
-
-
-
-
-targetRemoved:function(target)
-{
-this._agents.delete(target.networkAgent());
-},
-
-
-
-
-isThrottling:function()
-{
-return this._networkConditions.download>=0||this._networkConditions.upload>=0||this._networkConditions.latency>0;
-},
-
-
-
-
-isOffline:function()
-{
-return!this._networkConditions.download&&!this._networkConditions.upload;
-},
-
-
-
-
-setNetworkConditions:function(conditions)
-{
-this._networkConditions=conditions;
-for(var agent of this._agents)
-this._updateNetworkConditions(agent);
-this.dispatchEventToListeners(WebInspector.MultitargetNetworkManager.Events.ConditionsChanged);
-},
-
-
-
-
-networkConditions:function()
-{
-return this._networkConditions;
-},
-
-
-
-
-_updateNetworkConditions:function(networkAgent)
-{
-var conditions=this._networkConditions;
-if(!this.isThrottling()){
-networkAgent.emulateNetworkConditions(false,0,0,0);
-}else{
-networkAgent.emulateNetworkConditions(this.isOffline(),conditions.latency,conditions.download<0?0:conditions.download,conditions.upload<0?0:conditions.upload,WebInspector.NetworkManager._connectionType(conditions));
+    // Patches Chrome/CriOS version from user agent ("1.2.3.4" when user agent is: "Chrome/1.2.3.4").
+    var chromeRegex = new RegExp("(?:^|\\W)Chrome/(\\S+)");
+    var chromeMatch = navigator.userAgent.match(chromeRegex);
+    if (chromeMatch && chromeMatch.length > 1)
+        return String.sprintf(uaString, chromeMatch[1]);
+    return uaString;
 }
-},
 
+WebInspector.MultitargetNetworkManager.prototype = {
+    /**
+     * @override
+     * @param {!WebInspector.Target} target
+     */
+    targetAdded: function(target)
+    {
+        var networkAgent = target.networkAgent();
+        if (this._extraHeaders)
+            networkAgent.setExtraHTTPHeaders(this._extraHeaders);
+        if (this._currentUserAgent())
+            networkAgent.setUserAgentOverride(this._currentUserAgent());
+        for (var url of this._blockedURLs)
+            networkAgent.addBlockedURL(url);
+        this._agents.add(networkAgent);
+        if (this.isThrottling())
+            this._updateNetworkConditions(networkAgent);
+    },
 
+    /**
+     * @override
+     * @param {!WebInspector.Target} target
+     */
+    targetRemoved: function(target)
+    {
+        this._agents.delete(target.networkAgent());
+    },
 
+    /**
+     * @return {boolean}
+     */
+    isThrottling: function()
+    {
+        return this._networkConditions.download >= 0 || this._networkConditions.upload >= 0 || this._networkConditions.latency > 0;
+    },
 
-setExtraHTTPHeaders:function(headers)
-{
-this._extraHeaders=headers;
-for(var target of WebInspector.targetManager.targets())
-target.networkAgent().setExtraHTTPHeaders(this._extraHeaders);
-},
+    /**
+     * @return {boolean}
+     */
+    isOffline: function()
+    {
+        return !this._networkConditions.download && !this._networkConditions.upload;
+    },
 
+    /**
+     * @param {!WebInspector.NetworkManager.Conditions} conditions
+     */
+    setNetworkConditions: function(conditions)
+    {
+        this._networkConditions = conditions;
+        for (var agent of this._agents)
+            this._updateNetworkConditions(agent);
+        this.dispatchEventToListeners(WebInspector.MultitargetNetworkManager.Events.ConditionsChanged);
+    },
 
+    /**
+     * @return {!WebInspector.NetworkManager.Conditions}
+     */
+    networkConditions: function()
+    {
+        return this._networkConditions;
+    },
 
+    /**
+     * @param {!Protocol.NetworkAgent} networkAgent
+     */
+    _updateNetworkConditions: function(networkAgent)
+    {
+        var conditions = this._networkConditions;
+        if (!this.isThrottling()) {
+            networkAgent.emulateNetworkConditions(false, 0, 0, 0);
+        } else {
+            networkAgent.emulateNetworkConditions(this.isOffline(), conditions.latency, conditions.download < 0 ? 0 : conditions.download, conditions.upload < 0 ? 0 : conditions.upload, WebInspector.NetworkManager._connectionType(conditions));
+        }
+    },
 
-_currentUserAgent:function()
-{
-return this._customUserAgent?this._customUserAgent:this._userAgentOverride;
-},
+    /**
+     * @param {!NetworkAgent.Headers} headers
+     */
+    setExtraHTTPHeaders: function(headers)
+    {
+        this._extraHeaders = headers;
+        for (var target of WebInspector.targetManager.targets())
+            target.networkAgent().setExtraHTTPHeaders(this._extraHeaders);
+    },
 
-_updateUserAgentOverride:function()
-{
-var userAgent=this._currentUserAgent();
-WebInspector.ResourceLoader.targetUserAgent=userAgent;
-for(var target of WebInspector.targetManager.targets())
-target.networkAgent().setUserAgentOverride(userAgent);
-},
+    /**
+     * @return {string}
+     */
+    _currentUserAgent: function()
+    {
+        return this._customUserAgent ? this._customUserAgent : this._userAgentOverride;
+    },
 
+    _updateUserAgentOverride: function()
+    {
+        var userAgent = this._currentUserAgent();
+        WebInspector.ResourceLoader.targetUserAgent = userAgent;
+        for (var target of WebInspector.targetManager.targets())
+            target.networkAgent().setUserAgentOverride(userAgent);
+    },
 
+    /**
+     * @param {string} userAgent
+     */
+    setUserAgentOverride: function(userAgent)
+    {
+        if (this._userAgentOverride === userAgent)
+            return;
+        this._userAgentOverride = userAgent;
+        if (!this._customUserAgent)
+            this._updateUserAgentOverride();
+        this.dispatchEventToListeners(WebInspector.MultitargetNetworkManager.Events.UserAgentChanged);
+    },
 
+    /**
+     * @return {string}
+     */
+    userAgentOverride: function()
+    {
+        return this._userAgentOverride;
+    },
 
-setUserAgentOverride:function(userAgent)
-{
-if(this._userAgentOverride===userAgent)
-return;
-this._userAgentOverride=userAgent;
-if(!this._customUserAgent)
-this._updateUserAgentOverride();
-this.dispatchEventToListeners(WebInspector.MultitargetNetworkManager.Events.UserAgentChanged);
-},
+    /**
+     * @param {string} userAgent
+     */
+    setCustomUserAgentOverride: function(userAgent)
+    {
+        this._customUserAgent = userAgent;
+        this._updateUserAgentOverride();
+    },
 
+    _updateBlockedURLs: function()
+    {
+        var blocked = this._blockedSetting.get();
+        for (var url of blocked) {
+            if (!this._blockedURLs.has(url))
+                this._addBlockedURL(url);
+        }
+        for (var url of this._blockedURLs) {
+            if (blocked.indexOf(url) === -1)
+                this._removeBlockedURL(url);
+        }
+    },
 
+    /**
+     * @param {string} url
+     */
+    _addBlockedURL: function(url)
+    {
+        this._blockedURLs.add(url);
+        for (var target of WebInspector.targetManager.targets())
+            target.networkAgent().addBlockedURL(url);
+    },
 
+    /**
+     * @param {string} url
+     */
+    _removeBlockedURL: function(url)
+    {
+        this._blockedURLs.delete(url);
+        for (var target of WebInspector.targetManager.targets())
+            target.networkAgent().removeBlockedURL(url);
+    },
 
-userAgentOverride:function()
-{
-return this._userAgentOverride;
-},
+    clearBrowserCache: function()
+    {
+        for (var target of WebInspector.targetManager.targets())
+            target.networkAgent().clearBrowserCache();
+    },
 
+    clearBrowserCookies: function()
+    {
+        for (var target of WebInspector.targetManager.targets())
+            target.networkAgent().clearBrowserCookies();
+    },
 
+    /**
+     * @param {string} origin
+     * @param {function(!Array<string>)} callback
+     */
+    getCertificate: function(origin, callback)
+    {
+        var target = WebInspector.targetManager.mainTarget();
+        target.networkAgent().getCertificate(origin, mycallback);
 
+        /**
+         * @param {?Protocol.Error} error
+         * @param {!Array<string>} certificate
+         */
+        function mycallback(error, certificate)
+        {
+            callback(error ? [] : certificate);
+        }
+    },
 
-setCustomUserAgentOverride:function(userAgent)
-{
-this._customUserAgent=userAgent;
-this._updateUserAgentOverride();
-},
+    /**
+     * @param {string} url
+     * @param {function(number, !Object.<string, string>, string)} callback
+     */
+    loadResource: function(url, callback)
+    {
+        var headers = {};
 
-_updateBlockedURLs:function()
-{
-var blocked=this._blockedSetting.get();
-for(var url of blocked){
-if(!this._blockedURLs.has(url))
-this._addBlockedURL(url);
+        var currentUserAgent = this._currentUserAgent();
+        if (currentUserAgent)
+            headers["User-Agent"] = currentUserAgent;
+
+        if (WebInspector.moduleSetting("cacheDisabled").get())
+            headers["Cache-Control"] = "no-cache";
+
+        WebInspector.ResourceLoader.load(url, headers, callback);
+    },
+
+    __proto__: WebInspector.Object.prototype
 }
-for(var url of this._blockedURLs){
-if(blocked.indexOf(url)===-1)
-this._removeBlockedURL(url);
-}
-},
 
-
-
-
-_addBlockedURL:function(url)
-{
-this._blockedURLs.add(url);
-for(var target of WebInspector.targetManager.targets())
-target.networkAgent().addBlockedURL(url);
-},
-
-
-
-
-_removeBlockedURL:function(url)
-{
-this._blockedURLs.delete(url);
-for(var target of WebInspector.targetManager.targets())
-target.networkAgent().removeBlockedURL(url);
-},
-
-clearBrowserCache:function()
-{
-for(var target of WebInspector.targetManager.targets())
-target.networkAgent().clearBrowserCache();
-},
-
-clearBrowserCookies:function()
-{
-for(var target of WebInspector.targetManager.targets())
-target.networkAgent().clearBrowserCookies();
-},
-
-
-
-
-
-getCertificate:function(origin,callback)
-{
-var target=WebInspector.targetManager.mainTarget();
-target.networkAgent().getCertificate(origin,mycallback);
-
-
-
-
-
-function mycallback(error,certificate)
-{
-callback(error?[]:certificate);
-}
-},
-
-
-
-
-
-loadResource:function(url,callback)
-{
-var headers={};
-
-var currentUserAgent=this._currentUserAgent();
-if(currentUserAgent)
-headers["User-Agent"]=currentUserAgent;
-
-if(WebInspector.moduleSetting("cacheDisabled").get())
-headers["Cache-Control"]="no-cache";
-
-WebInspector.ResourceLoader.load(url,headers,callback);
-},
-
-__proto__:WebInspector.Object.prototype};
-
-
-
-
-
+/**
+ * @type {!WebInspector.MultitargetNetworkManager}
+ */
 WebInspector.multitargetNetworkManager;
 
-},{}],219:[function(require,module,exports){
+},{}],248:[function(require,module,exports){
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-WebInspector.NetworkRequest=function(target,requestId,url,documentURL,frameId,loaderId,initiator)
+/**
+ * @constructor
+ * @extends {WebInspector.SDKObject}
+ * @implements {WebInspector.ContentProvider}
+ * @param {!NetworkAgent.RequestId} requestId
+ * @param {!WebInspector.Target} target
+ * @param {string} url
+ * @param {string} documentURL
+ * @param {!PageAgent.FrameId} frameId
+ * @param {!NetworkAgent.LoaderId} loaderId
+ * @param {?NetworkAgent.Initiator} initiator
+ */
+WebInspector.NetworkRequest = function(target, requestId, url, documentURL, frameId, loaderId, initiator)
 {
-WebInspector.SDKObject.call(this,target);
+    WebInspector.SDKObject.call(this, target);
 
-this._networkLog=WebInspector.NetworkLog.fromTarget(target);
-this._networkManager=WebInspector.NetworkManager.fromTarget(target);
-this._requestId=requestId;
-this.url=url;
-this._documentURL=documentURL;
-this._frameId=frameId;
-this._loaderId=loaderId;
+    this._networkLog = /** @type {!WebInspector.NetworkLog} */ (WebInspector.NetworkLog.fromTarget(target));
+    this._networkManager = /** @type {!WebInspector.NetworkManager} */ (WebInspector.NetworkManager.fromTarget(target));
+    this._requestId = requestId;
+    this.url = url;
+    this._documentURL = documentURL;
+    this._frameId = frameId;
+    this._loaderId = loaderId;
+    /** @type {?NetworkAgent.Initiator} */
+    this._initiator = initiator;
+    this._issueTime = -1;
+    this._startTime = -1;
+    this._endTime = -1;
+    /** @type {!NetworkAgent.BlockedReason|undefined} */
+    this._blockedReason = undefined;
 
-this._initiator=initiator;
-this._issueTime=-1;
-this._startTime=-1;
-this._endTime=-1;
+    this.statusCode = 0;
+    this.statusText = "";
+    this.requestMethod = "";
+    this.requestTime = 0;
+    this.protocol = "";
+    /** @type {!NetworkAgent.RequestMixedContentType} */
+    this.mixedContentType = NetworkAgent.RequestMixedContentType.None;
 
-this._blockedReason=undefined;
+    /** @type {?NetworkAgent.ResourcePriority} */
+    this._initialPriority = null;
+    /** @type {?NetworkAgent.ResourcePriority} */
+    this._currentPriority = null;
 
-this.statusCode=0;
-this.statusText="";
-this.requestMethod="";
-this.requestTime=0;
-this.protocol="";
+    /** @type {!WebInspector.ResourceType} */
+    this._resourceType = WebInspector.resourceTypes.Other;
+    this._contentEncoded = false;
+    this._pendingContentCallbacks = [];
+    /** @type {!Array.<!WebInspector.NetworkRequest.WebSocketFrame>} */
+    this._frames = [];
+    /** @type {!Array.<!WebInspector.NetworkRequest.EventSourceMessage>} */
+    this._eventSourceMessages = [];
 
-this.mixedContentType=NetworkAgent.RequestMixedContentType.None;
+    this._responseHeaderValues = {};
 
+    this._remoteAddress = "";
 
-this._initialPriority=null;
+    /** @type {!SecurityAgent.SecurityState} */
+    this._securityState = SecurityAgent.SecurityState.Unknown;
+    /** @type {?NetworkAgent.SecurityDetails} */
+    this._securityDetails = null;
 
-this._currentPriority=null;
+    /** @type {string} */
+    this.connectionId = "0";
+}
 
+/** @enum {symbol} */
+WebInspector.NetworkRequest.Events = {
+    FinishedLoading: Symbol("FinishedLoading"),
+    TimingChanged: Symbol("TimingChanged"),
+    RemoteAddressChanged: Symbol("RemoteAddressChanged"),
+    RequestHeadersChanged: Symbol("RequestHeadersChanged"),
+    ResponseHeadersChanged: Symbol("ResponseHeadersChanged"),
+    WebsocketFrameAdded: Symbol("WebsocketFrameAdded"),
+    EventSourceMessageAdded: Symbol("EventSourceMessageAdded")
+}
 
-this._resourceType=WebInspector.resourceTypes.Other;
-this._contentEncoded=false;
-this._pendingContentCallbacks=[];
+/** @enum {string} */
+WebInspector.NetworkRequest.InitiatorType = {
+    Other: "other",
+    Parser: "parser",
+    Redirect: "redirect",
+    Script: "script"
+}
 
-this._frames=[];
-
-this._eventSourceMessages=[];
-
-this._responseHeaderValues={};
-
-this._remoteAddress="";
-
-
-this._securityState=SecurityAgent.SecurityState.Unknown;
-
-this._securityDetails=null;
-
-
-this.connectionId="0";
-};
-
-
-WebInspector.NetworkRequest.Events={
-FinishedLoading:Symbol("FinishedLoading"),
-TimingChanged:Symbol("TimingChanged"),
-RemoteAddressChanged:Symbol("RemoteAddressChanged"),
-RequestHeadersChanged:Symbol("RequestHeadersChanged"),
-ResponseHeadersChanged:Symbol("ResponseHeadersChanged"),
-WebsocketFrameAdded:Symbol("WebsocketFrameAdded"),
-EventSourceMessageAdded:Symbol("EventSourceMessageAdded")};
-
-
-
-WebInspector.NetworkRequest.InitiatorType={
-Other:"other",
-Parser:"parser",
-Redirect:"redirect",
-Script:"script"};
-
-
-
+/** @typedef {!{name: string, value: string}} */
 WebInspector.NetworkRequest.NameValue;
 
+/** @enum {string} */
+WebInspector.NetworkRequest.WebSocketFrameType = {
+    Send: "send",
+    Receive: "receive",
+    Error: "error"
+}
 
-WebInspector.NetworkRequest.WebSocketFrameType={
-Send:"send",
-Receive:"receive",
-Error:"error"};
-
-
-
+/** @typedef {!{type: WebInspector.NetworkRequest.WebSocketFrameType, time: number, text: string, opCode: number, mask: boolean}} */
 WebInspector.NetworkRequest.WebSocketFrame;
 
-
+/** @typedef {!{time: number, eventName: string, eventId: string, data: string}} */
 WebInspector.NetworkRequest.EventSourceMessage;
 
-WebInspector.NetworkRequest.prototype={
+WebInspector.NetworkRequest.prototype = {
+    /**
+     * @param {!WebInspector.NetworkRequest} other
+     * @return {number}
+     */
+    indentityCompare: function(other)
+    {
+        if (this._requestId > other._requestId)
+            return 1;
+        if (this._requestId < other._requestId)
+            return -1;
+        return 0;
+    },
 
+    /**
+     * @return {!NetworkAgent.RequestId}
+     */
+    get requestId()
+    {
+        return this._requestId;
+    },
 
+    set requestId(requestId)
+    {
+        this._requestId = requestId;
+    },
 
+    /**
+     * @return {string}
+     */
+    get url()
+    {
+        return this._url;
+    },
 
-indentityCompare:function(other)
-{
-if(this._requestId>other._requestId)
-return 1;
-if(this._requestId<other._requestId)
-return-1;
-return 0;
-},
+    set url(x)
+    {
+        if (this._url === x)
+            return;
 
+        this._url = x;
+        this._parsedURL = new WebInspector.ParsedURL(x);
+        delete this._queryString;
+        delete this._parsedQueryParameters;
+        delete this._name;
+        delete this._path;
+    },
 
+    /**
+     * @return {string}
+     */
+    get documentURL()
+    {
+        return this._documentURL;
+    },
 
+    get parsedURL()
+    {
+        return this._parsedURL;
+    },
 
-get requestId()
-{
-return this._requestId;
-},
+    /**
+     * @return {!PageAgent.FrameId}
+     */
+    get frameId()
+    {
+        return this._frameId;
+    },
 
-set requestId(requestId)
-{
-this._requestId=requestId;
-},
+    /**
+     * @return {!NetworkAgent.LoaderId}
+     */
+    get loaderId()
+    {
+        return this._loaderId;
+    },
 
+    /**
+     * @param {string} ip
+     * @param {number} port
+     */
+    setRemoteAddress: function(ip, port)
+    {
+        this._remoteAddress = ip + ":" + port;
+        this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.RemoteAddressChanged, this);
+    },
 
+    /**
+     * @return {string}
+     */
+    remoteAddress: function()
+    {
+        return this._remoteAddress;
+    },
 
+    /**
+     * @return {!SecurityAgent.SecurityState}
+     */
+    securityState: function()
+    {
+        return this._securityState;
+    },
 
-get url()
-{
-return this._url;
-},
+    /**
+     * @param {!SecurityAgent.SecurityState} securityState
+     */
+    setSecurityState: function(securityState)
+    {
+        this._securityState = securityState;
+    },
 
-set url(x)
-{
-if(this._url===x)
-return;
+    /**
+     * @return {?NetworkAgent.SecurityDetails}
+     */
+    securityDetails: function()
+    {
+        return this._securityDetails;
+    },
 
-this._url=x;
-this._parsedURL=new WebInspector.ParsedURL(x);
-delete this._queryString;
-delete this._parsedQueryParameters;
-delete this._name;
-delete this._path;
-},
+    /**
+     * @param {!NetworkAgent.SecurityDetails} securityDetails
+     */
+    setSecurityDetails: function(securityDetails)
+    {
+        this._securityDetails = securityDetails;
+    },
 
+    /**
+     * @return {number}
+     */
+    get startTime()
+    {
+        return this._startTime || -1;
+    },
 
+    /**
+     * @param {number} monotonicTime
+     * @param {number} wallTime
+     */
+    setIssueTime: function(monotonicTime, wallTime)
+    {
+        this._issueTime = monotonicTime;
+        this._wallIssueTime = wallTime;
+        this._startTime = monotonicTime;
+    },
 
+    /**
+     * @return {number}
+     */
+    issueTime: function()
+    {
+        return this._issueTime;
+    },
 
-get documentURL()
-{
-return this._documentURL;
-},
+    /**
+     * @param {number} monotonicTime
+     * @return {number}
+     */
+    pseudoWallTime: function(monotonicTime)
+    {
+        return this._wallIssueTime ? this._wallIssueTime - this._issueTime + monotonicTime : monotonicTime;
+    },
 
-get parsedURL()
-{
-return this._parsedURL;
-},
+    /**
+     * @return {number}
+     */
+    get responseReceivedTime()
+    {
+        return this._responseReceivedTime || -1;
+    },
 
+    set responseReceivedTime(x)
+    {
+        this._responseReceivedTime = x;
+    },
 
+    /**
+     * @return {number}
+     */
+    get endTime()
+    {
+        return this._endTime || -1;
+    },
 
+    set endTime(x)
+    {
+        if (this.timing && this.timing.requestTime) {
+            // Check against accurate responseReceivedTime.
+            this._endTime = Math.max(x, this.responseReceivedTime);
+        } else {
+            // Prefer endTime since it might be from the network stack.
+            this._endTime = x;
+            if (this._responseReceivedTime > x)
+                this._responseReceivedTime = x;
+        }
+        this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.TimingChanged, this);
+    },
 
-get frameId()
-{
-return this._frameId;
-},
+    /**
+     * @return {number}
+     */
+    get duration()
+    {
+        if (this._endTime === -1 || this._startTime === -1)
+            return -1;
+        return this._endTime - this._startTime;
+    },
 
+    /**
+     * @return {number}
+     */
+    get latency()
+    {
+        if (this._responseReceivedTime === -1 || this._startTime === -1)
+            return -1;
+        return this._responseReceivedTime - this._startTime;
+    },
 
+    /**
+     * @return {number}
+     */
+    get resourceSize()
+    {
+        return this._resourceSize || 0;
+    },
 
+    set resourceSize(x)
+    {
+        this._resourceSize = x;
+    },
 
-get loaderId()
-{
-return this._loaderId;
-},
+    /**
+     * @return {number}
+     */
+    get transferSize()
+    {
+        return this._transferSize || 0;
+    },
 
+    /**
+     * @param {number} x
+     */
+    increaseTransferSize: function(x)
+    {
+        this._transferSize = (this._transferSize || 0) + x;
+    },
 
+    /**
+     * @param {number} x
+     */
+    setTransferSize: function(x)
+    {
+        this._transferSize = x;
+    },
 
+    /**
+     * @return {boolean}
+     */
+    get finished()
+    {
+        return this._finished;
+    },
 
+    set finished(x)
+    {
+        if (this._finished === x)
+            return;
 
-setRemoteAddress:function(ip,port)
-{
-this._remoteAddress=ip+":"+port;
-this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.RemoteAddressChanged,this);
-},
+        this._finished = x;
 
+        if (x) {
+            this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.FinishedLoading, this);
+            if (this._pendingContentCallbacks.length)
+                this._innerRequestContent();
+        }
+    },
 
+    /**
+     * @return {boolean}
+     */
+    get failed()
+    {
+        return this._failed;
+    },
 
+    set failed(x)
+    {
+        this._failed = x;
+    },
 
-remoteAddress:function()
-{
-return this._remoteAddress;
-},
+    /**
+     * @return {boolean}
+     */
+    get canceled()
+    {
+        return this._canceled;
+    },
 
+    set canceled(x)
+    {
+        this._canceled = x;
+    },
 
+    /**
+     * @return {!NetworkAgent.BlockedReason|undefined}
+     */
+    blockedReason: function()
+    {
+        return this._blockedReason;
+    },
 
+    /**
+     * @param {!NetworkAgent.BlockedReason} reason
+     */
+    setBlockedReason: function(reason)
+    {
+        this._blockedReason = reason;
+    },
 
-securityState:function()
-{
-return this._securityState;
-},
+    /**
+     * @return {boolean}
+     */
+    wasBlocked: function()
+    {
+        return !!this._blockedReason;
+    },
 
+    /**
+     * @return {boolean}
+     */
+    cached: function()
+    {
+        return (!!this._fromMemoryCache || !!this._fromDiskCache) && !this._transferSize;
+    },
 
+    /**
+     * @return {boolean}
+     */
+    cachedInMemory: function()
+    {
+        return !!this._fromMemoryCache && !this._transferSize;
+    },
 
+    setFromMemoryCache: function()
+    {
+        this._fromMemoryCache = true;
+        delete this._timing;
+    },
 
-setSecurityState:function(securityState)
-{
-this._securityState=securityState;
-},
+    setFromDiskCache: function()
+    {
+        this._fromDiskCache = true;
+    },
 
+    /**
+     * @return {boolean}
+     */
+    get fetchedViaServiceWorker()
+    {
+        return this._fetchedViaServiceWorker;
+    },
 
+    set fetchedViaServiceWorker(x)
+    {
+        this._fetchedViaServiceWorker = x;
+    },
 
+    /**
+     * @return {!NetworkAgent.ResourceTiming|undefined}
+     */
+    get timing()
+    {
+        return this._timing;
+    },
 
-securityDetails:function()
-{
-return this._securityDetails;
-},
+    set timing(x)
+    {
+        if (x && !this._fromMemoryCache) {
+            // Take startTime and responseReceivedTime from timing data for better accuracy.
+            // Timing's requestTime is a baseline in seconds, rest of the numbers there are ticks in millis.
+            this._startTime = x.requestTime;
+            this._responseReceivedTime = x.requestTime + x.receiveHeadersEnd / 1000.0;
 
+            this._timing = x;
+            this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.TimingChanged, this);
+        }
+    },
 
+    /**
+     * @return {string}
+     */
+    get mimeType()
+    {
+        return this._mimeType;
+    },
 
+    set mimeType(x)
+    {
+        this._mimeType = x;
+    },
 
-setSecurityDetails:function(securityDetails)
-{
-this._securityDetails=securityDetails;
-},
+    /**
+     * @return {string}
+     */
+    get displayName()
+    {
+        return this._parsedURL.displayName;
+    },
 
+    /**
+     * @return {string}
+     */
+    name: function()
+    {
+        if (this._name)
+            return this._name;
+        this._parseNameAndPathFromURL();
+        return this._name;
+    },
 
+    /**
+     * @return {string}
+     */
+    path: function()
+    {
+        if (this._path)
+            return this._path;
+        this._parseNameAndPathFromURL();
+        return this._path;
+    },
 
+    _parseNameAndPathFromURL: function()
+    {
+        if (this._parsedURL.isDataURL()) {
+            this._name = this._parsedURL.dataURLDisplayName();
+            this._path = "";
+        } else if (this._parsedURL.isAboutBlank()) {
+            this._name = this._parsedURL.url;
+            this._path = "";
+        } else {
+            this._path = this._parsedURL.host + this._parsedURL.folderPathComponents;
 
-get startTime()
-{
-return this._startTime||-1;
-},
+            var inspectedURL = this.target().inspectedURL().asParsedURL();
+            this._path = this._path.trimURL(inspectedURL ? inspectedURL.host : "");
+            if (this._parsedURL.lastPathComponent || this._parsedURL.queryParams)
+                this._name = this._parsedURL.lastPathComponent + (this._parsedURL.queryParams ? "?" + this._parsedURL.queryParams : "");
+            else if (this._parsedURL.folderPathComponents) {
+                this._name = this._parsedURL.folderPathComponents.substring(this._parsedURL.folderPathComponents.lastIndexOf("/") + 1) + "/";
+                this._path = this._path.substring(0, this._path.lastIndexOf("/"));
+            } else {
+                this._name = this._parsedURL.host;
+                this._path = "";
+            }
+        }
+    },
 
+    /**
+     * @return {string}
+     */
+    get folder()
+    {
+        var path = this._parsedURL.path;
+        var indexOfQuery = path.indexOf("?");
+        if (indexOfQuery !== -1)
+            path = path.substring(0, indexOfQuery);
+        var lastSlashIndex = path.lastIndexOf("/");
+        return lastSlashIndex !== -1 ? path.substring(0, lastSlashIndex) : "";
+    },
 
+    /**
+     * @return {!WebInspector.ResourceType}
+     */
+    resourceType: function()
+    {
+        return this._resourceType;
+    },
 
+    /**
+     * @param {!WebInspector.ResourceType} resourceType
+     */
+    setResourceType: function(resourceType)
+    {
+        this._resourceType = resourceType;
+    },
 
+    /**
+     * @return {string}
+     */
+    get domain()
+    {
+        return this._parsedURL.host;
+    },
 
-setIssueTime:function(monotonicTime,wallTime)
-{
-this._issueTime=monotonicTime;
-this._wallIssueTime=wallTime;
-this._startTime=monotonicTime;
-},
+    /**
+     * @return {string}
+     */
+    get scheme()
+    {
+        return this._parsedURL.scheme;
+    },
 
+    /**
+     * @return {?WebInspector.NetworkRequest}
+     */
+    get redirectSource()
+    {
+        if (this.redirects && this.redirects.length > 0)
+            return this.redirects[this.redirects.length - 1];
+        return this._redirectSource;
+    },
 
+    set redirectSource(x)
+    {
+        this._redirectSource = x;
+        delete this._initiatorInfo;
+    },
 
+    /**
+     * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
+     */
+    requestHeaders: function()
+    {
+        return this._requestHeaders || [];
+    },
 
-issueTime:function()
-{
-return this._issueTime;
-},
+    /**
+     * @param {!Array.<!WebInspector.NetworkRequest.NameValue>} headers
+     */
+    setRequestHeaders: function(headers)
+    {
+        this._requestHeaders = headers;
+        delete this._requestCookies;
 
+        this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.RequestHeadersChanged);
+    },
 
+    /**
+     * @return {string|undefined}
+     */
+    requestHeadersText: function()
+    {
+        return this._requestHeadersText;
+    },
 
+    /**
+     * @param {string} text
+     */
+    setRequestHeadersText: function(text)
+    {
+        this._requestHeadersText = text;
 
+        this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.RequestHeadersChanged);
+    },
 
-pseudoWallTime:function(monotonicTime)
-{
-return this._wallIssueTime?this._wallIssueTime-this._issueTime+monotonicTime:monotonicTime;
-},
+    /**
+     * @param {string} headerName
+     * @return {string|undefined}
+     */
+    requestHeaderValue: function(headerName)
+    {
+        return this._headerValue(this.requestHeaders(), headerName);
+    },
 
+    /**
+     * @return {!Array.<!WebInspector.Cookie>}
+     */
+    get requestCookies()
+    {
+        if (!this._requestCookies)
+            this._requestCookies = WebInspector.CookieParser.parseCookie(this.target(), this.requestHeaderValue("Cookie"));
+        return this._requestCookies;
+    },
 
+    /**
+     * @return {string|undefined}
+     */
+    get requestFormData()
+    {
+        return this._requestFormData;
+    },
 
+    set requestFormData(x)
+    {
+        this._requestFormData = x;
+        delete this._parsedFormParameters;
+    },
 
-get responseReceivedTime()
-{
-return this._responseReceivedTime||-1;
-},
+    /**
+     * @return {string}
+     */
+    requestHttpVersion: function()
+    {
+        var headersText = this.requestHeadersText();
+        if (!headersText)
+            return this.requestHeaderValue("version") || this.requestHeaderValue(":version") || "unknown";
+        var firstLine = headersText.split(/\r\n/)[0];
+        var match = firstLine.match(/(HTTP\/\d+\.\d+)$/);
+        return match ? match[1] : "HTTP/0.9";
+    },
 
-set responseReceivedTime(x)
-{
-this._responseReceivedTime=x;
-},
+    /**
+     * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
+     */
+    get responseHeaders()
+    {
+        return this._responseHeaders || [];
+    },
 
+    set responseHeaders(x)
+    {
+        this._responseHeaders = x;
+        delete this._sortedResponseHeaders;
+        delete this._serverTimings;
+        delete this._responseCookies;
+        this._responseHeaderValues = {};
 
+        this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.ResponseHeadersChanged);
+    },
 
+    /**
+     * @return {string}
+     */
+    get responseHeadersText()
+    {
+        return this._responseHeadersText;
+    },
 
-get endTime()
-{
-return this._endTime||-1;
-},
+    set responseHeadersText(x)
+    {
+        this._responseHeadersText = x;
 
-set endTime(x)
-{
-if(this.timing&&this.timing.requestTime){
+        this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.ResponseHeadersChanged);
+    },
 
-this._endTime=Math.max(x,this.responseReceivedTime);
-}else{
+    /**
+     * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
+     */
+    get sortedResponseHeaders()
+    {
+        if (this._sortedResponseHeaders !== undefined)
+            return this._sortedResponseHeaders;
 
-this._endTime=x;
-if(this._responseReceivedTime>x)
-this._responseReceivedTime=x;
-}
-this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.TimingChanged,this);
-},
+        this._sortedResponseHeaders = this.responseHeaders.slice();
+        this._sortedResponseHeaders.sort(function(a, b) { return a.name.toLowerCase().compareTo(b.name.toLowerCase()); });
+        return this._sortedResponseHeaders;
+    },
 
+    /**
+     * @param {string} headerName
+     * @return {string|undefined}
+     */
+    responseHeaderValue: function(headerName)
+    {
+        var value = this._responseHeaderValues[headerName];
+        if (value === undefined) {
+            value = this._headerValue(this.responseHeaders, headerName);
+            this._responseHeaderValues[headerName] = (value !== undefined) ? value : null;
+        }
+        return (value !== null) ? value : undefined;
+    },
 
-
-
-get duration()
-{
-if(this._endTime===-1||this._startTime===-1)
-return-1;
-return this._endTime-this._startTime;
-},
-
-
-
-
-get latency()
-{
-if(this._responseReceivedTime===-1||this._startTime===-1)
-return-1;
-return this._responseReceivedTime-this._startTime;
-},
-
-
-
-
-get resourceSize()
-{
-return this._resourceSize||0;
-},
-
-set resourceSize(x)
-{
-this._resourceSize=x;
-},
-
-
-
-
-get transferSize()
-{
-return this._transferSize||0;
-},
-
-
-
-
-increaseTransferSize:function(x)
-{
-this._transferSize=(this._transferSize||0)+x;
-},
-
-
-
-
-setTransferSize:function(x)
-{
-this._transferSize=x;
-},
-
-
-
-
-get finished()
-{
-return this._finished;
-},
-
-set finished(x)
-{
-if(this._finished===x)
-return;
-
-this._finished=x;
-
-if(x){
-this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.FinishedLoading,this);
-if(this._pendingContentCallbacks.length)
-this._innerRequestContent();
-}
-},
+    /**
+     * @return {!Array.<!WebInspector.Cookie>}
+     */
+    get responseCookies()
+    {
+        if (!this._responseCookies)
+            this._responseCookies = WebInspector.CookieParser.parseSetCookie(this.target(), this.responseHeaderValue("Set-Cookie"));
+        return this._responseCookies;
+    },
 
+    /**
+     * @return {?Array.<!WebInspector.ServerTiming>}
+     */
+    get serverTimings()
+    {
+        if (typeof this._serverTimings === "undefined")
+            this._serverTimings = WebInspector.ServerTiming.parseHeaders(this.responseHeaders);
+        return this._serverTimings;
+    },
 
+    /**
+     * @return {?string}
+     */
+    queryString: function()
+    {
+        if (this._queryString !== undefined)
+            return this._queryString;
 
+        var queryString = null;
+        var url = this.url;
+        var questionMarkPosition = url.indexOf("?");
+        if (questionMarkPosition !== -1) {
+            queryString = url.substring(questionMarkPosition + 1);
+            var hashSignPosition = queryString.indexOf("#");
+            if (hashSignPosition !== -1)
+                queryString = queryString.substring(0, hashSignPosition);
+        }
+        this._queryString = queryString;
+        return this._queryString;
+    },
 
-get failed()
-{
-return this._failed;
-},
+    /**
+     * @return {?Array.<!WebInspector.NetworkRequest.NameValue>}
+     */
+    get queryParameters()
+    {
+        if (this._parsedQueryParameters)
+            return this._parsedQueryParameters;
+        var queryString = this.queryString();
+        if (!queryString)
+            return null;
+        this._parsedQueryParameters = this._parseParameters(queryString);
+        return this._parsedQueryParameters;
+    },
 
-set failed(x)
-{
-this._failed=x;
-},
+    /**
+     * @return {?Array.<!WebInspector.NetworkRequest.NameValue>}
+     */
+    get formParameters()
+    {
+        if (this._parsedFormParameters)
+            return this._parsedFormParameters;
+        if (!this.requestFormData)
+            return null;
+        var requestContentType = this.requestContentType();
+        if (!requestContentType || !requestContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i))
+            return null;
+        this._parsedFormParameters = this._parseParameters(this.requestFormData);
+        return this._parsedFormParameters;
+    },
 
+    /**
+     * @return {string}
+     */
+    responseHttpVersion: function()
+    {
+        var headersText = this._responseHeadersText;
+        if (!headersText)
+            return this.responseHeaderValue("version") || this.responseHeaderValue(":version") || "unknown";
+        var firstLine = headersText.split(/\r\n/)[0];
+        var match = firstLine.match(/^(HTTP\/\d+\.\d+)/);
+        return match ? match[1] : "HTTP/0.9";
+    },
 
+    /**
+     * @param {string} queryString
+     * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
+     */
+    _parseParameters: function(queryString)
+    {
+        function parseNameValue(pair)
+        {
+            var position = pair.indexOf("=");
+            if (position === -1)
+                return {name: pair, value: ""};
+            else
+                return {name: pair.substring(0, position), value: pair.substring(position + 1)};
+        }
+        return queryString.split("&").map(parseNameValue);
+    },
 
+    /**
+     * @param {!Array.<!WebInspector.NetworkRequest.NameValue>} headers
+     * @param {string} headerName
+     * @return {string|undefined}
+     */
+    _headerValue: function(headers, headerName)
+    {
+        headerName = headerName.toLowerCase();
 
-get canceled()
-{
-return this._canceled;
-},
+        var values = [];
+        for (var i = 0; i < headers.length; ++i) {
+            if (headers[i].name.toLowerCase() === headerName)
+                values.push(headers[i].value);
+        }
+        if (!values.length)
+            return undefined;
+        // Set-Cookie values should be separated by '\n', not comma, otherwise cookies could not be parsed.
+        if (headerName === "set-cookie")
+            return values.join("\n");
+        return values.join(", ");
+    },
 
-set canceled(x)
-{
-this._canceled=x;
-},
+    /**
+     * @return {?string|undefined}
+     */
+    get content()
+    {
+        return this._content;
+    },
 
+    /**
+     * @return {?Protocol.Error|undefined}
+     */
+    contentError: function()
+    {
+        return this._contentError;
+    },
 
+    /**
+     * @return {boolean}
+     */
+    get contentEncoded()
+    {
+        return this._contentEncoded;
+    },
 
+    /**
+     * @override
+     * @return {string}
+     */
+    contentURL: function()
+    {
+        return this._url;
+    },
 
-blockedReason:function()
-{
-return this._blockedReason;
-},
+    /**
+     * @override
+     * @return {!WebInspector.ResourceType}
+     */
+    contentType: function()
+    {
+        return this._resourceType;
+    },
 
+    /**
+     * @override
+     * @return {!Promise<?string>}
+     */
+    requestContent: function()
+    {
+        // We do not support content retrieval for WebSockets at the moment.
+        // Since WebSockets are potentially long-living, fail requests immediately
+        // to prevent caller blocking until resource is marked as finished.
+        if (this._resourceType === WebInspector.resourceTypes.WebSocket)
+            return Promise.resolve(/** @type {?string} */(null));
+        if (typeof this._content !== "undefined")
+            return Promise.resolve(/** @type {?string} */(this.content || null));
+        var callback;
+        var promise = new Promise(fulfill => callback = fulfill);
+        this._pendingContentCallbacks.push(callback);
+        if (this.finished)
+            this._innerRequestContent();
+        return promise;
+    },
 
+    /**
+     * @override
+     * @param {string} query
+     * @param {boolean} caseSensitive
+     * @param {boolean} isRegex
+     * @param {function(!Array.<!WebInspector.ContentProvider.SearchMatch>)} callback
+     */
+    searchInContent: function(query, caseSensitive, isRegex, callback)
+    {
+        callback([]);
+    },
 
+    /**
+     * @return {boolean}
+     */
+    isHttpFamily: function()
+    {
+        return !!this.url.match(/^https?:/i);
+    },
 
-setBlockedReason:function(reason)
-{
-this._blockedReason=reason;
-},
+    /**
+     * @return {string|undefined}
+     */
+    requestContentType: function()
+    {
+        return this.requestHeaderValue("Content-Type");
+    },
 
+    /**
+     * @return {boolean}
+     */
+    hasErrorStatusCode: function()
+    {
+        return this.statusCode >= 400;
+    },
 
+    /**
+     * @param {!NetworkAgent.ResourcePriority} priority
+     */
+    setInitialPriority: function(priority)
+    {
+        this._initialPriority = priority;
+    },
 
+    /**
+     * @return {?NetworkAgent.ResourcePriority}
+     */
+    initialPriority: function()
+    {
+        return this._initialPriority;
+    },
 
-wasBlocked:function()
-{
-return!!this._blockedReason;
-},
+    /**
+     * @param {!NetworkAgent.ResourcePriority} priority
+     */
+    setPriority: function(priority)
+    {
+        this._currentPriority = priority;
+    },
 
+    /**
+     * @return {?NetworkAgent.ResourcePriority}
+     */
+    priority: function()
+    {
+        return this._currentPriority || this._initialPriority || null;
+    },
 
+    /**
+     * @param {!Element} image
+     */
+    populateImageSource: function(image)
+    {
+        /**
+         * @param {?string} content
+         * @this {WebInspector.NetworkRequest}
+         */
+        function onResourceContent(content)
+        {
+            var imageSrc = WebInspector.ContentProvider.contentAsDataURL(content, this._mimeType, true);
+            if (imageSrc === null)
+                imageSrc = this._url;
+            image.src = imageSrc;
+        }
 
+        this.requestContent().then(onResourceContent.bind(this));
+    },
 
-cached:function()
-{
-return(!!this._fromMemoryCache||!!this._fromDiskCache)&&!this._transferSize;
-},
+    /**
+     * @return {?string}
+     */
+    asDataURL: function()
+    {
+        var content = this._content;
+        var charset = null;
+        if (!this._contentEncoded) {
+            content = content.toBase64();
+            charset = "utf-8";
+        }
+        return WebInspector.ContentProvider.contentAsDataURL(content, this.mimeType, true, charset);
+    },
 
+    _innerRequestContent: function()
+    {
+        if (this._contentRequested)
+            return;
+        this._contentRequested = true;
 
+        /**
+         * @param {?Protocol.Error} error
+         * @param {string} content
+         * @param {boolean} contentEncoded
+         * @this {WebInspector.NetworkRequest}
+         */
+        function onResourceContent(error, content, contentEncoded)
+        {
+            this._content = error ? null : content;
+            this._contentError = error;
+            this._contentEncoded = contentEncoded;
+            var callbacks = this._pendingContentCallbacks.slice();
+            for (var i = 0; i < callbacks.length; ++i)
+                callbacks[i](this._content);
+            this._pendingContentCallbacks.length = 0;
+            delete this._contentRequested;
+        }
+        this.target().networkAgent().getResponseBody(this._requestId, onResourceContent.bind(this));
+    },
 
+    /**
+     * @return {?NetworkAgent.Initiator}
+     */
+    initiator: function()
+    {
+        return this._initiator;
+    },
 
-cachedInMemory:function()
-{
-return!!this._fromMemoryCache&&!this._transferSize;
-},
+    /**
+     * @return {!{type: !WebInspector.NetworkRequest.InitiatorType, url: string, lineNumber: number, columnNumber: number, scriptId: ?string}}
+     */
+    initiatorInfo: function()
+    {
+        if (this._initiatorInfo)
+            return this._initiatorInfo;
 
-setFromMemoryCache:function()
-{
-this._fromMemoryCache=true;
-delete this._timing;
-},
+        var type = WebInspector.NetworkRequest.InitiatorType.Other;
+        var url = "";
+        var lineNumber = -Infinity;
+        var columnNumber = -Infinity;
+        var scriptId = null;
+        var initiator = this._initiator;
 
-setFromDiskCache:function()
-{
-this._fromDiskCache=true;
-},
+        if (this.redirectSource) {
+            type = WebInspector.NetworkRequest.InitiatorType.Redirect;
+            url = this.redirectSource.url;
+        } else if (initiator) {
+            if (initiator.type === NetworkAgent.InitiatorType.Parser) {
+                type = WebInspector.NetworkRequest.InitiatorType.Parser;
+                url = initiator.url ? initiator.url : url;
+                lineNumber = initiator.lineNumber ? initiator.lineNumber : lineNumber;
+            } else if (initiator.type === NetworkAgent.InitiatorType.Script) {
+                for (var stack = initiator.stack; stack; stack = stack.parent) {
+                    var topFrame = stack.callFrames.length ? stack.callFrames[0] : null;
+                    if (!topFrame)
+                        continue;
+                    type = WebInspector.NetworkRequest.InitiatorType.Script;
+                    url = topFrame.url || WebInspector.UIString("<anonymous>");
+                    lineNumber = topFrame.lineNumber;
+                    columnNumber = topFrame.columnNumber;
+                    scriptId = topFrame.scriptId;
+                    break;
+                }
+            }
+        }
 
+        this._initiatorInfo = {type: type, url: url, lineNumber: lineNumber, columnNumber: columnNumber, scriptId: scriptId};
+        return this._initiatorInfo;
+    },
 
+    /**
+     * @return {?WebInspector.NetworkRequest}
+     */
+    initiatorRequest: function()
+    {
+        if (this._initiatorRequest === undefined)
+            this._initiatorRequest = this._networkLog.requestForURL(this.initiatorInfo().url);
+        return this._initiatorRequest;
+    },
 
+    /**
+     * @return {!Set<!WebInspector.NetworkRequest>}
+     */
+    initiatorChain: function()
+    {
+        if (this._initiatorChain)
+            return this._initiatorChain;
+        this._initiatorChain = new Set();
+        var request = this;
+        while (request) {
+            this._initiatorChain.add(request);
+            request = request.initiatorRequest();
+        }
+        return this._initiatorChain;
+    },
 
-get fetchedViaServiceWorker()
-{
-return this._fetchedViaServiceWorker;
-},
+    /**
+     * @return {!Array.<!WebInspector.NetworkRequest.WebSocketFrame>}
+     */
+    frames: function()
+    {
+        return this._frames;
+    },
 
-set fetchedViaServiceWorker(x)
-{
-this._fetchedViaServiceWorker=x;
-},
+    /**
+     * @param {string} errorMessage
+     * @param {number} time
+     */
+    addFrameError: function(errorMessage, time)
+    {
+        this._addFrame({ type: WebInspector.NetworkRequest.WebSocketFrameType.Error, text: errorMessage, time: this.pseudoWallTime(time), opCode: -1, mask: false });
+    },
 
+    /**
+     * @param {!NetworkAgent.WebSocketFrame} response
+     * @param {number} time
+     * @param {boolean} sent
+     */
+    addFrame: function(response, time, sent)
+    {
+        var type = sent ? WebInspector.NetworkRequest.WebSocketFrameType.Send : WebInspector.NetworkRequest.WebSocketFrameType.Receive;
+        this._addFrame({ type: type, text: response.payloadData, time: this.pseudoWallTime(time), opCode: response.opcode, mask: response.mask });
+    },
 
+    /**
+     * @param {!WebInspector.NetworkRequest.WebSocketFrame} frame
+     */
+    _addFrame: function(frame)
+    {
+        this._frames.push(frame);
+        this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.WebsocketFrameAdded, frame);
+    },
 
+    /**
+     * @return {!Array.<!WebInspector.NetworkRequest.EventSourceMessage>}
+     */
+    eventSourceMessages: function()
+    {
+        return this._eventSourceMessages;
+    },
 
-get timing()
-{
-return this._timing;
-},
+    /**
+     * @param {number} time
+     * @param {string} eventName
+     * @param {string} eventId
+     * @param {string} data
+     */
+    addEventSourceMessage: function(time, eventName, eventId, data)
+    {
+        var message = {time: this.pseudoWallTime(time), eventName: eventName, eventId: eventId, data: data};
+        this._eventSourceMessages.push(message);
+        this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.EventSourceMessageAdded, message);
+    },
 
-set timing(x)
-{
-if(x&&!this._fromMemoryCache){
+    replayXHR: function()
+    {
+        this.target().networkAgent().replayXHR(this.requestId);
+    },
 
+    /**
+     * @return {!WebInspector.NetworkLog}
+     */
+    networkLog: function()
+    {
+        return this._networkLog;
+    },
 
-this._startTime=x.requestTime;
-this._responseReceivedTime=x.requestTime+x.receiveHeadersEnd/1000.0;
+    /**
+     * @return {!WebInspector.NetworkManager}
+     */
+    networkManager: function()
+    {
+        return this._networkManager;
+    },
 
-this._timing=x;
-this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.TimingChanged,this);
+    __proto__: WebInspector.SDKObject.prototype
 }
-},
 
+},{}],249:[function(require,module,exports){
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
 
-
-
-get mimeType()
+/**
+ * @constructor
+ * @param {!RuntimeAgent.CallFrame} callFrame
+ */
+WebInspector.ProfileNode = function(callFrame)
 {
-return this._mimeType;
-},
-
-set mimeType(x)
-{
-this._mimeType=x;
-},
-
-
-
-
-get displayName()
-{
-return this._parsedURL.displayName;
-},
-
-
-
-
-name:function()
-{
-if(this._name)
-return this._name;
-this._parseNameAndPathFromURL();
-return this._name;
-},
-
-
-
-
-path:function()
-{
-if(this._path)
-return this._path;
-this._parseNameAndPathFromURL();
-return this._path;
-},
-
-_parseNameAndPathFromURL:function()
-{
-if(this._parsedURL.isDataURL()){
-this._name=this._parsedURL.dataURLDisplayName();
-this._path="";
-}else if(this._parsedURL.isAboutBlank()){
-this._name=this._parsedURL.url;
-this._path="";
-}else{
-this._path=this._parsedURL.host+this._parsedURL.folderPathComponents;
-
-var inspectedURL=this.target().inspectedURL().asParsedURL();
-this._path=this._path.trimURL(inspectedURL?inspectedURL.host:"");
-if(this._parsedURL.lastPathComponent||this._parsedURL.queryParams)
-this._name=this._parsedURL.lastPathComponent+(this._parsedURL.queryParams?"?"+this._parsedURL.queryParams:"");else
-if(this._parsedURL.folderPathComponents){
-this._name=this._parsedURL.folderPathComponents.substring(this._parsedURL.folderPathComponents.lastIndexOf("/")+1)+"/";
-this._path=this._path.substring(0,this._path.lastIndexOf("/"));
-}else{
-this._name=this._parsedURL.host;
-this._path="";
-}
+    /** @type {!RuntimeAgent.CallFrame} */
+    this.callFrame = callFrame;
+    /** @type {string} */
+    this.callUID = `${this.callFrame.functionName}@${this.callFrame.scriptId}:${this.callFrame.lineNumber}`;
+    /** @type {number} */
+    this.self = 0;
+    /** @type {number} */
+    this.total = 0;
+    /** @type {number} */
+    this.id = 0;
+    /** @type {?WebInspector.ProfileNode} */
+    this.parent = null;
+    /** @type {!Array<!WebInspector.ProfileNode>} */
+    this.children = [];
 }
-},
 
+WebInspector.ProfileNode.prototype = {
+    /**
+     * @return {string}
+     */
+    get functionName()
+    {
+        return this.callFrame.functionName;
+    },
 
+    /**
+     * @return {string}
+     */
+    get scriptId()
+    {
+        return this.callFrame.scriptId;
+    },
 
+    /**
+     * @return {string}
+     */
+    get url()
+    {
+        return this.callFrame.url;
+    },
 
-get folder()
-{
-var path=this._parsedURL.path;
-var indexOfQuery=path.indexOf("?");
-if(indexOfQuery!==-1)
-path=path.substring(0,indexOfQuery);
-var lastSlashIndex=path.lastIndexOf("/");
-return lastSlashIndex!==-1?path.substring(0,lastSlashIndex):"";
-},
+    /**
+     * @return {number}
+     */
+    get lineNumber()
+    {
+        return this.callFrame.lineNumber;
+    },
 
-
-
-
-resourceType:function()
-{
-return this._resourceType;
-},
-
-
-
-
-setResourceType:function(resourceType)
-{
-this._resourceType=resourceType;
-},
-
-
-
-
-get domain()
-{
-return this._parsedURL.host;
-},
-
-
-
-
-get scheme()
-{
-return this._parsedURL.scheme;
-},
-
-
-
-
-get redirectSource()
-{
-if(this.redirects&&this.redirects.length>0)
-return this.redirects[this.redirects.length-1];
-return this._redirectSource;
-},
-
-set redirectSource(x)
-{
-this._redirectSource=x;
-delete this._initiatorInfo;
-},
-
-
-
-
-requestHeaders:function()
-{
-return this._requestHeaders||[];
-},
-
-
-
-
-setRequestHeaders:function(headers)
-{
-this._requestHeaders=headers;
-delete this._requestCookies;
-
-this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.RequestHeadersChanged);
-},
-
-
-
-
-requestHeadersText:function()
-{
-return this._requestHeadersText;
-},
-
-
-
-
-setRequestHeadersText:function(text)
-{
-this._requestHeadersText=text;
-
-this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.RequestHeadersChanged);
-},
-
-
-
-
-
-requestHeaderValue:function(headerName)
-{
-return this._headerValue(this.requestHeaders(),headerName);
-},
-
-
-
-
-get requestCookies()
-{
-if(!this._requestCookies)
-this._requestCookies=WebInspector.CookieParser.parseCookie(this.target(),this.requestHeaderValue("Cookie"));
-return this._requestCookies;
-},
-
-
-
-
-get requestFormData()
-{
-return this._requestFormData;
-},
-
-set requestFormData(x)
-{
-this._requestFormData=x;
-delete this._parsedFormParameters;
-},
-
-
-
-
-requestHttpVersion:function()
-{
-var headersText=this.requestHeadersText();
-if(!headersText)
-return this.requestHeaderValue("version")||this.requestHeaderValue(":version")||"unknown";
-var firstLine=headersText.split(/\r\n/)[0];
-var match=firstLine.match(/(HTTP\/\d+\.\d+)$/);
-return match?match[1]:"HTTP/0.9";
-},
-
-
-
-
-get responseHeaders()
-{
-return this._responseHeaders||[];
-},
-
-set responseHeaders(x)
-{
-this._responseHeaders=x;
-delete this._sortedResponseHeaders;
-delete this._serverTimings;
-delete this._responseCookies;
-this._responseHeaderValues={};
-
-this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.ResponseHeadersChanged);
-},
-
-
-
-
-get responseHeadersText()
-{
-return this._responseHeadersText;
-},
-
-set responseHeadersText(x)
-{
-this._responseHeadersText=x;
-
-this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.ResponseHeadersChanged);
-},
-
-
-
-
-get sortedResponseHeaders()
-{
-if(this._sortedResponseHeaders!==undefined)
-return this._sortedResponseHeaders;
-
-this._sortedResponseHeaders=this.responseHeaders.slice();
-this._sortedResponseHeaders.sort(function(a,b){return a.name.toLowerCase().compareTo(b.name.toLowerCase());});
-return this._sortedResponseHeaders;
-},
-
-
-
-
-
-responseHeaderValue:function(headerName)
-{
-var value=this._responseHeaderValues[headerName];
-if(value===undefined){
-value=this._headerValue(this.responseHeaders,headerName);
-this._responseHeaderValues[headerName]=value!==undefined?value:null;
-}
-return value!==null?value:undefined;
-},
-
-
-
-
-get responseCookies()
-{
-if(!this._responseCookies)
-this._responseCookies=WebInspector.CookieParser.parseSetCookie(this.target(),this.responseHeaderValue("Set-Cookie"));
-return this._responseCookies;
-},
-
-
-
-
-get serverTimings()
-{
-if(typeof this._serverTimings==="undefined")
-this._serverTimings=WebInspector.ServerTiming.parseHeaders(this.responseHeaders);
-return this._serverTimings;
-},
-
-
-
-
-queryString:function()
-{
-if(this._queryString!==undefined)
-return this._queryString;
-
-var queryString=null;
-var url=this.url;
-var questionMarkPosition=url.indexOf("?");
-if(questionMarkPosition!==-1){
-queryString=url.substring(questionMarkPosition+1);
-var hashSignPosition=queryString.indexOf("#");
-if(hashSignPosition!==-1)
-queryString=queryString.substring(0,hashSignPosition);
-}
-this._queryString=queryString;
-return this._queryString;
-},
-
-
-
-
-get queryParameters()
-{
-if(this._parsedQueryParameters)
-return this._parsedQueryParameters;
-var queryString=this.queryString();
-if(!queryString)
-return null;
-this._parsedQueryParameters=this._parseParameters(queryString);
-return this._parsedQueryParameters;
-},
-
-
-
-
-get formParameters()
-{
-if(this._parsedFormParameters)
-return this._parsedFormParameters;
-if(!this.requestFormData)
-return null;
-var requestContentType=this.requestContentType();
-if(!requestContentType||!requestContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i))
-return null;
-this._parsedFormParameters=this._parseParameters(this.requestFormData);
-return this._parsedFormParameters;
-},
-
-
-
-
-responseHttpVersion:function()
-{
-var headersText=this._responseHeadersText;
-if(!headersText)
-return this.responseHeaderValue("version")||this.responseHeaderValue(":version")||"unknown";
-var firstLine=headersText.split(/\r\n/)[0];
-var match=firstLine.match(/^(HTTP\/\d+\.\d+)/);
-return match?match[1]:"HTTP/0.9";
-},
-
-
-
-
-
-_parseParameters:function(queryString)
-{
-function parseNameValue(pair)
-{
-var position=pair.indexOf("=");
-if(position===-1)
-return{name:pair,value:""};else
-
-return{name:pair.substring(0,position),value:pair.substring(position+1)};
-}
-return queryString.split("&").map(parseNameValue);
-},
-
-
-
-
-
-
-_headerValue:function(headers,headerName)
-{
-headerName=headerName.toLowerCase();
-
-var values=[];
-for(var i=0;i<headers.length;++i){
-if(headers[i].name.toLowerCase()===headerName)
-values.push(headers[i].value);
-}
-if(!values.length)
-return undefined;
-
-if(headerName==="set-cookie")
-return values.join("\n");
-return values.join(", ");
-},
-
-
-
-
-get content()
-{
-return this._content;
-},
-
-
-
-
-contentError:function()
-{
-return this._contentError;
-},
-
-
-
-
-get contentEncoded()
-{
-return this._contentEncoded;
-},
-
-
-
-
-
-contentURL:function()
-{
-return this._url;
-},
-
-
-
-
-
-contentType:function()
-{
-return this._resourceType;
-},
-
-
-
-
-
-requestContent:function()
-{
-
-
-
-if(this._resourceType===WebInspector.resourceTypes.WebSocket)
-return Promise.resolve(null);
-if(typeof this._content!=="undefined")
-return Promise.resolve(this.content||null);
-var callback;
-var promise=new Promise(fulfill=>callback=fulfill);
-this._pendingContentCallbacks.push(callback);
-if(this.finished)
-this._innerRequestContent();
-return promise;
-},
-
-
-
-
-
-
-
-
-searchInContent:function(query,caseSensitive,isRegex,callback)
-{
-callback([]);
-},
-
-
-
-
-isHttpFamily:function()
-{
-return!!this.url.match(/^https?:/i);
-},
-
-
-
-
-requestContentType:function()
-{
-return this.requestHeaderValue("Content-Type");
-},
-
-
-
-
-hasErrorStatusCode:function()
-{
-return this.statusCode>=400;
-},
-
-
-
-
-setInitialPriority:function(priority)
-{
-this._initialPriority=priority;
-},
-
-
-
-
-initialPriority:function()
-{
-return this._initialPriority;
-},
-
-
-
-
-setPriority:function(priority)
-{
-this._currentPriority=priority;
-},
-
-
-
-
-priority:function()
-{
-return this._currentPriority||this._initialPriority||null;
-},
-
-
-
-
-populateImageSource:function(image)
-{
-
-
-
-
-function onResourceContent(content)
-{
-var imageSrc=WebInspector.ContentProvider.contentAsDataURL(content,this._mimeType,true);
-if(imageSrc===null)
-imageSrc=this._url;
-image.src=imageSrc;
-}
-
-this.requestContent().then(onResourceContent.bind(this));
-},
-
-
-
-
-asDataURL:function()
-{
-var content=this._content;
-var charset=null;
-if(!this._contentEncoded){
-content=content.toBase64();
-charset="utf-8";
+    /**
+     * @return {number}
+     */
+    get columnNumber()
+    {
+        return this.callFrame.columnNumber;
+    }
 }
-return WebInspector.ContentProvider.contentAsDataURL(content,this.mimeType,true,charset);
-},
 
-_innerRequestContent:function()
+/**
+ * @constructor
+ * @param {!WebInspector.ProfileNode} root
+ */
+WebInspector.ProfileTreeModel = function(root)
 {
-if(this._contentRequested)
-return;
-this._contentRequested=true;
-
-
-
-
-
-
-
-function onResourceContent(error,content,contentEncoded)
-{
-this._content=error?null:content;
-this._contentError=error;
-this._contentEncoded=contentEncoded;
-var callbacks=this._pendingContentCallbacks.slice();
-for(var i=0;i<callbacks.length;++i)
-callbacks[i](this._content);
-this._pendingContentCallbacks.length=0;
-delete this._contentRequested;
+    this.root = root;
+    this._assignDepthsAndParents();
+    this.total = this._calculateTotals(this.root);
 }
-this.target().networkAgent().getResponseBody(this._requestId,onResourceContent.bind(this));
-},
 
+WebInspector.ProfileTreeModel.prototype = {
+    _assignDepthsAndParents: function()
+    {
+        var root = this.root;
+        root.depth = -1;
+        root.parent = null;
+        this.maxDepth = 0;
+        var nodesToTraverse = [root];
+        while (nodesToTraverse.length) {
+            var parent = nodesToTraverse.pop();
+            var depth = parent.depth + 1;
+            if (depth > this.maxDepth)
+                this.maxDepth = depth;
+            var children = parent.children;
+            var length = children.length;
+            for (var i = 0; i < length; ++i) {
+                var child = children[i];
+                child.depth = depth;
+                child.parent = parent;
+                if (child.children.length)
+                    nodesToTraverse.push(child);
+            }
+        }
+    },
 
-
-
-initiator:function()
-{
-return this._initiator;
-},
-
-
-
-
-initiatorInfo:function()
-{
-if(this._initiatorInfo)
-return this._initiatorInfo;
-
-var type=WebInspector.NetworkRequest.InitiatorType.Other;
-var url="";
-var lineNumber=-Infinity;
-var columnNumber=-Infinity;
-var scriptId=null;
-var initiator=this._initiator;
-
-if(this.redirectSource){
-type=WebInspector.NetworkRequest.InitiatorType.Redirect;
-url=this.redirectSource.url;
-}else if(initiator){
-if(initiator.type===NetworkAgent.InitiatorType.Parser){
-type=WebInspector.NetworkRequest.InitiatorType.Parser;
-url=initiator.url?initiator.url:url;
-lineNumber=initiator.lineNumber?initiator.lineNumber:lineNumber;
-}else if(initiator.type===NetworkAgent.InitiatorType.Script){
-for(var stack=initiator.stack;stack;stack=stack.parent){
-var topFrame=stack.callFrames.length?stack.callFrames[0]:null;
-if(!topFrame)
-continue;
-type=WebInspector.NetworkRequest.InitiatorType.Script;
-url=topFrame.url||WebInspector.UIString("<anonymous>");
-lineNumber=topFrame.lineNumber;
-columnNumber=topFrame.columnNumber;
-scriptId=topFrame.scriptId;
-break;
-}
-}
+    /**
+     * @param {!WebInspector.ProfileNode} root
+     * @return {number}
+     */
+    _calculateTotals: function(root)
+    {
+        var nodesToTraverse = [root];
+        var dfsList = [];
+        while (nodesToTraverse.length) {
+            var node = nodesToTraverse.pop();
+            node.total = node.self;
+            dfsList.push(node);
+            nodesToTraverse.push(...node.children);
+        }
+        while (dfsList.length > 1) {
+            var node = dfsList.pop();
+            node.parent.total += node.total;
+        }
+        return root.total;
+    }
 }
 
-this._initiatorInfo={type:type,url:url,lineNumber:lineNumber,columnNumber:columnNumber,scriptId:scriptId};
-return this._initiatorInfo;
-},
+},{}],250:[function(require,module,exports){
+/*
+ * Copyright 2014 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
 
-
-
-
-initiatorRequest:function()
+/**
+ * @constructor
+ * @extends {Protocol.Agents}
+ * @param {!WebInspector.TargetManager} targetManager
+ * @param {string} name
+ * @param {number} capabilitiesMask
+ * @param {!InspectorBackendClass.Connection} connection
+ * @param {?WebInspector.Target} parentTarget
+ */
+WebInspector.Target = function(targetManager, name, capabilitiesMask, connection, parentTarget)
 {
-if(this._initiatorRequest===undefined)
-this._initiatorRequest=this._networkLog.requestForURL(this.initiatorInfo().url);
-return this._initiatorRequest;
-},
-
-
-
+    Protocol.Agents.call(this, connection.agentsMap());
+    this._targetManager = targetManager;
+    this._name = name;
+    this._inspectedURL = "";
+    this._capabilitiesMask = capabilitiesMask;
+    this._connection = connection;
+    this._parentTarget = parentTarget;
+    connection.addEventListener(InspectorBackendClass.Connection.Events.Disconnected, this._onDisconnect, this);
+    this._id = WebInspector.Target._nextId++;
 
-initiatorChain:function()
-{
-if(this._initiatorChain)
-return this._initiatorChain;
-this._initiatorChain=new Set();
-var request=this;
-while(request){
-this._initiatorChain.add(request);
-request=request.initiatorRequest();
+    /** @type {!Map.<!Function, !WebInspector.SDKModel>} */
+    this._modelByConstructor = new Map();
 }
-return this._initiatorChain;
-},
 
-
-
-
-frames:function()
-{
-return this._frames;
-},
-
-
-
-
-
-addFrameError:function(errorMessage,time)
-{
-this._addFrame({type:WebInspector.NetworkRequest.WebSocketFrameType.Error,text:errorMessage,time:this.pseudoWallTime(time),opCode:-1,mask:false});
-},
-
-
-
-
-
-
-addFrame:function(response,time,sent)
-{
-var type=sent?WebInspector.NetworkRequest.WebSocketFrameType.Send:WebInspector.NetworkRequest.WebSocketFrameType.Receive;
-this._addFrame({type:type,text:response.payloadData,time:this.pseudoWallTime(time),opCode:response.opcode,mask:response.mask});
-},
-
-
-
-
-_addFrame:function(frame)
-{
-this._frames.push(frame);
-this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.WebsocketFrameAdded,frame);
-},
-
-
-
-
-eventSourceMessages:function()
-{
-return this._eventSourceMessages;
-},
-
-
-
-
-
-
-
-addEventSourceMessage:function(time,eventName,eventId,data)
-{
-var message={time:this.pseudoWallTime(time),eventName:eventName,eventId:eventId,data:data};
-this._eventSourceMessages.push(message);
-this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.EventSourceMessageAdded,message);
-},
-
-replayXHR:function()
-{
-this.target().networkAgent().replayXHR(this.requestId);
-},
-
-
-
-
-networkLog:function()
-{
-return this._networkLog;
-},
-
-
-
-
-networkManager:function()
-{
-return this._networkManager;
-},
-
-__proto__:WebInspector.SDKObject.prototype};
-
-
-},{}],220:[function(require,module,exports){
-
-
-
-
-
-
-
-
-WebInspector.ProfileNode=function(callFrame)
-{
-
-this.callFrame=callFrame;
-
-this.callUID=`${this.callFrame.functionName}@${this.callFrame.scriptId}:${this.callFrame.lineNumber}`;
-
-this.self=0;
-
-this.total=0;
-
-this.id=0;
-
-this.parent=null;
-
-this.children=[];
+/**
+ * @enum {number}
+ */
+WebInspector.Target.Capability = {
+    Browser: 1,
+    DOM: 2,
+    JS: 4,
+    Log: 8,
+    Network: 16,
+    Worker: 32
 };
 
-WebInspector.ProfileNode.prototype={
+WebInspector.Target._nextId = 1;
 
+WebInspector.Target.prototype = {
+    /**
+     * @return {number}
+     */
+    id: function()
+    {
+        return this._id;
+    },
 
+    /**
+     * @return {string}
+     */
+    name: function()
+    {
+        return this._name;
+    },
 
-get functionName()
-{
-return this.callFrame.functionName;
-},
+    /**
+     * @return {!WebInspector.TargetManager}
+     */
+    targetManager: function()
+    {
+        return this._targetManager;
+    },
 
+    /**
+     * @param {number} capabilitiesMask
+     * @return {boolean}
+     */
+    hasAllCapabilities: function(capabilitiesMask)
+    {
+        return (this._capabilitiesMask & capabilitiesMask) === capabilitiesMask;
+    },
 
+    /**
+     *
+     * @return {!InspectorBackendClass.Connection}
+     */
+    connection: function()
+    {
+        return this._connection;
+    },
 
+    /**
+     * @param {string} label
+     * @return {string}
+     */
+    decorateLabel: function(label)
+    {
+        return !this.hasBrowserCapability() ? "\u2699 " + label : label;
+    },
 
-get scriptId()
-{
-return this.callFrame.scriptId;
-},
+    /**
+     * @override
+     * @param {string} domain
+     * @param {!Object} dispatcher
+     */
+    registerDispatcher: function(domain, dispatcher)
+    {
+        this._connection.registerDispatcher(domain, dispatcher);
+    },
 
+    /**
+     * @return {boolean}
+     */
+    hasBrowserCapability: function()
+    {
+        return this.hasAllCapabilities(WebInspector.Target.Capability.Browser);
+    },
 
+    /**
+     * @return {boolean}
+     */
+    hasJSCapability: function()
+    {
+        return this.hasAllCapabilities(WebInspector.Target.Capability.JS);
+    },
 
+    /**
+     * @return {boolean}
+     */
+    hasLogCapability: function()
+    {
+        return this.hasAllCapabilities(WebInspector.Target.Capability.Log);
+    },
 
-get url()
-{
-return this.callFrame.url;
-},
+    /**
+     * @return {boolean}
+     */
+    hasNetworkCapability: function()
+    {
+        return this.hasAllCapabilities(WebInspector.Target.Capability.Network);
+    },
 
+    /**
+     * @return {boolean}
+     */
+    hasWorkerCapability: function()
+    {
+        return this.hasAllCapabilities(WebInspector.Target.Capability.Worker);
+    },
 
+    /**
+     * @return {boolean}
+     */
+    hasDOMCapability: function()
+    {
+        return this.hasAllCapabilities(WebInspector.Target.Capability.DOM);
+    },
 
+    /**
+     * @return {?WebInspector.Target}
+     */
+    parentTarget: function()
+    {
+        return this._parentTarget;
+    },
 
-get lineNumber()
-{
-return this.callFrame.lineNumber;
-},
+    _onDisconnect: function()
+    {
+        this._targetManager.removeTarget(this);
+        this._dispose();
+    },
 
+    _dispose: function()
+    {
+        this._targetManager.dispatchEventToListeners(WebInspector.TargetManager.Events.TargetDisposed, this);
+        if (this.workerManager)
+            this.workerManager.dispose();
+    },
 
+    /**
+     * @return {boolean}
+     */
+    isDetached: function()
+    {
+        return this._connection.isClosed();
+    },
 
+    /**
+     * @param {!Function} modelClass
+     * @return {?WebInspector.SDKModel}
+     */
+    model: function(modelClass)
+    {
+        return this._modelByConstructor.get(modelClass) || null;
+    },
 
-get columnNumber()
-{
-return this.callFrame.columnNumber;
-}};
+    /**
+     * @return {!Array<!WebInspector.SDKModel>}
+     */
+    models: function()
+    {
+        return this._modelByConstructor.valuesArray();
+    },
 
+    /**
+     * @return {string}
+     */
+    inspectedURL: function()
+    {
+        return this._inspectedURL;
+    },
 
+    /**
+     * @param {string} inspectedURL
+     */
+    setInspectedURL: function(inspectedURL)
+    {
+        this._inspectedURL = inspectedURL;
+        InspectorFrontendHost.inspectedURLChanged(inspectedURL || "");
+        this._targetManager.dispatchEventToListeners(WebInspector.TargetManager.Events.InspectedURLChanged, this);
+    },
 
-
-
-
-WebInspector.ProfileTreeModel=function(root)
-{
-this.root=root;
-this._assignDepthsAndParents();
-this.total=this._calculateTotals(this.root);
-};
-
-WebInspector.ProfileTreeModel.prototype={
-_assignDepthsAndParents:function()
-{
-var root=this.root;
-root.depth=-1;
-root.parent=null;
-this.maxDepth=0;
-var nodesToTraverse=[root];
-while(nodesToTraverse.length){
-var parent=nodesToTraverse.pop();
-var depth=parent.depth+1;
-if(depth>this.maxDepth)
-this.maxDepth=depth;
-var children=parent.children;
-var length=children.length;
-for(var i=0;i<length;++i){
-var child=children[i];
-child.depth=depth;
-child.parent=parent;
-if(child.children.length)
-nodesToTraverse.push(child);
-}
+    __proto__: Protocol.Agents.prototype
 }
-},
 
-
-
-
-
-_calculateTotals:function(root)
+/**
+ * @constructor
+ * @extends {WebInspector.Object}
+ * @param {!WebInspector.Target} target
+ */
+WebInspector.SDKObject = function(target)
 {
-var nodesToTraverse=[root];
-var dfsList=[];
-while(nodesToTraverse.length){
-var node=nodesToTraverse.pop();
-node.total=node.self;
-dfsList.push(node);
-nodesToTraverse.push(...node.children);
-}
-while(dfsList.length>1){
-var node=dfsList.pop();
-node.parent.total+=node.total;
+    WebInspector.Object.call(this);
+    this._target = target;
 }
-return root.total;
-}};
 
+WebInspector.SDKObject.prototype = {
+    /**
+     * @return {!WebInspector.Target}
+     */
+    target: function()
+    {
+        return this._target;
+    },
 
-},{}],221:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-WebInspector.Target=function(targetManager,name,capabilitiesMask,connection,parentTarget)
-{
-Protocol.Agents.call(this,connection.agentsMap());
-this._targetManager=targetManager;
-this._name=name;
-this._inspectedURL="";
-this._capabilitiesMask=capabilitiesMask;
-this._connection=connection;
-this._parentTarget=parentTarget;
-connection.addEventListener(InspectorBackendClass.Connection.Events.Disconnected,this._onDisconnect,this);
-this._id=WebInspector.Target._nextId++;
-
-
-this._modelByConstructor=new Map();
-};
-
-
-
-
-WebInspector.Target.Capability={
-Browser:1,
-DOM:2,
-JS:4,
-Log:8,
-Network:16,
-Worker:32};
-
-
-WebInspector.Target._nextId=1;
-
-WebInspector.Target.prototype={
-
-
-
-id:function()
-{
-return this._id;
-},
-
-
-
-
-name:function()
-{
-return this._name;
-},
-
-
-
-
-targetManager:function()
-{
-return this._targetManager;
-},
-
-
-
-
-
-hasAllCapabilities:function(capabilitiesMask)
-{
-return(this._capabilitiesMask&capabilitiesMask)===capabilitiesMask;
-},
-
-
-
-
-
-connection:function()
-{
-return this._connection;
-},
-
-
-
-
-
-decorateLabel:function(label)
-{
-return!this.hasBrowserCapability()?"\u2699 "+label:label;
-},
-
-
-
-
-
-
-registerDispatcher:function(domain,dispatcher)
-{
-this._connection.registerDispatcher(domain,dispatcher);
-},
-
-
-
-
-hasBrowserCapability:function()
-{
-return this.hasAllCapabilities(WebInspector.Target.Capability.Browser);
-},
-
-
-
-
-hasJSCapability:function()
-{
-return this.hasAllCapabilities(WebInspector.Target.Capability.JS);
-},
-
-
-
-
-hasLogCapability:function()
-{
-return this.hasAllCapabilities(WebInspector.Target.Capability.Log);
-},
-
-
-
-
-hasNetworkCapability:function()
-{
-return this.hasAllCapabilities(WebInspector.Target.Capability.Network);
-},
-
-
-
-
-hasWorkerCapability:function()
-{
-return this.hasAllCapabilities(WebInspector.Target.Capability.Worker);
-},
-
-
-
-
-hasDOMCapability:function()
-{
-return this.hasAllCapabilities(WebInspector.Target.Capability.DOM);
-},
-
-
-
-
-parentTarget:function()
-{
-return this._parentTarget;
-},
-
-_onDisconnect:function()
-{
-this._targetManager.removeTarget(this);
-this._dispose();
-},
-
-_dispose:function()
-{
-this._targetManager.dispatchEventToListeners(WebInspector.TargetManager.Events.TargetDisposed,this);
-if(this.workerManager)
-this.workerManager.dispose();
-},
-
-
-
-
-isDetached:function()
-{
-return this._connection.isClosed();
-},
-
-
-
-
-
-model:function(modelClass)
-{
-return this._modelByConstructor.get(modelClass)||null;
-},
-
-
-
-
-models:function()
-{
-return this._modelByConstructor.valuesArray();
-},
-
-
-
-
-inspectedURL:function()
-{
-return this._inspectedURL;
-},
-
-
-
-
-setInspectedURL:function(inspectedURL)
-{
-this._inspectedURL=inspectedURL;
-InspectorFrontendHost.inspectedURLChanged(inspectedURL||"");
-this._targetManager.dispatchEventToListeners(WebInspector.TargetManager.Events.InspectedURLChanged,this);
-},
-
-__proto__:Protocol.Agents.prototype};
-
-
-
-
-
-
-
-WebInspector.SDKObject=function(target)
-{
-WebInspector.Object.call(this);
-this._target=target;
-};
-
-WebInspector.SDKObject.prototype={
-
-
-
-target:function()
-{
-return this._target;
-},
-
-__proto__:WebInspector.Object.prototype};
-
-
-
-
-
-
-
-
-WebInspector.SDKModel=function(modelClass,target)
-{
-WebInspector.SDKObject.call(this,target);
-target._modelByConstructor.set(modelClass,this);
-WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Events.TargetDisposed,this._targetDisposed,this);
-};
-
-WebInspector.SDKModel.prototype={
-
-
-
-suspendModel:function()
-{
-return Promise.resolve();
-},
-
-
-
-
-resumeModel:function()
-{
-return Promise.resolve();
-},
-
-dispose:function(){},
-
-
-
-
-_targetDisposed:function(event)
-{
-var target=event.data;
-if(target!==this._target)
-return;
-this.dispose();
-WebInspector.targetManager.removeEventListener(WebInspector.TargetManager.Events.TargetDisposed,this._targetDisposed,this);
-},
-
-__proto__:WebInspector.SDKObject.prototype};
-
-
-},{}],222:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-WebInspector.TargetManager=function()
-{
-WebInspector.Object.call(this);
-
-this._targets=[];
-
-this._observers=[];
-this._observerCapabiliesMaskSymbol=Symbol("observerCapabilitiesMask");
-
-this._modelListeners=new Map();
-this._isSuspended=false;
-};
-
-
-WebInspector.TargetManager.Events={
-InspectedURLChanged:Symbol("InspectedURLChanged"),
-MainFrameNavigated:Symbol("MainFrameNavigated"),
-Load:Symbol("Load"),
-PageReloadRequested:Symbol("PageReloadRequested"),
-WillReloadPage:Symbol("WillReloadPage"),
-TargetDisposed:Symbol("TargetDisposed"),
-SuspendStateChanged:Symbol("SuspendStateChanged")};
-
-
-WebInspector.TargetManager._listenersSymbol=Symbol("WebInspector.TargetManager.Listeners");
-
-WebInspector.TargetManager.prototype={
-suspendAllTargets:function()
-{
-if(this._isSuspended)
-return;
-this._isSuspended=true;
-this.dispatchEventToListeners(WebInspector.TargetManager.Events.SuspendStateChanged);
-
-for(var i=0;i<this._targets.length;++i){
-for(var model of this._targets[i].models())
-model.suspendModel();
+    __proto__: WebInspector.Object.prototype
 }
-},
 
-
-
-
-resumeAllTargets:function()
+/**
+ * @constructor
+ * @extends {WebInspector.SDKObject}
+ * @param {!Function} modelClass
+ * @param {!WebInspector.Target} target
+ */
+WebInspector.SDKModel = function(modelClass, target)
 {
-if(!this._isSuspended)
-throw new Error("Not suspended");
-this._isSuspended=false;
-this.dispatchEventToListeners(WebInspector.TargetManager.Events.SuspendStateChanged);
-
-var promises=[];
-for(var i=0;i<this._targets.length;++i){
-for(var model of this._targets[i].models())
-promises.push(model.resumeModel());
+    WebInspector.SDKObject.call(this, target);
+    target._modelByConstructor.set(modelClass, this);
+    WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Events.TargetDisposed, this._targetDisposed, this);
 }
-return Promise.all(promises);
-},
 
-suspendAndResumeAllTargets:function()
-{
-this.suspendAllTargets();
-this.resumeAllTargets();
-},
+WebInspector.SDKModel.prototype = {
+    /**
+     * @return {!Promise}
+     */
+    suspendModel: function()
+    {
+        return Promise.resolve();
+    },
 
+    /**
+     * @return {!Promise}
+     */
+    resumeModel: function()
+    {
+        return Promise.resolve();
+    },
 
+    dispose: function() { },
 
+    /**
+     * @param {!WebInspector.Event} event
+     */
+    _targetDisposed: function(event)
+    {
+        var target = /** @type {!WebInspector.Target} */ (event.data);
+        if (target !== this._target)
+            return;
+        this.dispose();
+        WebInspector.targetManager.removeEventListener(WebInspector.TargetManager.Events.TargetDisposed, this._targetDisposed, this);
+    },
 
-allTargetsSuspended:function()
-{
-return this._isSuspended;
-},
-
-
-
-
-inspectedURL:function()
-{
-return this._targets[0]?this._targets[0].inspectedURL():"";
-},
-
-
-
-
-
-_redispatchEvent:function(eventName,event)
-{
-this.dispatchEventToListeners(eventName,event.data);
-},
-
-
-
-
-
-reloadPage:function(bypassCache,injectedScript)
-{
-if(!this._targets.length)
-return;
-
-var resourceTreeModel=WebInspector.ResourceTreeModel.fromTarget(this._targets[0]);
-if(!resourceTreeModel)
-return;
-
-resourceTreeModel.reloadPage(bypassCache,injectedScript);
-},
-
-
-
-
-
-
-
-addModelListener:function(modelClass,eventType,listener,thisObject)
-{
-for(var i=0;i<this._targets.length;++i){
-var model=this._targets[i].model(modelClass);
-if(model)
-model.addEventListener(eventType,listener,thisObject);
-}
-if(!this._modelListeners.has(eventType))
-this._modelListeners.set(eventType,[]);
-this._modelListeners.get(eventType).push({modelClass:modelClass,thisObject:thisObject,listener:listener});
-},
-
-
-
-
-
-
-
-removeModelListener:function(modelClass,eventType,listener,thisObject)
-{
-if(!this._modelListeners.has(eventType))
-return;
-
-for(var i=0;i<this._targets.length;++i){
-var model=this._targets[i].model(modelClass);
-if(model)
-model.removeEventListener(eventType,listener,thisObject);
-}
-
-var listeners=this._modelListeners.get(eventType);
-for(var i=0;i<listeners.length;++i){
-if(listeners[i].modelClass===modelClass&&listeners[i].listener===listener&&listeners[i].thisObject===thisObject)
-listeners.splice(i--,1);
-}
-if(!listeners.length)
-this._modelListeners.delete(eventType);
-},
-
-
-
-
-
-observeTargets:function(targetObserver,capabilitiesMask)
-{
-if(this._observerCapabiliesMaskSymbol in targetObserver)
-throw new Error("Observer can only be registered once");
-targetObserver[this._observerCapabiliesMaskSymbol]=capabilitiesMask||0;
-this.targets(capabilitiesMask).forEach(targetObserver.targetAdded.bind(targetObserver));
-this._observers.push(targetObserver);
-},
-
-
-
-
-unobserveTargets:function(targetObserver)
-{
-delete targetObserver[this._observerCapabiliesMaskSymbol];
-this._observers.remove(targetObserver);
-},
-
-
-
-
-
-
-
-
-createTarget:function(name,capabilitiesMask,connection,parentTarget)
-{
-var target=new WebInspector.Target(this,name,capabilitiesMask,connection,parentTarget);
-
-var logAgent=target.hasLogCapability()?target.logAgent():null;
-
-
-target.consoleModel=new WebInspector.ConsoleModel(target,logAgent);
-
-target.runtimeModel=new WebInspector.RuntimeModel(target);
-
-var networkManager=null;
-var resourceTreeModel=null;
-if(target.hasNetworkCapability())
-networkManager=new WebInspector.NetworkManager(target);
-if(networkManager&&target.hasDOMCapability()){
-resourceTreeModel=new WebInspector.ResourceTreeModel(target,networkManager,WebInspector.SecurityOriginManager.fromTarget(target));
-new WebInspector.NetworkLog(target,resourceTreeModel,networkManager);
-}
-
-if(target.hasJSCapability())
-new WebInspector.DebuggerModel(target);
-
-if(resourceTreeModel){
-var domModel=new WebInspector.DOMModel(target);
-
-new WebInspector.CSSModel(target,domModel);
-}
-
-
-target.workerManager=target.hasWorkerCapability()?new WebInspector.WorkerManager(target):null;
-
-target.cpuProfilerModel=new WebInspector.CPUProfilerModel(target);
-
-target.heapProfilerModel=new WebInspector.HeapProfilerModel(target);
-
-target.tracingManager=new WebInspector.TracingManager(target);
-
-if(target.hasBrowserCapability()){
-target.subTargetsManager=new WebInspector.SubTargetsManager(target);
-target.serviceWorkerManager=new WebInspector.ServiceWorkerManager(target,target.subTargetsManager);
-}
-
-this.addTarget(target);
-return target;
-},
-
-
-
-
-
-_observersForTarget:function(target)
-{
-return this._observers.filter(observer=>target.hasAllCapabilities(observer[this._observerCapabiliesMaskSymbol]||0));
-},
-
-
-
-
-addTarget:function(target)
-{
-this._targets.push(target);
-var resourceTreeModel=WebInspector.ResourceTreeModel.fromTarget(target);
-if(this._targets.length===1&&resourceTreeModel){
-resourceTreeModel[WebInspector.TargetManager._listenersSymbol]=[
-setupRedispatch.call(this,WebInspector.ResourceTreeModel.Events.MainFrameNavigated,WebInspector.TargetManager.Events.MainFrameNavigated),
-setupRedispatch.call(this,WebInspector.ResourceTreeModel.Events.Load,WebInspector.TargetManager.Events.Load),
-setupRedispatch.call(this,WebInspector.ResourceTreeModel.Events.PageReloadRequested,WebInspector.TargetManager.Events.PageReloadRequested),
-setupRedispatch.call(this,WebInspector.ResourceTreeModel.Events.WillReloadPage,WebInspector.TargetManager.Events.WillReloadPage)];
-
-}
-var copy=this._observersForTarget(target);
-for(var i=0;i<copy.length;++i)
-copy[i].targetAdded(target);
-
-for(var pair of this._modelListeners){
-var listeners=pair[1];
-for(var i=0;i<listeners.length;++i){
-var model=target.model(listeners[i].modelClass);
-if(model)
-model.addEventListener(pair[0],listeners[i].listener,listeners[i].thisObject);
-}
-}
-
-
-
-
-
-
-
-function setupRedispatch(sourceEvent,targetEvent)
-{
-return resourceTreeModel.addEventListener(sourceEvent,this._redispatchEvent.bind(this,targetEvent));
-}
-},
-
-
-
-
-removeTarget:function(target)
-{
-this._targets.remove(target);
-var resourceTreeModel=WebInspector.ResourceTreeModel.fromTarget(target);
-var treeModelListeners=resourceTreeModel&&resourceTreeModel[WebInspector.TargetManager._listenersSymbol];
-if(treeModelListeners)
-WebInspector.EventTarget.removeEventListeners(treeModelListeners);
-
-var copy=this._observersForTarget(target);
-for(var i=0;i<copy.length;++i)
-copy[i].targetRemoved(target);
-
-for(var pair of this._modelListeners){
-var listeners=pair[1];
-for(var i=0;i<listeners.length;++i){
-var model=target.model(listeners[i].modelClass);
-if(model)
-model.removeEventListener(pair[0],listeners[i].listener,listeners[i].thisObject);
-}
-}
-},
-
-
-
-
-
-targets:function(capabilitiesMask)
-{
-if(!capabilitiesMask)
-return this._targets.slice();else
-
-return this._targets.filter(target=>target.hasAllCapabilities(capabilitiesMask||0));
-},
-
-
-
-
-
-
-targetById:function(id)
-{
-for(var i=0;i<this._targets.length;++i){
-if(this._targets[i].id()===id)
-return this._targets[i];
-}
-return null;
-},
-
-
-
-
-mainTarget:function()
-{
-return this._targets[0]||null;
-},
-
-
-
-
-suspendReload:function(target)
-{
-var resourceTreeModel=WebInspector.ResourceTreeModel.fromTarget(target);
-if(resourceTreeModel)
-resourceTreeModel.suspendReload();
-},
-
-
-
-
-resumeReload:function(target)
-{
-var resourceTreeModel=WebInspector.ResourceTreeModel.fromTarget(target);
-if(resourceTreeModel)
-setImmediate(resourceTreeModel.resumeReload.bind(resourceTreeModel));
-},
-
-__proto__:WebInspector.Object.prototype};
-
-
-
-
-
-WebInspector.TargetManager.Observer=function()
-{
-};
-
-WebInspector.TargetManager.Observer.prototype={
-
-
-
-targetAdded:function(target){},
-
-
-
-
-targetRemoved:function(target){}};
-
-
-
-
-
-WebInspector.targetManager=new WebInspector.TargetManager();
-
-},{}],223:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-WebInspector.TracingModel=function(backingStorage)
-{
-this._backingStorage=backingStorage;
-
-this._firstWritePending=true;
-this.reset();
-};
-
-
-
-
-WebInspector.TracingModel.Phase={
-Begin:"B",
-End:"E",
-Complete:"X",
-Instant:"I",
-AsyncBegin:"S",
-AsyncStepInto:"T",
-AsyncStepPast:"p",
-AsyncEnd:"F",
-NestableAsyncBegin:"b",
-NestableAsyncEnd:"e",
-NestableAsyncInstant:"n",
-FlowBegin:"s",
-FlowStep:"t",
-FlowEnd:"f",
-Metadata:"M",
-Counter:"C",
-Sample:"P",
-CreateObject:"N",
-SnapshotObject:"O",
-DeleteObject:"D"};
-
-
-WebInspector.TracingModel.MetadataEvent={
-ProcessSortIndex:"process_sort_index",
-ProcessName:"process_name",
-ThreadSortIndex:"thread_sort_index",
-ThreadName:"thread_name"};
-
-
-WebInspector.TracingModel.TopLevelEventCategory="toplevel";
-WebInspector.TracingModel.DevToolsMetadataEventCategory="disabled-by-default-devtools.timeline";
-WebInspector.TracingModel.DevToolsTimelineEventCategory="disabled-by-default-devtools.timeline";
-
-WebInspector.TracingModel.FrameLifecycleEventCategory="cc,devtools";
-
-
-
-
-
-WebInspector.TracingModel.isNestableAsyncPhase=function(phase)
-{
-return phase==="b"||phase==="e"||phase==="n";
-};
-
-
-
-
-
-WebInspector.TracingModel.isAsyncBeginPhase=function(phase)
-{
-return phase==="S"||phase==="b";
-};
-
-
-
-
-
-WebInspector.TracingModel.isAsyncPhase=function(phase)
-{
-return WebInspector.TracingModel.isNestableAsyncPhase(phase)||phase==="S"||phase==="T"||phase==="F"||phase==="p";
-};
-
-
-
-
-
-WebInspector.TracingModel.isFlowPhase=function(phase)
-{
-return phase==="s"||phase==="t"||phase==="f";
-};
-
-
-
-
-
-WebInspector.TracingModel.isTopLevelEvent=function(event)
-{
-return event.hasCategory(WebInspector.TracingModel.TopLevelEventCategory)||
-event.hasCategory(WebInspector.TracingModel.DevToolsMetadataEventCategory)&&event.name==="Program";
-};
-
-
-
-
-
-WebInspector.TracingModel._extractId=function(payload)
-{
-var scope=payload.scope||"";
-if(typeof payload.id2==="undefined")
-return scope&&payload.id?`${scope}@${payload.id}`:payload.id;
-var id2=payload.id2;
-if(typeof id2==="object"&&"global"in id2!=="local"in id2)
-return typeof id2["global"]!=="undefined"?`:${scope}:${id2["global"]}`:`:${scope}:${payload.pid}:${id2["local"]}`;
-console.error(`Unexpected id2 field at ${payload.ts/1000}, one and only one of 'local' and 'global' should be present.`);
-};
-
-
-
-
-
-
-
-
-
-WebInspector.TracingModel.browserMainThread=function(tracingModel)
-{
-var processes=tracingModel.sortedProcesses();
-var browserProcesses=[];
-var crRendererMainThreads=[];
-for(var process of processes){
-if(process.name().toLowerCase().endsWith("browser"))
-browserProcesses.push(process);
-crRendererMainThreads.push(...process.sortedThreads().filter(t=>t.name()==="CrBrowserMain"));
-}
-if(crRendererMainThreads.length===1)
-return crRendererMainThreads[0];
-if(browserProcesses.length===1)
-return browserProcesses[0].threadByName("CrBrowserMain");
-var tracingStartedInBrowser=tracingModel.devToolsMetadataEvents().filter(e=>e.name==="TracingStartedInBrowser");
-if(tracingStartedInBrowser.length===1)
-return tracingStartedInBrowser[0].thread;
-WebInspector.console.error("Failed to find browser main thread in trace, some timeline features may be unavailable");
-return null;
-};
-
-
-
-
-WebInspector.BackingStorage=function()
-{
-};
-
-WebInspector.BackingStorage.prototype={
-
-
-
-appendString:function(string){},
-
-
-
-
-
-appendAccessibleString:function(string){},
-
-finishWriting:function(){},
-
-reset:function(){}};
-
-
-
-WebInspector.TracingModel.prototype={
-
-
-
-devToolsMetadataEvents:function()
-{
-return this._devToolsMetadataEvents;
-},
-
-
-
-
-setEventsForTest:function(events)
-{
-this.reset();
-this.addEvents(events);
-this.tracingComplete();
-},
-
-
-
-
-addEvents:function(events)
-{
-for(var i=0;i<events.length;++i)
-this._addEvent(events[i]);
-},
-
-tracingComplete:function()
-{
-this._processPendingAsyncEvents();
-this._backingStorage.appendString(this._firstWritePending?"[]":"]");
-this._backingStorage.finishWriting();
-this._firstWritePending=false;
-for(var process of this._processById.values()){
-for(var thread of process._threads.values())
-thread.tracingComplete();
-}
-},
-
-reset:function()
-{
-
-this._processById=new Map();
-this._processByName=new Map();
-this._minimumRecordTime=0;
-this._maximumRecordTime=0;
-this._devToolsMetadataEvents=[];
-if(!this._firstWritePending)
-this._backingStorage.reset();
-
-this._firstWritePending=true;
-
-this._asyncEvents=[];
-
-this._openAsyncEvents=new Map();
-
-this._openNestableAsyncEvents=new Map();
-
-this._parsedCategories=new Map();
-},
-
-
-
-
-_addEvent:function(payload)
-{
-var process=this._processById.get(payload.pid);
-if(!process){
-process=new WebInspector.TracingModel.Process(this,payload.pid);
-this._processById.set(payload.pid,process);
-}
-
-var eventsDelimiter=",\n";
-this._backingStorage.appendString(this._firstWritePending?"[":eventsDelimiter);
-this._firstWritePending=false;
-var stringPayload=JSON.stringify(payload);
-var isAccessible=payload.ph===WebInspector.TracingModel.Phase.SnapshotObject;
-var backingStorage=null;
-var keepStringsLessThan=10000;
-if(isAccessible&&stringPayload.length>keepStringsLessThan)
-backingStorage=this._backingStorage.appendAccessibleString(stringPayload);else
-
-this._backingStorage.appendString(stringPayload);
-
-var timestamp=payload.ts/1000;
-
-
-if(timestamp&&(!this._minimumRecordTime||timestamp<this._minimumRecordTime))
-this._minimumRecordTime=timestamp;
-var endTimeStamp=(payload.ts+(payload.dur||0))/1000;
-this._maximumRecordTime=Math.max(this._maximumRecordTime,endTimeStamp);
-var event=process._addEvent(payload);
-if(!event)
-return;
-
-
-
-if(WebInspector.TracingModel.isAsyncPhase(payload.ph))
-this._asyncEvents.push(event);
-event._setBackingStorage(backingStorage);
-if(event.hasCategory(WebInspector.TracingModel.DevToolsMetadataEventCategory))
-this._devToolsMetadataEvents.push(event);
-
-if(payload.ph!==WebInspector.TracingModel.Phase.Metadata)
-return;
-
-switch(payload.name){
-case WebInspector.TracingModel.MetadataEvent.ProcessSortIndex:
-process._setSortIndex(payload.args["sort_index"]);
-break;
-case WebInspector.TracingModel.MetadataEvent.ProcessName:
-var processName=payload.args["name"];
-process._setName(processName);
-this._processByName.set(processName,process);
-break;
-case WebInspector.TracingModel.MetadataEvent.ThreadSortIndex:
-process.threadById(payload.tid)._setSortIndex(payload.args["sort_index"]);
-break;
-case WebInspector.TracingModel.MetadataEvent.ThreadName:
-process.threadById(payload.tid)._setName(payload.args["name"]);
-break;}
-
-},
-
-
-
-
-minimumRecordTime:function()
-{
-return this._minimumRecordTime;
-},
-
-
-
-
-maximumRecordTime:function()
-{
-return this._maximumRecordTime;
-},
-
-
-
-
-sortedProcesses:function()
-{
-return WebInspector.TracingModel.NamedObject._sort(this._processById.valuesArray());
-},
-
-
-
-
-
-processByName:function(name)
-{
-return this._processByName.get(name);
-},
-
-
-
-
-
-
-threadByName:function(processName,threadName)
-{
-var process=this.processByName(processName);
-return process&&process.threadByName(threadName);
-},
-
-_processPendingAsyncEvents:function()
-{
-this._asyncEvents.stableSort(WebInspector.TracingModel.Event.compareStartTime);
-for(var i=0;i<this._asyncEvents.length;++i){
-var event=this._asyncEvents[i];
-if(WebInspector.TracingModel.isNestableAsyncPhase(event.phase))
-this._addNestableAsyncEvent(event);else
-
-this._addAsyncEvent(event);
-}
-this._asyncEvents=[];
-this._closeOpenAsyncEvents();
-},
-
-_closeOpenAsyncEvents:function()
-{
-for(var event of this._openAsyncEvents.values()){
-event.setEndTime(this._maximumRecordTime);
-
-
-event.steps[0].setEndTime(this._maximumRecordTime);
-}
-this._openAsyncEvents.clear();
-
-for(var eventStack of this._openNestableAsyncEvents.values()){
-while(eventStack.length)
-eventStack.pop().setEndTime(this._maximumRecordTime);
-}
-this._openNestableAsyncEvents.clear();
-},
-
-
-
-
-_addNestableAsyncEvent:function(event)
-{
-var phase=WebInspector.TracingModel.Phase;
-var key=event.categoriesString+"."+event.id;
-var openEventsStack=this._openNestableAsyncEvents.get(key);
-
-switch(event.phase){
-case phase.NestableAsyncBegin:
-if(!openEventsStack){
-openEventsStack=[];
-this._openNestableAsyncEvents.set(key,openEventsStack);
-}
-var asyncEvent=new WebInspector.TracingModel.AsyncEvent(event);
-openEventsStack.push(asyncEvent);
-event.thread._addAsyncEvent(asyncEvent);
-break;
-
-case phase.NestableAsyncInstant:
-if(openEventsStack&&openEventsStack.length)
-openEventsStack.peekLast()._addStep(event);
-break;
-
-case phase.NestableAsyncEnd:
-if(!openEventsStack||!openEventsStack.length)
-break;
-var top=openEventsStack.pop();
-if(top.name!==event.name){
-console.error(`Begin/end event mismatch for nestable async event, ${top.name} vs. ${event.name}, key: ${key}`);
-break;
-}
-top._addStep(event);}
-
-},
-
-
-
-
-_addAsyncEvent:function(event)
-{
-var phase=WebInspector.TracingModel.Phase;
-var key=event.categoriesString+"."+event.name+"."+event.id;
-var asyncEvent=this._openAsyncEvents.get(key);
-
-if(event.phase===phase.AsyncBegin){
-if(asyncEvent){
-console.error(`Event ${event.name} has already been started`);
-return;
-}
-asyncEvent=new WebInspector.TracingModel.AsyncEvent(event);
-this._openAsyncEvents.set(key,asyncEvent);
-event.thread._addAsyncEvent(asyncEvent);
-return;
-}
-if(!asyncEvent){
-
-return;
-}
-if(event.phase===phase.AsyncEnd){
-asyncEvent._addStep(event);
-this._openAsyncEvents.delete(key);
-return;
-}
-if(event.phase===phase.AsyncStepInto||event.phase===phase.AsyncStepPast){
-var lastStep=asyncEvent.steps.peekLast();
-if(lastStep.phase!==phase.AsyncBegin&&lastStep.phase!==event.phase){
-console.assert(false,"Async event step phase mismatch: "+lastStep.phase+" at "+lastStep.startTime+" vs. "+event.phase+" at "+event.startTime);
-return;
-}
-asyncEvent._addStep(event);
-return;
-}
-console.assert(false,"Invalid async event phase");
-},
-
-
-
-
-
-_parsedCategoriesForString:function(str)
-{
-var parsedCategories=this._parsedCategories.get(str);
-if(!parsedCategories){
-parsedCategories=new Set(str.split(","));
-this._parsedCategories.set(str,parsedCategories);
-}
-return parsedCategories;
-}};
-
-
-
-
-
-
-
-
-
-
-WebInspector.TracingModel.Event=function(categories,name,phase,startTime,thread)
-{
-
-this.categoriesString=categories;
-
-this._parsedCategories=thread._model._parsedCategoriesForString(categories);
-
-this.name=name;
-
-this.phase=phase;
-
-this.startTime=startTime;
-
-this.thread=thread;
-
-this.args={};
-
-
-this.warning=null;
-
-this.initiator=null;
-
-this.stackTrace=null;
-
-this.previewElement=null;
-
-this.url=null;
-
-this.backendNodeId=0;
-
-
-this.selfTime=0;
-};
-
-
-
-
-
-
-WebInspector.TracingModel.Event.fromPayload=function(payload,thread)
-{
-var event=new WebInspector.TracingModel.Event(payload.cat,payload.name,payload.ph,payload.ts/1000,thread);
-if(payload.args)
-event.addArgs(payload.args);else
-
-console.error("Missing mandatory event argument 'args' at "+payload.ts/1000);
-if(typeof payload.dur==="number")
-event.setEndTime((payload.ts+payload.dur)/1000);
-var id=WebInspector.TracingModel._extractId(payload);
-if(typeof id!=="undefined")
-event.id=id;
-if(payload.bind_id)
-event.bind_id=payload.bind_id;
-
-return event;
-};
-
-WebInspector.TracingModel.Event.prototype={
-
-
-
-
-hasCategory:function(categoryName)
-{
-return this._parsedCategories.has(categoryName);
-},
-
-
-
-
-setEndTime:function(endTime)
-{
-if(endTime<this.startTime){
-console.assert(false,"Event out of order: "+this.name);
-return;
-}
-this.endTime=endTime;
-this.duration=endTime-this.startTime;
-},
-
-
-
-
-addArgs:function(args)
-{
-
-for(var name in args){
-if(name in this.args)
-console.error("Same argument name ("+name+") is used for begin and end phases of "+this.name);
-this.args[name]=args[name];
-}
-},
-
-
-
-
-_complete:function(endEvent)
-{
-if(endEvent.args)
-this.addArgs(endEvent.args);else
-
-console.error("Missing mandatory event argument 'args' at "+endEvent.startTime);
-this.setEndTime(endEvent.startTime);
-},
-
-
-
-
-_setBackingStorage:function(backingStorage)
-{
-}};
-
-
-
-
-
-
-
-WebInspector.TracingModel.Event.compareStartTime=function(a,b)
-{
-return a.startTime-b.startTime;
-};
-
-
-
-
-
-
-WebInspector.TracingModel.Event.compareStartAndEndTime=function(a,b)
-{
-return a.startTime-b.startTime||b.endTime!==undefined&&a.endTime!==undefined&&b.endTime-a.endTime||0;
-};
-
-
-
-
-
-
-WebInspector.TracingModel.Event.orderedCompareStartTime=function(a,b)
-{
-
-
-
-return a.startTime-b.startTime||a.ordinal-b.ordinal||-1;
-};
-
-
-
-
-
-
-
-
-
-WebInspector.TracingModel.ObjectSnapshot=function(category,name,startTime,thread)
-{
-WebInspector.TracingModel.Event.call(this,category,name,WebInspector.TracingModel.Phase.SnapshotObject,startTime,thread);
-};
-
-
-
-
-
-
-WebInspector.TracingModel.ObjectSnapshot.fromPayload=function(payload,thread)
-{
-var snapshot=new WebInspector.TracingModel.ObjectSnapshot(payload.cat,payload.name,payload.ts/1000,thread);
-var id=WebInspector.TracingModel._extractId(payload);
-if(typeof id!=="undefined")
-snapshot.id=id;
-if(!payload.args||!payload.args["snapshot"]){
-console.error("Missing mandatory 'snapshot' argument at "+payload.ts/1000);
-return snapshot;
-}
-if(payload.args)
-snapshot.addArgs(payload.args);
-return snapshot;
-};
-
-WebInspector.TracingModel.ObjectSnapshot.prototype={
-
-
-
-requestObject:function(callback)
-{
-var snapshot=this.args["snapshot"];
-if(snapshot){
-callback(snapshot);
-return;
-}
-this._backingStorage().then(onRead,callback.bind(null,null));
-
-
-
-function onRead(result)
-{
-if(!result){
-callback(null);
-return;
-}
-try{
-var payload=JSON.parse(result);
-callback(payload["args"]["snapshot"]);
-}catch(e){
-WebInspector.console.error("Malformed event data in backing storage");
-callback(null);
-}
-}
-},
-
-
-
-
-objectPromise:function()
-{
-if(!this._objectPromise)
-this._objectPromise=new Promise(this.requestObject.bind(this));
-return this._objectPromise;
-},
-
-
-
-
-
-_setBackingStorage:function(backingStorage)
-{
-if(!backingStorage)
-return;
-this._backingStorage=backingStorage;
-this.args={};
-},
-
-__proto__:WebInspector.TracingModel.Event.prototype};
-
-
-
-
-
-
-
-WebInspector.TracingModel.AsyncEvent=function(startEvent)
-{
-WebInspector.TracingModel.Event.call(this,startEvent.categoriesString,startEvent.name,startEvent.phase,startEvent.startTime,startEvent.thread);
-this.addArgs(startEvent.args);
-this.steps=[startEvent];
-};
-
-WebInspector.TracingModel.AsyncEvent.prototype={
-
-
-
-_addStep:function(event)
-{
-this.steps.push(event);
-if(event.phase===WebInspector.TracingModel.Phase.AsyncEnd||event.phase===WebInspector.TracingModel.Phase.NestableAsyncEnd){
-this.setEndTime(event.startTime);
-
-
-this.steps[0].setEndTime(event.startTime);
-}
-},
-
-__proto__:WebInspector.TracingModel.Event.prototype};
-
-
-
-
-
-WebInspector.TracingModel.NamedObject=function()
-{
-};
-
-WebInspector.TracingModel.NamedObject.prototype=
-{
-
-
-
-_setName:function(name)
-{
-this._name=name;
-},
-
-
-
-
-name:function()
-{
-return this._name;
-},
-
-
-
-
-_setSortIndex:function(sortIndex)
-{
-this._sortIndex=sortIndex;
-}};
-
-
-
-
-
-WebInspector.TracingModel.NamedObject._sort=function(array)
-{
-
-
-
-
-function comparator(a,b)
-{
-return a._sortIndex!==b._sortIndex?a._sortIndex-b._sortIndex:a.name().localeCompare(b.name());
-}
-return array.sort(comparator);
-};
-
-
-
-
-
-
-
-WebInspector.TracingModel.Process=function(model,id)
-{
-WebInspector.TracingModel.NamedObject.call(this);
-this._setName("Process "+id);
-this._id=id;
-
-this._threads=new Map();
-this._threadByName=new Map();
-this._model=model;
-};
-
-WebInspector.TracingModel.Process.prototype={
-
-
-
-id:function()
-{
-return this._id;
-},
-
-
-
-
-
-threadById:function(id)
-{
-var thread=this._threads.get(id);
-if(!thread){
-thread=new WebInspector.TracingModel.Thread(this,id);
-this._threads.set(id,thread);
-}
-return thread;
-},
-
-
-
-
-
-threadByName:function(name)
-{
-return this._threadByName.get(name)||null;
-},
-
-
-
-
-
-_setThreadByName:function(name,thread)
-{
-this._threadByName.set(name,thread);
-},
-
-
-
-
-
-_addEvent:function(payload)
-{
-return this.threadById(payload.tid)._addEvent(payload);
-},
-
-
-
-
-sortedThreads:function()
-{
-return WebInspector.TracingModel.NamedObject._sort(this._threads.valuesArray());
-},
-
-__proto__:WebInspector.TracingModel.NamedObject.prototype};
-
-
-
-
-
-
-
-
-WebInspector.TracingModel.Thread=function(process,id)
-{
-WebInspector.TracingModel.NamedObject.call(this);
-this._process=process;
-this._setName("Thread "+id);
-this._events=[];
-this._asyncEvents=[];
-this._id=id;
-this._model=process._model;
-};
-
-WebInspector.TracingModel.Thread.prototype={
-tracingComplete:function()
-{
-this._asyncEvents.stableSort(WebInspector.TracingModel.Event.compareStartAndEndTime);
-this._events.stableSort(WebInspector.TracingModel.Event.compareStartTime);
-var phases=WebInspector.TracingModel.Phase;
-var stack=[];
-for(var i=0;i<this._events.length;++i){
-var e=this._events[i];
-e.ordinal=i;
-switch(e.phase){
-case phases.End:
-this._events[i]=null;
-
-if(!stack.length)
-continue;
-var top=stack.pop();
-if(top.name!==e.name||top.categoriesString!==e.categoriesString)
-console.error("B/E events mismatch at "+top.startTime+" ("+top.name+") vs. "+e.startTime+" ("+e.name+")");else
-
-top._complete(e);
-break;
-case phases.Begin:
-stack.push(e);
-break;}
-
-}
-while(stack.length)
-stack.pop().setEndTime(this._model.maximumRecordTime());
-this._events.remove(null,false);
-},
-
-
-
-
-
-_addEvent:function(payload)
-{
-var event=payload.ph===WebInspector.TracingModel.Phase.SnapshotObject?
-WebInspector.TracingModel.ObjectSnapshot.fromPayload(payload,this):
-WebInspector.TracingModel.Event.fromPayload(payload,this);
-if(WebInspector.TracingModel.isTopLevelEvent(event)){
-
-if(this._lastTopLevelEvent&&this._lastTopLevelEvent.endTime>event.startTime)
-return null;
-this._lastTopLevelEvent=event;
-}
-this._events.push(event);
-return event;
-},
-
-
-
-
-_addAsyncEvent:function(asyncEvent)
-{
-this._asyncEvents.push(asyncEvent);
-},
-
-
-
-
-
-_setName:function(name)
-{
-WebInspector.TracingModel.NamedObject.prototype._setName.call(this,name);
-this._process._setThreadByName(name,this);
-},
-
-
-
-
-id:function()
-{
-return this._id;
-},
-
-
-
-
-process:function()
-{
-return this._process;
-},
-
-
-
-
-events:function()
-{
-return this._events;
-},
-
-
-
-
-asyncEvents:function()
-{
-return this._asyncEvents;
-},
-
-__proto__:WebInspector.TracingModel.NamedObject.prototype};
-
-
-},{}],224:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-WebInspector.TimelineTreeView=function(model,filters)
-{
-WebInspector.VBox.call(this);
-this.element.classList.add("timeline-tree-view");
-
-this._model=model;
-this._linkifier=new WebInspector.Linkifier();
-
-this._filters=filters.slice();
-
-var columns=[];
-this._populateColumns(columns);
-
-var mainView=new WebInspector.VBox();
-this._populateToolbar(mainView.element);
-this._dataGrid=new WebInspector.SortableDataGrid(columns);
-this._dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged,this._sortingChanged,this);
-this._dataGrid.element.addEventListener("mousemove",this._onMouseMove.bind(this),true);
-this._dataGrid.setResizeMethod(WebInspector.DataGrid.ResizeMethod.Last);
-this._dataGrid.asWidget().show(mainView.element);
-
-this._splitWidget=new WebInspector.SplitWidget(true,true,"timelineTreeViewDetailsSplitWidget");
-this._splitWidget.show(this.element);
-this._splitWidget.setMainWidget(mainView);
-
-this._detailsView=new WebInspector.VBox();
-this._detailsView.element.classList.add("timeline-details-view","timeline-details-view-body");
-this._splitWidget.setSidebarWidget(this._detailsView);
-this._dataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode,this._updateDetailsForSelection,this);
-
-
-this._lastSelectedNode;
-};
-
-WebInspector.TimelineTreeView.prototype={
-
-
-
-updateContents:function(selection)
-{
-this.setRange(selection.startTime(),selection.endTime());
-},
-
-
-
-
-
-setRange:function(startTime,endTime)
-{
-this._startTime=startTime;
-this._endTime=endTime;
-this._refreshTree();
-},
-
-
-
-
-_exposePercentages:function()
-{
-return false;
-},
-
-
-
-
-_populateToolbar:function(parent){},
-
-
-
-
-_onHover:function(node){},
-
-
-
-
-
-_linkifyLocation:function(event)
-{
-var target=this._model.targetByEvent(event);
-if(!target)
-return null;
-var frame=WebInspector.TimelineProfileTree.eventStackFrame(event);
-if(!frame)
-return null;
-return this._linkifier.maybeLinkifyConsoleCallFrame(target,frame);
-},
-
-
-
-
-
-selectProfileNode:function(treeNode,suppressSelectedEvent)
-{
-var pathToRoot=[];
-for(var node=treeNode;node;node=node.parent)
-pathToRoot.push(node);
-for(var i=pathToRoot.length-1;i>0;--i){
-var gridNode=this._dataGridNodeForTreeNode(pathToRoot[i]);
-if(gridNode&&gridNode.dataGrid)
-gridNode.expand();
-}
-var gridNode=this._dataGridNodeForTreeNode(treeNode);
-if(gridNode.dataGrid){
-gridNode.reveal();
-gridNode.select(suppressSelectedEvent);
-}
-},
-
-_refreshTree:function()
-{
-this._linkifier.reset();
-this._dataGrid.rootNode().removeChildren();
-var tree=this._buildTree();
-if(!tree.children)
-return;
-var maxSelfTime=0;
-var maxTotalTime=0;
-for(var child of tree.children.values()){
-maxSelfTime=Math.max(maxSelfTime,child.selfTime);
-maxTotalTime=Math.max(maxTotalTime,child.totalTime);
-}
-for(var child of tree.children.values()){
-
-var gridNode=new WebInspector.TimelineTreeView.TreeGridNode(child,tree.totalTime,maxSelfTime,maxTotalTime,this);
-this._dataGrid.insertChild(gridNode);
-}
-this._sortingChanged();
-this._updateDetailsForSelection();
-},
-
-
-
-
-_buildTree:function()
-{
-throw new Error("Not Implemented");
-},
-
-
-
-
-
-_buildTopDownTree:function(eventIdCallback)
-{
-return WebInspector.TimelineProfileTree.buildTopDown(this._model.mainThreadEvents(),this._filters,this._startTime,this._endTime,eventIdCallback);
-},
-
-
-
-
-_populateColumns:function(columns)
-{
-columns.push({id:"self",title:WebInspector.UIString("Self Time"),width:"110px",fixedWidth:true,sortable:true});
-columns.push({id:"total",title:WebInspector.UIString("Total Time"),width:"110px",fixedWidth:true,sortable:true});
-columns.push({id:"activity",title:WebInspector.UIString("Activity"),disclosure:true,sortable:true});
-},
-
-_sortingChanged:function()
-{
-var columnIdentifier=this._dataGrid.sortColumnIdentifier();
-if(!columnIdentifier)
-return;
-var sortFunction;
-switch(columnIdentifier){
-case"startTime":
-sortFunction=compareStartTime;
-break;
-case"self":
-sortFunction=compareNumericField.bind(null,"selfTime");
-break;
-case"total":
-sortFunction=compareNumericField.bind(null,"totalTime");
-break;
-case"activity":
-sortFunction=compareName;
-break;
-default:
-console.assert(false,"Unknown sort field: "+columnIdentifier);
-return;}
-
-this._dataGrid.sortNodes(sortFunction,!this._dataGrid.isSortOrderAscending());
-
-
-
-
-
-
-
-function compareNumericField(field,a,b)
-{
-var nodeA=a;
-var nodeB=b;
-return nodeA._profileNode[field]-nodeB._profileNode[field];
-}
-
-
-
-
-
-
-function compareStartTime(a,b)
-{
-var nodeA=a;
-var nodeB=b;
-return nodeA._profileNode.event.startTime-nodeB._profileNode.event.startTime;
-}
-
-
-
-
-
-
-function compareName(a,b)
-{
-var nodeA=a;
-var nodeB=b;
-var nameA=WebInspector.TimelineTreeView.eventNameForSorting(nodeA._profileNode.event);
-var nameB=WebInspector.TimelineTreeView.eventNameForSorting(nodeB._profileNode.event);
-return nameA.localeCompare(nameB);
-}
-},
-
-_updateDetailsForSelection:function()
-{
-var selectedNode=this._dataGrid.selectedNode?this._dataGrid.selectedNode._profileNode:null;
-if(selectedNode===this._lastSelectedNode)
-return;
-this._lastSelectedNode=selectedNode;
-this._detailsView.detachChildWidgets();
-this._detailsView.element.removeChildren();
-if(!selectedNode||!this._showDetailsForNode(selectedNode)){
-var banner=this._detailsView.element.createChild("div","full-widget-dimmed-banner");
-banner.createTextChild(WebInspector.UIString("Select item for details."));
-}
-},
-
-
-
-
-
-_showDetailsForNode:function(node)
-{
-return false;
-},
-
-
-
-
-_onMouseMove:function(event)
-{
-var gridNode=event.target&&event.target instanceof Node?
-this._dataGrid.dataGridNodeFromNode(event.target):
-null;
-var profileNode=gridNode&&gridNode._profileNode;
-if(profileNode===this._lastHoveredProfileNode)
-return;
-this._lastHoveredProfileNode=profileNode;
-this._onHover(profileNode);
-},
-
-
-
-
-
-_dataGridNodeForTreeNode:function(treeNode)
-{
-return treeNode[WebInspector.TimelineTreeView.TreeGridNode._gridNodeSymbol]||null;
-},
-
-__proto__:WebInspector.VBox.prototype};
-
-
-
-
-
-
-WebInspector.TimelineTreeView.eventNameForSorting=function(event)
-{
-if(event.name===WebInspector.TimelineModel.RecordType.JSFrame){
-var data=event.args["data"];
-return data["functionName"]+"@"+(data["scriptId"]||data["url"]||"");
-}
-return event.name+":@"+WebInspector.TimelineProfileTree.eventURL(event);
-};
-
-
-
-
-
-
-
-
-
-
-WebInspector.TimelineTreeView.GridNode=function(profileNode,grandTotalTime,maxSelfTime,maxTotalTime,treeView)
-{
-this._populated=false;
-this._profileNode=profileNode;
-this._treeView=treeView;
-this._grandTotalTime=grandTotalTime;
-this._maxSelfTime=maxSelfTime;
-this._maxTotalTime=maxTotalTime;
-WebInspector.SortableDataGridNode.call(this,null,false);
-};
-
-WebInspector.TimelineTreeView.GridNode.prototype={
-
-
-
-
-
-createCell:function(columnIdentifier)
-{
-if(columnIdentifier==="activity")
-return this._createNameCell(columnIdentifier);
-return this._createValueCell(columnIdentifier)||WebInspector.DataGridNode.prototype.createCell.call(this,columnIdentifier);
-},
-
-
-
-
-
-_createNameCell:function(columnIdentifier)
-{
-var cell=this.createTD(columnIdentifier);
-var container=cell.createChild("div","name-container");
-var icon=container.createChild("div","activity-icon");
-var name=container.createChild("div","activity-name");
-var event=this._profileNode.event;
-if(this._profileNode.isGroupNode()){
-var treeView=this._treeView;
-var info=treeView._displayInfoForGroupNode(this._profileNode);
-name.textContent=info.name;
-icon.style.backgroundColor=info.color;
-}else if(event){
-var data=event.args["data"];
-var deoptReason=data&&data["deoptReason"];
-if(deoptReason)
-container.createChild("div","activity-warning").title=WebInspector.UIString("Not optimized: %s",deoptReason);
-name.textContent=event.name===WebInspector.TimelineModel.RecordType.JSFrame?
-WebInspector.beautifyFunctionName(event.args["data"]["functionName"]):
-WebInspector.TimelineUIUtils.eventTitle(event);
-var link=this._treeView._linkifyLocation(event);
-if(link)
-container.createChild("div","activity-link").appendChild(link);
-icon.style.backgroundColor=WebInspector.TimelineUIUtils.eventColor(event);
-}
-return cell;
-},
-
-
-
-
-
-_createValueCell:function(columnIdentifier)
-{
-if(columnIdentifier!=="self"&&columnIdentifier!=="total"&&columnIdentifier!=="startTime")
-return null;
-
-var showPercents=false;
-var value;
-var maxTime;
-switch(columnIdentifier){
-case"startTime":
-value=this._profileNode.event.startTime-this._treeView._model.minimumRecordTime();
-break;
-case"self":
-value=this._profileNode.selfTime;
-maxTime=this._maxSelfTime;
-showPercents=true;
-break;
-case"total":
-value=this._profileNode.totalTime;
-maxTime=this._maxTotalTime;
-showPercents=true;
-break;
-default:
-return null;}
-
-var cell=this.createTD(columnIdentifier);
-cell.className="numeric-column";
-var textDiv=cell.createChild("div");
-textDiv.createChild("span").textContent=WebInspector.UIString("%.1f\u2009ms",value);
-
-if(showPercents&&this._treeView._exposePercentages())
-textDiv.createChild("span","percent-column").textContent=WebInspector.UIString("%.1f\u2009%%",value/this._grandTotalTime*100);
-if(maxTime){
-textDiv.classList.add("background-percent-bar");
-cell.createChild("div","background-bar-container").createChild("div","background-bar").style.width=(value*100/maxTime).toFixed(1)+"%";
-}
-return cell;
-},
-
-__proto__:WebInspector.SortableDataGridNode.prototype};
-
-
-
-
-
-
-
-
-
-
-
-WebInspector.TimelineTreeView.TreeGridNode=function(profileNode,grandTotalTime,maxSelfTime,maxTotalTime,treeView)
-{
-WebInspector.TimelineTreeView.GridNode.call(this,profileNode,grandTotalTime,maxSelfTime,maxTotalTime,treeView);
-this.hasChildren=this._profileNode.children?this._profileNode.children.size>0:false;
-profileNode[WebInspector.TimelineTreeView.TreeGridNode._gridNodeSymbol]=this;
-};
-
-WebInspector.TimelineTreeView.TreeGridNode._gridNodeSymbol=Symbol("treeGridNode");
-
-WebInspector.TimelineTreeView.TreeGridNode.prototype={
-
-
-
-populate:function()
-{
-if(this._populated)
-return;
-this._populated=true;
-if(!this._profileNode.children)
-return;
-for(var node of this._profileNode.children.values()){
-var gridNode=new WebInspector.TimelineTreeView.TreeGridNode(node,this._grandTotalTime,this._maxSelfTime,this._maxTotalTime,this._treeView);
-this.insertChildOrdered(gridNode);
-}
-},
-
-__proto__:WebInspector.TimelineTreeView.GridNode.prototype};
-
-
-
-
-
-
-
-
-
-WebInspector.AggregatedTimelineTreeView=function(model,filters)
-{
-this._groupBySetting=WebInspector.settings.createSetting("timelineTreeGroupBy",WebInspector.TimelineAggregator.GroupBy.Category);
-WebInspector.TimelineTreeView.call(this,model,filters);
-var nonessentialEvents=[
-WebInspector.TimelineModel.RecordType.EventDispatch,
-WebInspector.TimelineModel.RecordType.FunctionCall,
-WebInspector.TimelineModel.RecordType.TimerFire];
-
-this._filters.push(new WebInspector.ExclusiveNameFilter(nonessentialEvents));
-this._stackView=new WebInspector.TimelineStackView(this);
-this._stackView.addEventListener(WebInspector.TimelineStackView.Events.SelectionChanged,this._onStackViewSelectionChanged,this);
-};
-
-WebInspector.AggregatedTimelineTreeView.prototype={
-
-
-
-
-updateContents:function(selection)
-{
-this._updateExtensionResolver();
-WebInspector.TimelineTreeView.prototype.updateContents.call(this,selection);
-var rootNode=this._dataGrid.rootNode();
-if(rootNode.children.length)
-rootNode.children[0].revealAndSelect();
-},
-
-_updateExtensionResolver:function()
-{
-this._executionContextNamesByOrigin=new Map();
-for(var target of WebInspector.targetManager.targets()){
-for(var context of target.runtimeModel.executionContexts())
-this._executionContextNamesByOrigin.set(context.origin,context.name);
-}
-},
-
-
-
-
-
-_displayInfoForGroupNode:function(node)
-{
-var categories=WebInspector.TimelineUIUtils.categories();
-var color=node.id?WebInspector.TimelineUIUtils.eventColor(node.event):categories["other"].color;
-
-switch(this._groupBySetting.get()){
-case WebInspector.TimelineAggregator.GroupBy.Category:
-var category=categories[node.id]||categories["other"];
-return{name:category.title,color:category.color};
-
-case WebInspector.TimelineAggregator.GroupBy.Domain:
-case WebInspector.TimelineAggregator.GroupBy.Subdomain:
-var name=node.id;
-if(WebInspector.TimelineAggregator.isExtensionInternalURL(name))
-name=WebInspector.UIString("[Chrome extensions overhead]");else
-if(name.startsWith("chrome-extension"))
-name=this._executionContextNamesByOrigin.get(name)||name;
-return{
-name:name||WebInspector.UIString("unattributed"),
-color:color};
-
-
-case WebInspector.TimelineAggregator.GroupBy.EventName:
-var name=node.event.name===WebInspector.TimelineModel.RecordType.JSFrame?
-WebInspector.UIString("JavaScript"):WebInspector.TimelineUIUtils.eventTitle(node.event);
-return{
-name:name,
-color:node.event.name===WebInspector.TimelineModel.RecordType.JSFrame?
-WebInspector.TimelineUIUtils.eventStyle(node.event).category.color:color};
-
-
-case WebInspector.TimelineAggregator.GroupBy.URL:
-break;
-
-default:
-console.assert(false,"Unexpected aggregation type");}
-
-return{
-name:node.id||WebInspector.UIString("unattributed"),
-color:color};
-
-},
-
-
-
-
-
-_populateToolbar:function(parent)
-{
-var panelToolbar=new WebInspector.Toolbar("",parent);
-this._groupByCombobox=new WebInspector.ToolbarComboBox(this._onGroupByChanged.bind(this));
-
-
-
-
-
-function addGroupingOption(name,id)
-{
-var option=this._groupByCombobox.createOption(name,"",id);
-this._groupByCombobox.addOption(option);
-if(id===this._groupBySetting.get())
-this._groupByCombobox.select(option);
-}
-addGroupingOption.call(this,WebInspector.UIString("No Grouping"),WebInspector.TimelineAggregator.GroupBy.None);
-addGroupingOption.call(this,WebInspector.UIString("Group by Activity"),WebInspector.TimelineAggregator.GroupBy.EventName);
-addGroupingOption.call(this,WebInspector.UIString("Group by Category"),WebInspector.TimelineAggregator.GroupBy.Category);
-addGroupingOption.call(this,WebInspector.UIString("Group by Domain"),WebInspector.TimelineAggregator.GroupBy.Domain);
-addGroupingOption.call(this,WebInspector.UIString("Group by Subdomain"),WebInspector.TimelineAggregator.GroupBy.Subdomain);
-addGroupingOption.call(this,WebInspector.UIString("Group by URL"),WebInspector.TimelineAggregator.GroupBy.URL);
-panelToolbar.appendToolbarItem(this._groupByCombobox);
-},
-
-
-
-
-
-_buildHeaviestStack:function(treeNode)
-{
-console.assert(!!treeNode.parent,"Attempt to build stack for tree root");
-var result=[];
-
-for(var node=treeNode;node&&node.parent;node=node.parent)
-result.push(node);
-result=result.reverse();
-for(node=treeNode;node&&node.children&&node.children.size;){
-var children=Array.from(node.children.values());
-node=children.reduce((a,b)=>a.totalTime>b.totalTime?a:b);
-result.push(node);
-}
-return result;
-},
-
-
-
-
-
-_exposePercentages:function()
-{
-return true;
-},
-
-_onGroupByChanged:function()
-{
-this._groupBySetting.set(this._groupByCombobox.selectedOption().value);
-this._refreshTree();
-},
-
-_onStackViewSelectionChanged:function()
-{
-var treeNode=this._stackView.selectedTreeNode();
-if(treeNode)
-this.selectProfileNode(treeNode,true);
-},
-
-
-
-
-
-
-_showDetailsForNode:function(node)
-{
-var stack=this._buildHeaviestStack(node);
-this._stackView.setStack(stack,node);
-this._stackView.show(this._detailsView.element);
-return true;
-},
-
-
-
-
-_createAggregator:function()
-{
-return new WebInspector.TimelineAggregator(
-event=>WebInspector.TimelineUIUtils.eventStyle(event).title,
-event=>WebInspector.TimelineUIUtils.eventStyle(event).category.name);
-
-},
-
-__proto__:WebInspector.TimelineTreeView.prototype};
-
-
-
-
-
-
-
-
-WebInspector.CallTreeTimelineTreeView=function(model,filters)
-{
-WebInspector.AggregatedTimelineTreeView.call(this,model,filters);
-this._dataGrid.markColumnAsSortedBy("total",WebInspector.DataGrid.Order.Descending);
-};
-
-WebInspector.CallTreeTimelineTreeView.prototype={
-
-
-
-
-_buildTree:function()
-{
-var topDown=this._buildTopDownTree(WebInspector.TimelineAggregator.eventId);
-return this._createAggregator().performGrouping(topDown,this._groupBySetting.get());
-},
-
-__proto__:WebInspector.AggregatedTimelineTreeView.prototype};
-
-
-
-
-
-
-
-
-WebInspector.BottomUpTimelineTreeView=function(model,filters)
-{
-WebInspector.AggregatedTimelineTreeView.call(this,model,filters);
-this._dataGrid.markColumnAsSortedBy("self",WebInspector.DataGrid.Order.Descending);
-};
-
-WebInspector.BottomUpTimelineTreeView.prototype={
-
-
-
-
-_buildTree:function()
-{
-var topDown=this._buildTopDownTree(WebInspector.TimelineAggregator.eventId);
-return WebInspector.TimelineProfileTree.buildBottomUp(topDown,this._createAggregator().groupFunction(this._groupBySetting.get()));
-},
-
-__proto__:WebInspector.AggregatedTimelineTreeView.prototype};
-
-
-
-
-
-
-
-
-
-WebInspector.EventsTimelineTreeView=function(model,filters,delegate)
-{
-this._filtersControl=new WebInspector.TimelineFilters();
-this._filtersControl.addEventListener(WebInspector.TimelineFilters.Events.FilterChanged,this._onFilterChanged,this);
-WebInspector.TimelineTreeView.call(this,model,filters);
-this._delegate=delegate;
-this._filters.push.apply(this._filters,this._filtersControl.filters());
-this._dataGrid.markColumnAsSortedBy("startTime",WebInspector.DataGrid.Order.Ascending);
-};
-
-WebInspector.EventsTimelineTreeView.prototype={
-
-
-
-
-updateContents:function(selection)
-{
-WebInspector.TimelineTreeView.prototype.updateContents.call(this,selection);
-if(selection.type()===WebInspector.TimelineSelection.Type.TraceEvent){
-var event=selection.object();
-this._selectEvent(event,true);
-}
-},
-
-
-
-
-
-_buildTree:function()
-{
-this._currentTree=this._buildTopDownTree();
-return this._currentTree;
-},
-
-_onFilterChanged:function()
-{
-var selectedEvent=this._lastSelectedNode&&this._lastSelectedNode.event;
-this._refreshTree();
-if(selectedEvent)
-this._selectEvent(selectedEvent,false);
-},
-
-
-
-
-
-_findNodeWithEvent:function(event)
-{
-var iterators=[this._currentTree.children.values()];
-
-while(iterators.length){
-var iterator=iterators.peekLast().next();
-if(iterator.done){
-iterators.pop();
-continue;
-}
-var child=iterator.value;
-if(child.event===event)
-return child;
-if(child.children)
-iterators.push(child.children.values());
-}
-return null;
-},
-
-
-
-
-
-_selectEvent:function(event,expand)
-{
-var node=this._findNodeWithEvent(event);
-if(!node)
-return;
-this.selectProfileNode(node,false);
-if(expand)
-this._dataGridNodeForTreeNode(node).expand();
-},
-
-
-
-
-
-_populateColumns:function(columns)
-{
-columns.push({id:"startTime",title:WebInspector.UIString("Start Time"),width:"110px",fixedWidth:true,sortable:true});
-WebInspector.TimelineTreeView.prototype._populateColumns.call(this,columns);
-},
-
-
-
-
-
-_populateToolbar:function(parent)
-{
-var filtersWidget=this._filtersControl.filtersWidget();
-filtersWidget.forceShowFilterBar();
-filtersWidget.show(parent);
-},
-
-
-
-
-
-
-_showDetailsForNode:function(node)
-{
-var traceEvent=node.event;
-if(!traceEvent)
-return false;
-WebInspector.TimelineUIUtils.buildTraceEventDetails(traceEvent,this._model,this._linkifier,false,showDetails.bind(this));
-return true;
-
-
-
-
-
-function showDetails(fragment)
-{
-this._detailsView.element.appendChild(fragment);
-}
-},
-
-
-
-
-
-_onHover:function(node)
-{
-this._delegate.highlightEvent(node&&node.event);
-},
-
-__proto__:WebInspector.TimelineTreeView.prototype};
-
-
-
-
-
-
-WebInspector.TimelineStackView=function(treeView)
-{
-WebInspector.VBox.call(this);
-var header=this.element.createChild("div","timeline-stack-view-header");
-header.textContent=WebInspector.UIString("Heaviest stack");
-this._treeView=treeView;
-var columns=[
-{id:"total",title:WebInspector.UIString("Total Time"),fixedWidth:true,width:"110px"},
-{id:"activity",title:WebInspector.UIString("Activity")}];
-
-this._dataGrid=new WebInspector.ViewportDataGrid(columns);
-this._dataGrid.setResizeMethod(WebInspector.DataGrid.ResizeMethod.Last);
-this._dataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode,this._onSelectionChanged,this);
-this._dataGrid.asWidget().show(this.element);
-};
-
-
-WebInspector.TimelineStackView.Events={
-SelectionChanged:Symbol("SelectionChanged")};
-
-
-WebInspector.TimelineStackView.prototype={
-
-
-
-
-setStack:function(stack,selectedNode)
-{
-var rootNode=this._dataGrid.rootNode();
-rootNode.removeChildren();
-var nodeToReveal=null;
-var totalTime=Math.max.apply(Math,stack.map(node=>node.totalTime));
-for(var node of stack){
-var gridNode=new WebInspector.TimelineTreeView.GridNode(node,totalTime,totalTime,totalTime,this._treeView);
-rootNode.appendChild(gridNode);
-if(node===selectedNode)
-nodeToReveal=gridNode;
-}
-nodeToReveal.revealAndSelect();
-},
-
-
-
-
-selectedTreeNode:function()
-{
-var selectedNode=this._dataGrid.selectedNode;
-return selectedNode&&selectedNode._profileNode;
-},
-
-_onSelectionChanged:function()
-{
-this.dispatchEventToListeners(WebInspector.TimelineStackView.Events.SelectionChanged);
-},
-
-__proto__:WebInspector.VBox.prototype};
-
-
-},{}],225:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-WebInspector.TimelineUIUtils=function(){};
-
-
-
-
-
-
-
-WebInspector.TimelineRecordStyle=function(title,category,hidden)
-{
-this.title=title;
-this.category=category;
-this.hidden=!!hidden;
-};
-
-
-
-
-WebInspector.TimelineUIUtils._initEventStyles=function()
-{
-if(WebInspector.TimelineUIUtils._eventStylesMap)
-return WebInspector.TimelineUIUtils._eventStylesMap;
-
-var recordTypes=WebInspector.TimelineModel.RecordType;
-var categories=WebInspector.TimelineUIUtils.categories();
-
-var eventStyles={};
-eventStyles[recordTypes.Task]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Task"),categories["other"]);
-eventStyles[recordTypes.Program]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Other"),categories["other"]);
-eventStyles[recordTypes.Animation]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Animation"),categories["rendering"]);
-eventStyles[recordTypes.EventDispatch]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Event"),categories["scripting"]);
-eventStyles[recordTypes.RequestMainThreadFrame]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Request Main Thread Frame"),categories["rendering"],true);
-eventStyles[recordTypes.BeginFrame]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Frame Start"),categories["rendering"],true);
-eventStyles[recordTypes.BeginMainThreadFrame]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Frame Start (main thread)"),categories["rendering"],true);
-eventStyles[recordTypes.DrawFrame]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Draw Frame"),categories["rendering"],true);
-eventStyles[recordTypes.HitTest]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Hit Test"),categories["rendering"]);
-eventStyles[recordTypes.ScheduleStyleRecalculation]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Schedule Style Recalculation"),categories["rendering"],true);
-eventStyles[recordTypes.RecalculateStyles]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Recalculate Style"),categories["rendering"]);
-eventStyles[recordTypes.UpdateLayoutTree]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Recalculate Style"),categories["rendering"]);
-eventStyles[recordTypes.InvalidateLayout]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Invalidate Layout"),categories["rendering"],true);
-eventStyles[recordTypes.Layout]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Layout"),categories["rendering"]);
-eventStyles[recordTypes.PaintSetup]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Paint Setup"),categories["painting"]);
-eventStyles[recordTypes.PaintImage]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Paint Image"),categories["painting"],true);
-eventStyles[recordTypes.UpdateLayer]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Update Layer"),categories["painting"],true);
-eventStyles[recordTypes.UpdateLayerTree]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Update Layer Tree"),categories["rendering"]);
-eventStyles[recordTypes.Paint]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Paint"),categories["painting"]);
-eventStyles[recordTypes.RasterTask]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Rasterize Paint"),categories["painting"]);
-eventStyles[recordTypes.ScrollLayer]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Scroll"),categories["rendering"]);
-eventStyles[recordTypes.CompositeLayers]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Composite Layers"),categories["painting"]);
-eventStyles[recordTypes.ParseHTML]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Parse HTML"),categories["loading"]);
-eventStyles[recordTypes.ParseAuthorStyleSheet]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Parse Stylesheet"),categories["loading"]);
-eventStyles[recordTypes.TimerInstall]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Install Timer"),categories["scripting"]);
-eventStyles[recordTypes.TimerRemove]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Remove Timer"),categories["scripting"]);
-eventStyles[recordTypes.TimerFire]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Timer Fired"),categories["scripting"]);
-eventStyles[recordTypes.XHRReadyStateChange]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("XHR Ready State Change"),categories["scripting"]);
-eventStyles[recordTypes.XHRLoad]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("XHR Load"),categories["scripting"]);
-eventStyles[recordTypes.CompileScript]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Compile Script"),categories["scripting"]);
-eventStyles[recordTypes.EvaluateScript]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Evaluate Script"),categories["scripting"]);
-eventStyles[recordTypes.ParseScriptOnBackground]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Parse Script"),categories["scripting"]);
-eventStyles[recordTypes.MarkLoad]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Load event"),categories["scripting"],true);
-eventStyles[recordTypes.MarkDOMContent]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("DOMContentLoaded event"),categories["scripting"],true);
-eventStyles[recordTypes.MarkFirstPaint]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("First paint"),categories["painting"],true);
-eventStyles[recordTypes.TimeStamp]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Timestamp"),categories["scripting"]);
-eventStyles[recordTypes.ConsoleTime]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Console Time"),categories["scripting"]);
-eventStyles[recordTypes.UserTiming]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("User Timing"),categories["scripting"]);
-eventStyles[recordTypes.ResourceSendRequest]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Send Request"),categories["loading"]);
-eventStyles[recordTypes.ResourceReceiveResponse]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Receive Response"),categories["loading"]);
-eventStyles[recordTypes.ResourceFinish]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Finish Loading"),categories["loading"]);
-eventStyles[recordTypes.ResourceReceivedData]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Receive Data"),categories["loading"]);
-eventStyles[recordTypes.RunMicrotasks]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Run Microtasks"),categories["scripting"]);
-eventStyles[recordTypes.FunctionCall]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Function Call"),categories["scripting"]);
-eventStyles[recordTypes.GCEvent]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("GC Event"),categories["scripting"]);
-eventStyles[recordTypes.MajorGC]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Major GC"),categories["scripting"]);
-eventStyles[recordTypes.MinorGC]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Minor GC"),categories["scripting"]);
-eventStyles[recordTypes.JSFrame]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("JS Frame"),categories["scripting"]);
-eventStyles[recordTypes.RequestAnimationFrame]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Request Animation Frame"),categories["scripting"]);
-eventStyles[recordTypes.CancelAnimationFrame]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Cancel Animation Frame"),categories["scripting"]);
-eventStyles[recordTypes.FireAnimationFrame]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Animation Frame Fired"),categories["scripting"]);
-eventStyles[recordTypes.RequestIdleCallback]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Request Idle Callback"),categories["scripting"]);
-eventStyles[recordTypes.CancelIdleCallback]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Cancel Idle Callback"),categories["scripting"]);
-eventStyles[recordTypes.FireIdleCallback]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Fire Idle Callback"),categories["scripting"]);
-eventStyles[recordTypes.WebSocketCreate]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Create WebSocket"),categories["scripting"]);
-eventStyles[recordTypes.WebSocketSendHandshakeRequest]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Send WebSocket Handshake"),categories["scripting"]);
-eventStyles[recordTypes.WebSocketReceiveHandshakeResponse]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Receive WebSocket Handshake"),categories["scripting"]);
-eventStyles[recordTypes.WebSocketDestroy]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Destroy WebSocket"),categories["scripting"]);
-eventStyles[recordTypes.EmbedderCallback]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Embedder Callback"),categories["scripting"]);
-eventStyles[recordTypes.DecodeImage]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Image Decode"),categories["painting"]);
-eventStyles[recordTypes.ResizeImage]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Image Resize"),categories["painting"]);
-eventStyles[recordTypes.GPUTask]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("GPU"),categories["gpu"]);
-eventStyles[recordTypes.LatencyInfo]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("Input Latency"),categories["scripting"]);
-
-eventStyles[recordTypes.GCIdleLazySweep]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("DOM GC"),categories["scripting"]);
-eventStyles[recordTypes.GCCompleteSweep]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("DOM GC"),categories["scripting"]);
-eventStyles[recordTypes.GCCollectGarbage]=new WebInspector.TimelineRecordStyle(WebInspector.UIString("DOM GC"),categories["scripting"]);
-
-WebInspector.TimelineUIUtils._eventStylesMap=eventStyles;
-return eventStyles;
-};
-
-
-
-
-
-WebInspector.TimelineUIUtils.inputEventDisplayName=function(inputEventType)
-{
-if(!WebInspector.TimelineUIUtils._inputEventToDisplayName){
-var inputEvent=WebInspector.TimelineIRModel.InputEvents;
-
-
-WebInspector.TimelineUIUtils._inputEventToDisplayName=new Map([
-[inputEvent.Char,WebInspector.UIString("Key Character")],
-[inputEvent.KeyDown,WebInspector.UIString("Key Down")],
-[inputEvent.KeyDownRaw,WebInspector.UIString("Key Down")],
-[inputEvent.KeyUp,WebInspector.UIString("Key Up")],
-[inputEvent.Click,WebInspector.UIString("Click")],
-[inputEvent.ContextMenu,WebInspector.UIString("Context Menu")],
-[inputEvent.MouseDown,WebInspector.UIString("Mouse Down")],
-[inputEvent.MouseMove,WebInspector.UIString("Mouse Move")],
-[inputEvent.MouseUp,WebInspector.UIString("Mouse Up")],
-[inputEvent.MouseWheel,WebInspector.UIString("Mouse Wheel")],
-[inputEvent.ScrollBegin,WebInspector.UIString("Scroll Begin")],
-[inputEvent.ScrollEnd,WebInspector.UIString("Scroll End")],
-[inputEvent.ScrollUpdate,WebInspector.UIString("Scroll Update")],
-[inputEvent.FlingStart,WebInspector.UIString("Fling Start")],
-[inputEvent.FlingCancel,WebInspector.UIString("Fling Halt")],
-[inputEvent.Tap,WebInspector.UIString("Tap")],
-[inputEvent.TapCancel,WebInspector.UIString("Tap Halt")],
-[inputEvent.ShowPress,WebInspector.UIString("Tap Begin")],
-[inputEvent.TapDown,WebInspector.UIString("Tap Down")],
-[inputEvent.TouchCancel,WebInspector.UIString("Touch Cancel")],
-[inputEvent.TouchEnd,WebInspector.UIString("Touch End")],
-[inputEvent.TouchMove,WebInspector.UIString("Touch Move")],
-[inputEvent.TouchStart,WebInspector.UIString("Touch Start")],
-[inputEvent.PinchBegin,WebInspector.UIString("Pinch Begin")],
-[inputEvent.PinchEnd,WebInspector.UIString("Pinch End")],
-[inputEvent.PinchUpdate,WebInspector.UIString("Pinch Update")]]);
-
-}
-return WebInspector.TimelineUIUtils._inputEventToDisplayName.get(inputEventType)||null;
-};
-
-
-
-
-
-
-WebInspector.TimelineUIUtils.testContentMatching=function(traceEvent,regExp)
-{
-var title=WebInspector.TimelineUIUtils.eventStyle(traceEvent).title;
-var tokens=[title];
-if(traceEvent.url)
-tokens.push(traceEvent.url);
-for(var argName in traceEvent.args){
-var argValue=traceEvent.args[argName];
-for(var key in argValue)
-tokens.push(argValue[key]);
-}
-return regExp.test(tokens.join("|"));
-};
-
-
-
-
-
-WebInspector.TimelineUIUtils.categoryForRecord=function(record)
-{
-return WebInspector.TimelineUIUtils.eventStyle(record.traceEvent()).category;
-};
-
-
-
-
-
-
-WebInspector.TimelineUIUtils.eventStyle=function(event)
-{
-var eventStyles=WebInspector.TimelineUIUtils._initEventStyles();
-if(event.hasCategory(WebInspector.TimelineModel.Category.Console)||event.hasCategory(WebInspector.TimelineModel.Category.UserTiming))
-return{title:event.name,category:WebInspector.TimelineUIUtils.categories()["scripting"]};
-
-if(event.hasCategory(WebInspector.TimelineModel.Category.LatencyInfo)){
-
-var prefix="InputLatency::";
-var inputEventType=event.name.startsWith(prefix)?event.name.substr(prefix.length):event.name;
-var displayName=WebInspector.TimelineUIUtils.inputEventDisplayName(inputEventType);
-return{title:displayName||inputEventType,category:WebInspector.TimelineUIUtils.categories()["scripting"]};
-}
-var result=eventStyles[event.name];
-if(!result){
-result=new WebInspector.TimelineRecordStyle(event.name,WebInspector.TimelineUIUtils.categories()["other"],true);
-eventStyles[event.name]=result;
-}
-return result;
-};
-
-
-
-
-
-WebInspector.TimelineUIUtils.eventColor=function(event)
-{
-if(event.name===WebInspector.TimelineModel.RecordType.JSFrame){
-var frame=event.args["data"];
-if(WebInspector.TimelineUIUtils.isUserFrame(frame))
-return WebInspector.TimelineUIUtils.colorForURL(frame.url);
-}
-return WebInspector.TimelineUIUtils.eventStyle(event).category.color;
-};
-
-
-
-
-
-WebInspector.TimelineUIUtils.eventTitle=function(event)
-{
-var title=WebInspector.TimelineUIUtils.eventStyle(event).title;
-if(event.hasCategory(WebInspector.TimelineModel.Category.Console))
-return title;
-if(event.name===WebInspector.TimelineModel.RecordType.TimeStamp)
-return WebInspector.UIString("%s: %s",title,event.args["data"]["message"]);
-if(event.name===WebInspector.TimelineModel.RecordType.Animation&&event.args["data"]&&event.args["data"]["name"])
-return WebInspector.UIString("%s: %s",title,event.args["data"]["name"]);
-return title;
-};
-
-
-
-
-
-WebInspector.TimelineUIUtils.eventURL=function(event)
-{
-if(event.url)
-return event.url;
-var data=event.args["data"]||event.args["beginData"];
-return data&&data.url||null;
-};
-
-
-
-
-WebInspector.TimelineUIUtils._interactionPhaseStyles=function()
-{
-var map=WebInspector.TimelineUIUtils._interactionPhaseStylesMap;
-if(!map){
-map=new Map([
-[WebInspector.TimelineIRModel.Phases.Idle,{color:"white",label:"Idle"}],
-[WebInspector.TimelineIRModel.Phases.Response,{color:"hsl(43, 83%, 64%)",label:WebInspector.UIString("Response")}],
-[WebInspector.TimelineIRModel.Phases.Scroll,{color:"hsl(256, 67%, 70%)",label:WebInspector.UIString("Scroll")}],
-[WebInspector.TimelineIRModel.Phases.Fling,{color:"hsl(256, 67%, 70%)",label:WebInspector.UIString("Fling")}],
-[WebInspector.TimelineIRModel.Phases.Drag,{color:"hsl(256, 67%, 70%)",label:WebInspector.UIString("Drag")}],
-[WebInspector.TimelineIRModel.Phases.Animation,{color:"hsl(256, 67%, 70%)",label:WebInspector.UIString("Animation")}],
-[WebInspector.TimelineIRModel.Phases.Uncategorized,{color:"hsl(0, 0%, 87%)",label:WebInspector.UIString("Uncategorized")}]]);
-
-WebInspector.TimelineUIUtils._interactionPhaseStylesMap=map;
-}
-return map;
-};
-
-
-
-
-
-WebInspector.TimelineUIUtils.interactionPhaseColor=function(phase)
-{
-return WebInspector.TimelineUIUtils._interactionPhaseStyles().get(phase).color;
-};
-
-
-
-
-
-WebInspector.TimelineUIUtils.interactionPhaseLabel=function(phase)
-{
-return WebInspector.TimelineUIUtils._interactionPhaseStyles().get(phase).label;
-};
-
-
-
-
-
-WebInspector.TimelineUIUtils.isUserFrame=function(frame)
-{
-return frame.scriptId!=="0"&&!(frame.url&&frame.url.startsWith("native "));
-};
-
-
-
-
-
-WebInspector.TimelineUIUtils.topStackFrame=function(event)
-{
-var stackTrace=event.stackTrace||event.initiator&&event.initiator.stackTrace;
-return stackTrace&&stackTrace.length?stackTrace[0]:null;
-};
-
-
-
-
-WebInspector.TimelineUIUtils.NetworkCategory={
-HTML:Symbol("HTML"),
-Script:Symbol("Script"),
-Style:Symbol("Style"),
-Media:Symbol("Media"),
-Other:Symbol("Other")};
-
-
-
-
-
-
-WebInspector.TimelineUIUtils.networkRequestCategory=function(request)
-{
-var categories=WebInspector.TimelineUIUtils.NetworkCategory;
-switch(request.mimeType){
-case"text/html":
-return categories.HTML;
-case"application/javascript":
-case"application/x-javascript":
-case"text/javascript":
-return categories.Script;
-case"text/css":
-return categories.Style;
-case"audio/ogg":
-case"image/gif":
-case"image/jpeg":
-case"image/png":
-case"image/svg+xml":
-case"image/webp":
-case"image/x-icon":
-case"font/opentype":
-case"font/woff2":
-case"application/font-woff":
-return categories.Media;
-default:
-return categories.Other;}
-
-};
-
-
-
-
-
-WebInspector.TimelineUIUtils.networkCategoryColor=function(category)
-{
-var categories=WebInspector.TimelineUIUtils.NetworkCategory;
-switch(category){
-case categories.HTML:return"hsl(214, 67%, 66%)";
-case categories.Script:return"hsl(43, 83%, 64%)";
-case categories.Style:return"hsl(256, 67%, 70%)";
-case categories.Media:return"hsl(109, 33%, 55%)";
-default:return"hsl(0, 0%, 70%)";}
-
-};
-
-
-
-
-
-
-WebInspector.TimelineUIUtils.buildDetailsTextForTraceEvent=function(event,target)
-{
-var recordType=WebInspector.TimelineModel.RecordType;
-var detailsText;
-var eventData=event.args["data"];
-switch(event.name){
-case recordType.GCEvent:
-case recordType.MajorGC:
-case recordType.MinorGC:
-var delta=event.args["usedHeapSizeBefore"]-event.args["usedHeapSizeAfter"];
-detailsText=WebInspector.UIString("%s collected",Number.bytesToString(delta));
-break;
-case recordType.FunctionCall:
-
-if(eventData)
-detailsText=linkifyLocationAsText(eventData["scriptId"],eventData["lineNumber"],0);
-break;
-case recordType.JSFrame:
-detailsText=WebInspector.beautifyFunctionName(eventData["functionName"]);
-break;
-case recordType.EventDispatch:
-detailsText=eventData?eventData["type"]:null;
-break;
-case recordType.Paint:
-var width=WebInspector.TimelineUIUtils.quadWidth(eventData.clip);
-var height=WebInspector.TimelineUIUtils.quadHeight(eventData.clip);
-if(width&&height)
-detailsText=WebInspector.UIString("%d\u2009\u00d7\u2009%d",width,height);
-break;
-case recordType.ParseHTML:
-var endLine=event.args["endData"]&&event.args["endData"]["endLine"];
-var url=WebInspector.displayNameForURL(event.args["beginData"]["url"]);
-detailsText=WebInspector.UIString("%s [%s\u2026%s]",url,event.args["beginData"]["startLine"]+1,endLine>=0?endLine+1:"");
-break;
-
-case recordType.CompileScript:
-case recordType.EvaluateScript:
-var url=eventData["url"];
-if(url)
-detailsText=WebInspector.displayNameForURL(url)+":"+(eventData["lineNumber"]+1);
-break;
-case recordType.ParseScriptOnBackground:
-case recordType.XHRReadyStateChange:
-case recordType.XHRLoad:
-var url=eventData["url"];
-if(url)
-detailsText=WebInspector.displayNameForURL(url);
-break;
-case recordType.TimeStamp:
-detailsText=eventData["message"];
-break;
-
-case recordType.WebSocketCreate:
-case recordType.WebSocketSendHandshakeRequest:
-case recordType.WebSocketReceiveHandshakeResponse:
-case recordType.WebSocketDestroy:
-case recordType.ResourceSendRequest:
-case recordType.ResourceReceivedData:
-case recordType.ResourceReceiveResponse:
-case recordType.ResourceFinish:
-case recordType.PaintImage:
-case recordType.DecodeImage:
-case recordType.ResizeImage:
-case recordType.DecodeLazyPixelRef:
-if(event.url)
-detailsText=WebInspector.displayNameForURL(event.url);
-break;
-
-case recordType.EmbedderCallback:
-detailsText=eventData["callbackName"];
-break;
-
-case recordType.Animation:
-detailsText=eventData&&eventData["name"];
-break;
-
-case recordType.GCIdleLazySweep:
-detailsText=WebInspector.UIString("idle sweep");
-break;
-
-case recordType.GCCompleteSweep:
-detailsText=WebInspector.UIString("complete sweep");
-break;
-
-case recordType.GCCollectGarbage:
-detailsText=WebInspector.UIString("collect");
-break;
-
-default:
-if(event.hasCategory(WebInspector.TimelineModel.Category.Console))
-detailsText=null;else
-
-detailsText=linkifyTopCallFrameAsText();
-break;}
-
-
-return detailsText;
-
-
-
-
-
-
-
-function linkifyLocationAsText(scriptId,lineNumber,columnNumber)
-{
-var debuggerModel=WebInspector.DebuggerModel.fromTarget(target);
-if(!target||target.isDetached()||!scriptId||!debuggerModel)
-return null;
-var rawLocation=debuggerModel.createRawLocationByScriptId(scriptId,lineNumber,columnNumber);
-if(!rawLocation)
-return null;
-var uiLocation=WebInspector.debuggerWorkspaceBinding.rawLocationToUILocation(rawLocation);
-return uiLocation.linkText();
-}
-
-
-
-
-function linkifyTopCallFrameAsText()
-{
-var frame=WebInspector.TimelineUIUtils.topStackFrame(event);
-if(!frame)
-return null;
-var text=linkifyLocationAsText(frame.scriptId,frame.lineNumber,frame.columnNumber);
-if(!text){
-text=frame.url;
-if(typeof frame.lineNumber==="number")
-text+=":"+(frame.lineNumber+1);
-}
-return text;
-}
-};
-
-
-
-
-
-
-
-WebInspector.TimelineUIUtils.buildDetailsNodeForTraceEvent=function(event,target,linkifier)
-{
-var recordType=WebInspector.TimelineModel.RecordType;
-var details=null;
-var detailsText;
-var eventData=event.args["data"];
-switch(event.name){
-case recordType.GCEvent:
-case recordType.MajorGC:
-case recordType.MinorGC:
-case recordType.EventDispatch:
-case recordType.Paint:
-case recordType.Animation:
-case recordType.EmbedderCallback:
-case recordType.ParseHTML:
-case recordType.WebSocketCreate:
-case recordType.WebSocketSendHandshakeRequest:
-case recordType.WebSocketReceiveHandshakeResponse:
-case recordType.WebSocketDestroy:
-case recordType.GCIdleLazySweep:
-case recordType.GCCompleteSweep:
-case recordType.GCCollectGarbage:
-detailsText=WebInspector.TimelineUIUtils.buildDetailsTextForTraceEvent(event,target);
-break;
-case recordType.PaintImage:
-case recordType.DecodeImage:
-case recordType.ResizeImage:
-case recordType.DecodeLazyPixelRef:
-case recordType.XHRReadyStateChange:
-case recordType.XHRLoad:
-case recordType.ResourceSendRequest:
-case recordType.ResourceReceivedData:
-case recordType.ResourceReceiveResponse:
-case recordType.ResourceFinish:
-if(event.url)
-details=WebInspector.linkifyResourceAsNode(event.url);
-break;
-case recordType.FunctionCall:
-case recordType.JSFrame:
-details=createElement("span");
-details.createTextChild(WebInspector.beautifyFunctionName(eventData["functionName"]));
-var location=linkifyLocation(eventData["scriptId"],eventData["url"],eventData["lineNumber"],eventData["columnNumber"]);
-if(location){
-details.createTextChild(" @ ");
-details.appendChild(location);
-}
-break;
-case recordType.CompileScript:
-case recordType.EvaluateScript:
-var url=eventData["url"];
-if(url)
-details=linkifyLocation("",url,eventData["lineNumber"],0);
-break;
-case recordType.ParseScriptOnBackground:
-var url=eventData["url"];
-if(url)
-details=linkifyLocation("",url,0,0);
-break;
-default:
-if(event.hasCategory(WebInspector.TimelineModel.Category.Console))
-detailsText=null;else
-
-details=linkifyTopCallFrame();
-break;}
-
-
-if(!details&&detailsText)
-details=createTextNode(detailsText);
-return details;
-
-
-
-
-
-
-
-
-function linkifyLocation(scriptId,url,lineNumber,columnNumber)
-{
-return linkifier.linkifyScriptLocation(target,scriptId,url,lineNumber,columnNumber,"timeline-details");
-}
-
-
-
-
-function linkifyTopCallFrame()
-{
-var frame=WebInspector.TimelineUIUtils.topStackFrame(event);
-return frame?linkifier.maybeLinkifyConsoleCallFrame(target,frame,"timeline-details"):null;
-}
-};
-
-
-
-
-
-
-
-
-WebInspector.TimelineUIUtils.buildTraceEventDetails=function(event,model,linkifier,detailed,callback)
-{
-var target=model.targetByEvent(event);
-if(!target){
-callbackWrapper();
-return;
-}
-var relatedNodes=null;
-var barrier=new CallbackBarrier();
-if(!event.previewElement){
-if(event.url)
-WebInspector.DOMPresentationUtils.buildImagePreviewContents(target,event.url,false,barrier.createCallback(saveImage));else
-if(event.picture)
-WebInspector.TimelineUIUtils.buildPicturePreviewContent(event,target,barrier.createCallback(saveImage));
-}
-var nodeIdsToResolve=new Set();
-if(event.backendNodeId)
-nodeIdsToResolve.add(event.backendNodeId);
-if(event.invalidationTrackingEvents)
-WebInspector.TimelineUIUtils._collectInvalidationNodeIds(nodeIdsToResolve,event.invalidationTrackingEvents);
-if(nodeIdsToResolve.size){
-var domModel=WebInspector.DOMModel.fromTarget(target);
-if(domModel)
-domModel.pushNodesByBackendIdsToFrontend(nodeIdsToResolve,barrier.createCallback(setRelatedNodeMap));
-}
-barrier.callWhenDone(callbackWrapper);
-
-
-
-
-function saveImage(element)
-{
-event.previewElement=element||null;
-}
-
-
-
-
-function setRelatedNodeMap(nodeMap)
-{
-relatedNodes=nodeMap;
-}
-
-function callbackWrapper()
-{
-callback(WebInspector.TimelineUIUtils._buildTraceEventDetailsSynchronously(event,model,linkifier,detailed,relatedNodes));
-}
-};
-
-
-
-
-
-
-
-
-
-WebInspector.TimelineUIUtils._buildTraceEventDetailsSynchronously=function(event,model,linkifier,detailed,relatedNodesMap)
-{
-var stats={};
-var recordTypes=WebInspector.TimelineModel.RecordType;
-
-
-var relatedNodeLabel;
-
-var contentHelper=new WebInspector.TimelineDetailsContentHelper(model.targetByEvent(event),linkifier);
-contentHelper.addSection(WebInspector.TimelineUIUtils.eventTitle(event),WebInspector.TimelineUIUtils.eventStyle(event).category);
-
-var eventData=event.args["data"];
-var initiator=event.initiator;
-
-if(event.warning)
-contentHelper.appendWarningRow(event);
-if(event.name===recordTypes.JSFrame&&eventData["deoptReason"])
-contentHelper.appendWarningRow(event,WebInspector.TimelineModel.WarningType.V8Deopt);
-
-if(detailed){
-contentHelper.appendTextRow(WebInspector.UIString("Self Time"),Number.millisToString(event.selfTime,true));
-contentHelper.appendTextRow(WebInspector.UIString("Total Time"),Number.millisToString(event.duration||0,true));
-}
-
-switch(event.name){
-case recordTypes.GCEvent:
-case recordTypes.MajorGC:
-case recordTypes.MinorGC:
-var delta=event.args["usedHeapSizeBefore"]-event.args["usedHeapSizeAfter"];
-contentHelper.appendTextRow(WebInspector.UIString("Collected"),Number.bytesToString(delta));
-break;
-case recordTypes.JSFrame:
-case recordTypes.FunctionCall:
-var detailsNode=WebInspector.TimelineUIUtils.buildDetailsNodeForTraceEvent(event,model.targetByEvent(event),linkifier);
-if(detailsNode)
-contentHelper.appendElementRow(WebInspector.UIString("Function"),detailsNode);
-break;
-case recordTypes.TimerFire:
-case recordTypes.TimerInstall:
-case recordTypes.TimerRemove:
-contentHelper.appendTextRow(WebInspector.UIString("Timer ID"),eventData["timerId"]);
-if(event.name===recordTypes.TimerInstall){
-contentHelper.appendTextRow(WebInspector.UIString("Timeout"),Number.millisToString(eventData["timeout"]));
-contentHelper.appendTextRow(WebInspector.UIString("Repeats"),!eventData["singleShot"]);
-}
-break;
-case recordTypes.FireAnimationFrame:
-contentHelper.appendTextRow(WebInspector.UIString("Callback ID"),eventData["id"]);
-break;
-case recordTypes.ResourceSendRequest:
-case recordTypes.ResourceReceiveResponse:
-case recordTypes.ResourceReceivedData:
-case recordTypes.ResourceFinish:
-var url=event.name===recordTypes.ResourceSendRequest?eventData["url"]:initiator&&initiator.args["data"]["url"];
-if(url)
-contentHelper.appendElementRow(WebInspector.UIString("Resource"),WebInspector.linkifyResourceAsNode(url));
-if(eventData["requestMethod"])
-contentHelper.appendTextRow(WebInspector.UIString("Request Method"),eventData["requestMethod"]);
-if(typeof eventData["statusCode"]==="number")
-contentHelper.appendTextRow(WebInspector.UIString("Status Code"),eventData["statusCode"]);
-if(eventData["mimeType"])
-contentHelper.appendTextRow(WebInspector.UIString("MIME Type"),eventData["mimeType"]);
-if("priority"in eventData){
-var priority=WebInspector.uiLabelForPriority(eventData["priority"]);
-contentHelper.appendTextRow(WebInspector.UIString("Priority"),priority);
-}
-if(eventData["encodedDataLength"])
-contentHelper.appendTextRow(WebInspector.UIString("Encoded Data Length"),WebInspector.UIString("%d Bytes",eventData["encodedDataLength"]));
-break;
-case recordTypes.CompileScript:
-case recordTypes.EvaluateScript:
-var url=eventData["url"];
-if(url)
-contentHelper.appendLocationRow(WebInspector.UIString("Script"),url,eventData["lineNumber"],eventData["columnNumber"]);
-break;
-case recordTypes.Paint:
-var clip=eventData["clip"];
-contentHelper.appendTextRow(WebInspector.UIString("Location"),WebInspector.UIString("(%d, %d)",clip[0],clip[1]));
-var clipWidth=WebInspector.TimelineUIUtils.quadWidth(clip);
-var clipHeight=WebInspector.TimelineUIUtils.quadHeight(clip);
-contentHelper.appendTextRow(WebInspector.UIString("Dimensions"),WebInspector.UIString("%d × %d",clipWidth,clipHeight));
-
-
-case recordTypes.PaintSetup:
-case recordTypes.Rasterize:
-case recordTypes.ScrollLayer:
-relatedNodeLabel=WebInspector.UIString("Layer Root");
-break;
-case recordTypes.PaintImage:
-case recordTypes.DecodeLazyPixelRef:
-case recordTypes.DecodeImage:
-case recordTypes.ResizeImage:
-case recordTypes.DrawLazyPixelRef:
-relatedNodeLabel=WebInspector.UIString("Owner Element");
-if(event.url)
-contentHelper.appendElementRow(WebInspector.UIString("Image URL"),WebInspector.linkifyResourceAsNode(event.url));
-break;
-case recordTypes.ParseAuthorStyleSheet:
-var url=eventData["styleSheetUrl"];
-if(url)
-contentHelper.appendElementRow(WebInspector.UIString("Stylesheet URL"),WebInspector.linkifyResourceAsNode(url));
-break;
-case recordTypes.UpdateLayoutTree:
-case recordTypes.RecalculateStyles:
-contentHelper.appendTextRow(WebInspector.UIString("Elements Affected"),event.args["elementCount"]);
-break;
-case recordTypes.Layout:
-var beginData=event.args["beginData"];
-contentHelper.appendTextRow(WebInspector.UIString("Nodes That Need Layout"),WebInspector.UIString("%s of %s",beginData["dirtyObjects"],beginData["totalObjects"]));
-relatedNodeLabel=WebInspector.UIString("Layout root");
-break;
-case recordTypes.ConsoleTime:
-contentHelper.appendTextRow(WebInspector.UIString("Message"),event.name);
-break;
-case recordTypes.WebSocketCreate:
-case recordTypes.WebSocketSendHandshakeRequest:
-case recordTypes.WebSocketReceiveHandshakeResponse:
-case recordTypes.WebSocketDestroy:
-var initiatorData=initiator?initiator.args["data"]:eventData;
-if(typeof initiatorData["webSocketURL"]!=="undefined")
-contentHelper.appendTextRow(WebInspector.UIString("URL"),initiatorData["webSocketURL"]);
-if(typeof initiatorData["webSocketProtocol"]!=="undefined")
-contentHelper.appendTextRow(WebInspector.UIString("WebSocket Protocol"),initiatorData["webSocketProtocol"]);
-if(typeof eventData["message"]!=="undefined")
-contentHelper.appendTextRow(WebInspector.UIString("Message"),eventData["message"]);
-break;
-case recordTypes.EmbedderCallback:
-contentHelper.appendTextRow(WebInspector.UIString("Callback Function"),eventData["callbackName"]);
-break;
-case recordTypes.Animation:
-if(event.phase===WebInspector.TracingModel.Phase.NestableAsyncInstant)
-contentHelper.appendTextRow(WebInspector.UIString("State"),eventData["state"]);
-break;
-case recordTypes.ParseHTML:
-var beginData=event.args["beginData"];
-var url=beginData["url"];
-var startLine=beginData["startLine"]-1;
-var endLine=event.args["endData"]?event.args["endData"]["endLine"]-1:undefined;
-if(url)
-contentHelper.appendLocationRange(WebInspector.UIString("Range"),url,startLine,endLine);
-break;
-
-case recordTypes.FireIdleCallback:
-contentHelper.appendTextRow(WebInspector.UIString("Allotted Time"),Number.millisToString(eventData["allottedMilliseconds"]));
-contentHelper.appendTextRow(WebInspector.UIString("Invoked by Timeout"),eventData["timedOut"]);
-
-
-case recordTypes.RequestIdleCallback:
-case recordTypes.CancelIdleCallback:
-contentHelper.appendTextRow(WebInspector.UIString("Callback ID"),eventData["id"]);
-break;
-case recordTypes.EventDispatch:
-contentHelper.appendTextRow(WebInspector.UIString("Type"),eventData["type"]);
-break;
-
-default:
-var detailsNode=WebInspector.TimelineUIUtils.buildDetailsNodeForTraceEvent(event,model.targetByEvent(event),linkifier);
-if(detailsNode)
-contentHelper.appendElementRow(WebInspector.UIString("Details"),detailsNode);
-break;}
-
-
-if(event.timeWaitingForMainThread)
-contentHelper.appendTextRow(WebInspector.UIString("Time Waiting for Main Thread"),Number.millisToString(event.timeWaitingForMainThread,true));
-
-var relatedNode=relatedNodesMap&&relatedNodesMap.get(event.backendNodeId);
-if(relatedNode)
-contentHelper.appendElementRow(relatedNodeLabel||WebInspector.UIString("Related Node"),WebInspector.DOMPresentationUtils.linkifyNodeReference(relatedNode));
-
-if(event.previewElement){
-contentHelper.addSection(WebInspector.UIString("Preview"));
-contentHelper.appendElementRow("",event.previewElement);
-}
-
-if(event.stackTrace||event.initiator&&event.initiator.stackTrace||event.invalidationTrackingEvents)
-WebInspector.TimelineUIUtils._generateCauses(event,model.targetByEvent(event),relatedNodesMap,contentHelper);
-
-var showPieChart=detailed&&WebInspector.TimelineUIUtils._aggregatedStatsForTraceEvent(stats,model,event);
-if(showPieChart){
-contentHelper.addSection(WebInspector.UIString("Aggregated Time"));
-var pieChart=WebInspector.TimelineUIUtils.generatePieChart(stats,WebInspector.TimelineUIUtils.eventStyle(event).category,event.selfTime);
-contentHelper.appendElementRow("",pieChart);
-}
-
-return contentHelper.fragment;
-};
-
-WebInspector.TimelineUIUtils._aggregatedStatsKey=Symbol("aggregatedStats");
-
-
-
-
-
-
-
-WebInspector.TimelineUIUtils.buildRangeStats=function(model,startTime,endTime)
-{
-var aggregatedStats={};
-
-
-
-
-
-
-function compareEndTime(value,task)
-{
-return value<task.endTime()?-1:1;
-}
-var mainThreadTasks=model.mainThreadTasks();
-var taskIndex=mainThreadTasks.lowerBound(startTime,compareEndTime);
-for(;taskIndex<mainThreadTasks.length;++taskIndex){
-var task=mainThreadTasks[taskIndex];
-if(task.startTime()>endTime)
-break;
-if(task.startTime()>startTime&&task.endTime()<endTime){
-
-var taskStats=task[WebInspector.TimelineUIUtils._aggregatedStatsKey];
-if(!taskStats){
-taskStats={};
-WebInspector.TimelineUIUtils._collectAggregatedStatsForRecord(task,startTime,endTime,taskStats);
-task[WebInspector.TimelineUIUtils._aggregatedStatsKey]=taskStats;
-}
-for(var key in taskStats)
-aggregatedStats[key]=(aggregatedStats[key]||0)+taskStats[key];
-continue;
-}
-WebInspector.TimelineUIUtils._collectAggregatedStatsForRecord(task,startTime,endTime,aggregatedStats);
-}
-
-var aggregatedTotal=0;
-for(var categoryName in aggregatedStats)
-aggregatedTotal+=aggregatedStats[categoryName];
-aggregatedStats["idle"]=Math.max(0,endTime-startTime-aggregatedTotal);
-
-var startOffset=startTime-model.minimumRecordTime();
-var endOffset=endTime-model.minimumRecordTime();
-
-var contentHelper=new WebInspector.TimelineDetailsContentHelper(null,null);
-contentHelper.addSection(WebInspector.UIString("Range:  %s \u2013 %s",Number.millisToString(startOffset),Number.millisToString(endOffset)));
-var pieChart=WebInspector.TimelineUIUtils.generatePieChart(aggregatedStats);
-contentHelper.appendElementRow("",pieChart);
-return contentHelper.fragment;
-};
-
-
-
-
-
-
-
-WebInspector.TimelineUIUtils._collectAggregatedStatsForRecord=function(record,startTime,endTime,aggregatedStats)
-{
-var records=[];
-
-if(!record.endTime()||record.endTime()<startTime||record.startTime()>endTime)
-return;
-
-var childrenTime=0;
-var children=record.children()||[];
-for(var i=0;i<children.length;++i){
-var child=children[i];
-if(!child.endTime()||child.endTime()<startTime||child.startTime()>endTime)
-continue;
-childrenTime+=Math.min(endTime,child.endTime())-Math.max(startTime,child.startTime());
-WebInspector.TimelineUIUtils._collectAggregatedStatsForRecord(child,startTime,endTime,aggregatedStats);
-}
-var categoryName=WebInspector.TimelineUIUtils.categoryForRecord(record).name;
-var ownTime=Math.min(endTime,record.endTime())-Math.max(startTime,record.startTime())-childrenTime;
-aggregatedStats[categoryName]=(aggregatedStats[categoryName]||0)+ownTime;
-};
-
-
-
-
-
-
-
-WebInspector.TimelineUIUtils.buildNetworkRequestDetails=function(request,model,linkifier)
-{
-var target=model.targetByEvent(request.children[0]);
-var contentHelper=new WebInspector.TimelineDetailsContentHelper(target,linkifier);
-
-var duration=request.endTime-(request.startTime||-Infinity);
-var items=[];
-if(request.url)
-contentHelper.appendElementRow(WebInspector.UIString("URL"),WebInspector.linkifyURLAsNode(request.url));
-if(isFinite(duration))
-contentHelper.appendTextRow(WebInspector.UIString("Duration"),Number.millisToString(duration,true));
-if(request.requestMethod)
-contentHelper.appendTextRow(WebInspector.UIString("Request Method"),request.requestMethod);
-if(typeof request.priority==="string"){
-var priority=WebInspector.uiLabelForPriority(request.priority);
-contentHelper.appendTextRow(WebInspector.UIString("Priority"),priority);
-}
-if(request.mimeType)
-contentHelper.appendTextRow(WebInspector.UIString("Mime Type"),request.mimeType);
-
-var title=WebInspector.UIString("Initiator");
-var sendRequest=request.children[0];
-var topFrame=WebInspector.TimelineUIUtils.topStackFrame(sendRequest);
-if(topFrame){
-var link=linkifier.maybeLinkifyConsoleCallFrame(target,topFrame);
-if(link)
-contentHelper.appendElementRow(title,link);
-}else if(sendRequest.initiator){
-var initiatorURL=WebInspector.TimelineUIUtils.eventURL(sendRequest.initiator);
-if(initiatorURL){
-var link=linkifier.maybeLinkifyScriptLocation(target,null,initiatorURL,0);
-if(link)
-contentHelper.appendElementRow(title,link);
-}
-}
-
-
-
-
-function action(fulfill)
-{
-WebInspector.DOMPresentationUtils.buildImagePreviewContents(target,request.url,false,saveImage);
-
-
-
-function saveImage(element)
-{
-request.previewElement=element||null;
-fulfill(request.previewElement);
-}
-}
-var previewPromise;
-if(request.previewElement)
-previewPromise=Promise.resolve(request.previewElement);else
-
-previewPromise=request.url&&target?new Promise(action):Promise.resolve(null);
-
-
-
-
-function appendPreview(element)
-{
-if(element)
-contentHelper.appendElementRow(WebInspector.UIString("Preview"),request.previewElement);
-return contentHelper.fragment;
-}
-return previewPromise.then(appendPreview);
-};
-
-
-
-
-
-WebInspector.TimelineUIUtils._stackTraceFromCallFrames=function(callFrames)
-{
-return{callFrames:callFrames};
-};
-
-
-
-
-
-
-
-WebInspector.TimelineUIUtils._generateCauses=function(event,target,relatedNodesMap,contentHelper)
-{
-var recordTypes=WebInspector.TimelineModel.RecordType;
-
-var callSiteStackLabel;
-var stackLabel;
-var initiator=event.initiator;
-
-switch(event.name){
-case recordTypes.TimerFire:
-callSiteStackLabel=WebInspector.UIString("Timer Installed");
-break;
-case recordTypes.FireAnimationFrame:
-callSiteStackLabel=WebInspector.UIString("Animation Frame Requested");
-break;
-case recordTypes.FireIdleCallback:
-callSiteStackLabel=WebInspector.UIString("Idle Callback Requested");
-break;
-case recordTypes.UpdateLayoutTree:
-case recordTypes.RecalculateStyles:
-stackLabel=WebInspector.UIString("Recalculation Forced");
-break;
-case recordTypes.Layout:
-callSiteStackLabel=WebInspector.UIString("First Layout Invalidation");
-stackLabel=WebInspector.UIString("Layout Forced");
-break;}
-
-
-
-if(event.stackTrace&&event.stackTrace.length){
-contentHelper.addSection(WebInspector.UIString("Call Stacks"));
-contentHelper.appendStackTrace(stackLabel||WebInspector.UIString("Stack Trace"),WebInspector.TimelineUIUtils._stackTraceFromCallFrames(event.stackTrace));
-}
-
-
-if(event.invalidationTrackingEvents&&target){
-contentHelper.addSection(WebInspector.UIString("Invalidations"));
-WebInspector.TimelineUIUtils._generateInvalidations(event,target,relatedNodesMap,contentHelper);
-}else if(initiator&&initiator.stackTrace){
-contentHelper.appendStackTrace(callSiteStackLabel||WebInspector.UIString("First Invalidated"),WebInspector.TimelineUIUtils._stackTraceFromCallFrames(initiator.stackTrace));
-}
-};
-
-
-
-
-
-
-
-WebInspector.TimelineUIUtils._generateInvalidations=function(event,target,relatedNodesMap,contentHelper)
-{
-if(!event.invalidationTrackingEvents)
-return;
-
-var invalidations={};
-event.invalidationTrackingEvents.forEach(function(invalidation){
-if(!invalidations[invalidation.type])
-invalidations[invalidation.type]=[invalidation];else
-
-invalidations[invalidation.type].push(invalidation);
-});
-
-Object.keys(invalidations).forEach(function(type){
-WebInspector.TimelineUIUtils._generateInvalidationsForType(
-type,target,invalidations[type],relatedNodesMap,contentHelper);
-});
-};
-
-
-
-
-
-
-
-
-WebInspector.TimelineUIUtils._generateInvalidationsForType=function(type,target,invalidations,relatedNodesMap,contentHelper)
-{
-var title;
-switch(type){
-case WebInspector.TimelineModel.RecordType.StyleRecalcInvalidationTracking:
-title=WebInspector.UIString("Style Invalidations");
-break;
-case WebInspector.TimelineModel.RecordType.LayoutInvalidationTracking:
-title=WebInspector.UIString("Layout Invalidations");
-break;
-default:
-title=WebInspector.UIString("Other Invalidations");
-break;}
-
-
-var invalidationsTreeOutline=new TreeOutlineInShadow();
-invalidationsTreeOutline.registerRequiredCSS("timeline/invalidationsTree.css");
-invalidationsTreeOutline.element.classList.add("invalidations-tree");
-
-var invalidationGroups=groupInvalidationsByCause(invalidations);
-invalidationGroups.forEach(function(group){
-var groupElement=new WebInspector.TimelineUIUtils.InvalidationsGroupElement(target,relatedNodesMap,contentHelper,group);
-invalidationsTreeOutline.appendChild(groupElement);
-});
-contentHelper.appendElementRow(title,invalidationsTreeOutline.element,false,true);
-
-
-
-
-
-function groupInvalidationsByCause(invalidations)
-{
-
-var causeToInvalidationMap=new Map();
-for(var index=0;index<invalidations.length;index++){
-var invalidation=invalidations[index];
-var causeKey="";
-if(invalidation.cause.reason)
-causeKey+=invalidation.cause.reason+".";
-if(invalidation.cause.stackTrace){
-invalidation.cause.stackTrace.forEach(function(stackFrame){
-causeKey+=stackFrame["functionName"]+".";
-causeKey+=stackFrame["scriptId"]+".";
-causeKey+=stackFrame["url"]+".";
-causeKey+=stackFrame["lineNumber"]+".";
-causeKey+=stackFrame["columnNumber"]+".";
-});
-}
-
-if(causeToInvalidationMap.has(causeKey))
-causeToInvalidationMap.get(causeKey).push(invalidation);else
-
-causeToInvalidationMap.set(causeKey,[invalidation]);
-}
-return causeToInvalidationMap.valuesArray();
-}
-};
-
-
-
-
-
-WebInspector.TimelineUIUtils._collectInvalidationNodeIds=function(nodeIds,invalidations)
-{
-for(var i=0;i<invalidations.length;++i){
-if(invalidations[i].nodeId)
-nodeIds.add(invalidations[i].nodeId);
-}
-};
-
-
-
-
-
-
-
-
-
-WebInspector.TimelineUIUtils.InvalidationsGroupElement=function(target,relatedNodesMap,contentHelper,invalidations)
-{
-TreeElement.call(this,"",true);
-
-this.listItemElement.classList.add("header");
-this.selectable=false;
-this.toggleOnClick=true;
-
-this._relatedNodesMap=relatedNodesMap;
-this._contentHelper=contentHelper;
-this._invalidations=invalidations;
-this.title=this._createTitle(target);
-};
-
-WebInspector.TimelineUIUtils.InvalidationsGroupElement.prototype={
-
-
-
-
-
-_createTitle:function(target)
-{
-var first=this._invalidations[0];
-var reason=first.cause.reason;
-var topFrame=first.cause.stackTrace&&first.cause.stackTrace[0];
-
-var title=createElement("span");
-if(reason)
-title.createTextChild(WebInspector.UIString("%s for ",reason));else
-
-title.createTextChild(WebInspector.UIString("Unknown cause for "));
-
-this._appendTruncatedNodeList(title,this._invalidations);
-
-if(topFrame&&this._contentHelper.linkifier()){
-title.createTextChild(WebInspector.UIString(". "));
-var stack=title.createChild("span","monospace");
-stack.createChild("span").textContent=WebInspector.beautifyFunctionName(topFrame.functionName);
-var link=this._contentHelper.linkifier().maybeLinkifyConsoleCallFrame(target,topFrame);
-if(link){
-stack.createChild("span").textContent=" @ ";
-stack.createChild("span").appendChild(link);
-}
-}
-
-return title;
-},
-
-
-
-
-onpopulate:function()
-{
-var content=createElementWithClass("div","content");
-
-var first=this._invalidations[0];
-if(first.cause.stackTrace){
-var stack=content.createChild("div");
-stack.createTextChild(WebInspector.UIString("Stack trace:"));
-this._contentHelper.createChildStackTraceElement(stack,WebInspector.TimelineUIUtils._stackTraceFromCallFrames(first.cause.stackTrace));
-}
-
-content.createTextChild(this._invalidations.length>1?WebInspector.UIString("Nodes:"):WebInspector.UIString("Node:"));
-var nodeList=content.createChild("div","node-list");
-var firstNode=true;
-for(var i=0;i<this._invalidations.length;i++){
-var invalidation=this._invalidations[i];
-var invalidationNode=this._createInvalidationNode(invalidation,true);
-if(invalidationNode){
-if(!firstNode)
-nodeList.createTextChild(WebInspector.UIString(", "));
-firstNode=false;
-
-nodeList.appendChild(invalidationNode);
-
-var extraData=invalidation.extraData?", "+invalidation.extraData:"";
-if(invalidation.changedId)
-nodeList.createTextChild(WebInspector.UIString("(changed id to \"%s\"%s)",invalidation.changedId,extraData));else
-if(invalidation.changedClass)
-nodeList.createTextChild(WebInspector.UIString("(changed class to \"%s\"%s)",invalidation.changedClass,extraData));else
-if(invalidation.changedAttribute)
-nodeList.createTextChild(WebInspector.UIString("(changed attribute to \"%s\"%s)",invalidation.changedAttribute,extraData));else
-if(invalidation.changedPseudo)
-nodeList.createTextChild(WebInspector.UIString("(changed pesudo to \"%s\"%s)",invalidation.changedPseudo,extraData));else
-if(invalidation.selectorPart)
-nodeList.createTextChild(WebInspector.UIString("(changed \"%s\"%s)",invalidation.selectorPart,extraData));
-}
-}
-
-var contentTreeElement=new TreeElement(content,false);
-contentTreeElement.selectable=false;
-this.appendChild(contentTreeElement);
-},
-
-
-
-
-
-_appendTruncatedNodeList:function(parentElement,invalidations)
-{
-var invalidationNodes=[];
-var invalidationNodeIdMap={};
-for(var i=0;i<invalidations.length;i++){
-var invalidation=invalidations[i];
-var invalidationNode=this._createInvalidationNode(invalidation,false);
-invalidationNode.addEventListener("click",consumeEvent,false);
-if(invalidationNode&&!invalidationNodeIdMap[invalidation.nodeId]){
-invalidationNodes.push(invalidationNode);
-invalidationNodeIdMap[invalidation.nodeId]=true;
-}
-}
-
-if(invalidationNodes.length===1){
-parentElement.appendChild(invalidationNodes[0]);
-}else if(invalidationNodes.length===2){
-parentElement.appendChild(invalidationNodes[0]);
-parentElement.createTextChild(WebInspector.UIString(" and "));
-parentElement.appendChild(invalidationNodes[1]);
-}else if(invalidationNodes.length>=3){
-parentElement.appendChild(invalidationNodes[0]);
-parentElement.createTextChild(WebInspector.UIString(", "));
-parentElement.appendChild(invalidationNodes[1]);
-parentElement.createTextChild(WebInspector.UIString(", and %s others",invalidationNodes.length-2));
-}
-},
-
-
-
-
-
-_createInvalidationNode:function(invalidation,showUnknownNodes)
-{
-var node=invalidation.nodeId&&this._relatedNodesMap?this._relatedNodesMap.get(invalidation.nodeId):null;
-if(node)
-return WebInspector.DOMPresentationUtils.linkifyNodeReference(node);
-if(invalidation.nodeName){
-var nodeSpan=createElement("span");
-nodeSpan.textContent=WebInspector.UIString("[ %s ]",invalidation.nodeName);
-return nodeSpan;
-}
-if(showUnknownNodes){
-var nodeSpan=createElement("span");
-return nodeSpan.createTextChild(WebInspector.UIString("[ unknown node ]"));
-}
-},
-
-__proto__:TreeElement.prototype};
-
-
-
-
-
-
-
-
-WebInspector.TimelineUIUtils._aggregatedStatsForTraceEvent=function(total,model,event)
-{
-var events=model.inspectedTargetEvents();
-
-
-
-
-
-function eventComparator(startTime,e)
-{
-return startTime-e.startTime;
-}
-var index=events.binaryIndexOf(event.startTime,eventComparator);
-
-if(index<0)
-return false;
-var hasChildren=false;
-var endTime=event.endTime;
-if(endTime){
-for(var i=index;i<events.length;i++){
-var nextEvent=events[i];
-if(nextEvent.startTime>=endTime)
-break;
-if(!nextEvent.selfTime)
-continue;
-if(nextEvent.thread!==event.thread)
-continue;
-if(i>index)
-hasChildren=true;
-var categoryName=WebInspector.TimelineUIUtils.eventStyle(nextEvent).category.name;
-total[categoryName]=(total[categoryName]||0)+nextEvent.selfTime;
-}
-}
-if(WebInspector.TracingModel.isAsyncPhase(event.phase)){
-if(event.endTime){
-var aggregatedTotal=0;
-for(var categoryName in total)
-aggregatedTotal+=total[categoryName];
-total["idle"]=Math.max(0,event.endTime-event.startTime-aggregatedTotal);
-}
-return false;
-}
-return hasChildren;
-};
-
-
-
-
-
-
-WebInspector.TimelineUIUtils.buildPicturePreviewContent=function(event,target,callback)
-{
-new WebInspector.LayerPaintEvent(event,target).loadSnapshot(onSnapshotLoaded);
-
-
-
-
-function onSnapshotLoaded(rect,snapshot)
-{
-if(!snapshot){
-callback();
-return;
-}
-snapshot.requestImage(null,null,1,onGotImage);
-snapshot.dispose();
-}
-
-
-
-
-function onGotImage(imageURL)
-{
-if(!imageURL){
-callback();
-return;
-}
-var container=createElement("div");
-container.classList.add("image-preview-container","vbox","link");
-var img=container.createChild("img");
-img.src=imageURL;
-var paintProfilerButton=container.createChild("a");
-paintProfilerButton.textContent=WebInspector.UIString("Paint Profiler");
-container.addEventListener("click",showPaintProfiler,false);
-callback(container);
-}
-
-function showPaintProfiler()
-{
-WebInspector.TimelinePanel.instance().select(WebInspector.TimelineSelection.fromTraceEvent(event),WebInspector.TimelinePanel.DetailsTab.PaintProfiler);
-}
-};
-
-
-
-
-
-
-
-WebInspector.TimelineUIUtils.createEventDivider=function(recordType,title,position)
-{
-var eventDivider=createElement("div");
-eventDivider.className="resources-event-divider";
-var recordTypes=WebInspector.TimelineModel.RecordType;
-
-if(recordType===recordTypes.MarkDOMContent)
-eventDivider.className+=" resources-blue-divider";else
-if(recordType===recordTypes.MarkLoad)
-eventDivider.className+=" resources-red-divider";else
-if(recordType===recordTypes.MarkFirstPaint)
-eventDivider.className+=" resources-green-divider";else
-if(recordType===recordTypes.TimeStamp||recordType===recordTypes.ConsoleTime||recordType===recordTypes.UserTiming)
-eventDivider.className+=" resources-orange-divider";else
-if(recordType===recordTypes.BeginFrame)
-eventDivider.className+=" timeline-frame-divider";
-
-if(title)
-eventDivider.title=title;
-eventDivider.style.left=position+"px";
-return eventDivider;
-};
-
-
-
-
-
-
-
-WebInspector.TimelineUIUtils.createDividerForRecord=function(record,zeroTime,position)
-{
-var startTime=Number.millisToString(record.startTime()-zeroTime);
-var title=WebInspector.UIString("%s at %s",WebInspector.TimelineUIUtils.eventTitle(record.traceEvent()),startTime);
-return WebInspector.TimelineUIUtils.createEventDivider(record.type(),title,position);
-};
-
-
-
-
-WebInspector.TimelineUIUtils._visibleTypes=function()
-{
-var eventStyles=WebInspector.TimelineUIUtils._initEventStyles();
-var result=[];
-for(var name in eventStyles){
-if(!eventStyles[name].hidden)
-result.push(name);
-}
-return result;
-};
-
-
-
-
-WebInspector.TimelineUIUtils.visibleEventsFilter=function()
-{
-return new WebInspector.TimelineVisibleEventsFilter(WebInspector.TimelineUIUtils._visibleTypes());
-};
-
-
-
-
-WebInspector.TimelineUIUtils.categories=function()
-{
-if(WebInspector.TimelineUIUtils._categories)
-return WebInspector.TimelineUIUtils._categories;
-WebInspector.TimelineUIUtils._categories={
-loading:new WebInspector.TimelineCategory("loading",WebInspector.UIString("Loading"),true,"hsl(214, 67%, 74%)","hsl(214, 67%, 66%)"),
-scripting:new WebInspector.TimelineCategory("scripting",WebInspector.UIString("Scripting"),true,"hsl(43, 83%, 72%)","hsl(43, 83%, 64%) "),
-rendering:new WebInspector.TimelineCategory("rendering",WebInspector.UIString("Rendering"),true,"hsl(256, 67%, 76%)","hsl(256, 67%, 70%)"),
-painting:new WebInspector.TimelineCategory("painting",WebInspector.UIString("Painting"),true,"hsl(109, 33%, 64%)","hsl(109, 33%, 55%)"),
-gpu:new WebInspector.TimelineCategory("gpu",WebInspector.UIString("GPU"),false,"hsl(109, 33%, 64%)","hsl(109, 33%, 55%)"),
-other:new WebInspector.TimelineCategory("other",WebInspector.UIString("Other"),false,"hsl(0, 0%, 87%)","hsl(0, 0%, 79%)"),
-idle:new WebInspector.TimelineCategory("idle",WebInspector.UIString("Idle"),false,"hsl(0, 100%, 100%)","hsl(0, 100%, 100%)")};
-
-return WebInspector.TimelineUIUtils._categories;
-};
-
-
-
-
-
-WebInspector.TimelineUIUtils.titleForAsyncEventGroup=function(group)
-{
-if(!WebInspector.TimelineUIUtils._titleForAsyncEventGroupMap){
-var groups=WebInspector.TimelineModel.AsyncEventGroup;
-WebInspector.TimelineUIUtils._titleForAsyncEventGroupMap=new Map([
-[groups.animation,WebInspector.UIString("Animation")],
-[groups.console,WebInspector.UIString("Console")],
-[groups.userTiming,WebInspector.UIString("User Timing")],
-[groups.input,WebInspector.UIString("Input")]]);
-
-}
-return WebInspector.TimelineUIUtils._titleForAsyncEventGroupMap.get(group)||"";
-};
-
-
-
-
-
-
-
-WebInspector.TimelineUIUtils.generatePieChart=function(aggregatedStats,selfCategory,selfTime)
-{
-var total=0;
-for(var categoryName in aggregatedStats)
-total+=aggregatedStats[categoryName];
-
-var element=createElementWithClass("div","timeline-details-view-pie-chart-wrapper hbox");
-var pieChart=new WebInspector.PieChart(100);
-pieChart.element.classList.add("timeline-details-view-pie-chart");
-pieChart.setTotal(total);
-var pieChartContainer=element.createChild("div","vbox");
-pieChartContainer.appendChild(pieChart.element);
-pieChartContainer.createChild("div","timeline-details-view-pie-chart-total").textContent=WebInspector.UIString("Total: %s",Number.millisToString(total,true));
-var footerElement=element.createChild("div","timeline-aggregated-info-legend");
-
-
-
-
-
-
-
-function appendLegendRow(name,title,value,color)
-{
-if(!value)
-return;
-pieChart.addSlice(value,color);
-var rowElement=footerElement.createChild("div");
-rowElement.createChild("span","timeline-aggregated-legend-value").textContent=Number.preciseMillisToString(value,1);
-rowElement.createChild("span","timeline-aggregated-legend-swatch").style.backgroundColor=color;
-rowElement.createChild("span","timeline-aggregated-legend-title").textContent=title;
-}
-
-
-if(selfCategory){
-if(selfTime)
-appendLegendRow(selfCategory.name,WebInspector.UIString("%s (self)",selfCategory.title),selfTime,selfCategory.color);
-
-var categoryTime=aggregatedStats[selfCategory.name];
-var value=categoryTime-selfTime;
-if(value>0)
-appendLegendRow(selfCategory.name,WebInspector.UIString("%s (children)",selfCategory.title),value,selfCategory.childColor);
-}
-
-
-for(var categoryName in WebInspector.TimelineUIUtils.categories()){
-var category=WebInspector.TimelineUIUtils.categories()[categoryName];
-if(category===selfCategory)
-continue;
-appendLegendRow(category.name,category.title,aggregatedStats[category.name],category.childColor);
-}
-return element;
-};
-
-
-
-
-
-
-
-WebInspector.TimelineUIUtils.generateDetailsContentForFrame=function(frameModel,frame,filmStripFrame)
-{
-var pieChart=WebInspector.TimelineUIUtils.generatePieChart(frame.timeByCategory);
-var contentHelper=new WebInspector.TimelineDetailsContentHelper(null,null);
-contentHelper.addSection(WebInspector.UIString("Frame"));
-
-var duration=WebInspector.TimelineUIUtils.frameDuration(frame);
-contentHelper.appendElementRow(WebInspector.UIString("Duration"),duration,frame.hasWarnings());
-if(filmStripFrame){
-var filmStripPreview=createElementWithClass("img","timeline-filmstrip-preview");
-filmStripFrame.imageDataPromise().then(onGotImageData.bind(null,filmStripPreview));
-contentHelper.appendElementRow("",filmStripPreview);
-filmStripPreview.addEventListener("click",frameClicked.bind(null,filmStripFrame),false);
-}
-var durationInMillis=frame.endTime-frame.startTime;
-contentHelper.appendTextRow(WebInspector.UIString("FPS"),Math.floor(1000/durationInMillis));
-contentHelper.appendTextRow(WebInspector.UIString("CPU time"),Number.millisToString(frame.cpuTime,true));
-
-if(Runtime.experiments.isEnabled("layersPanel")&&frame.layerTree){
-contentHelper.appendElementRow(WebInspector.UIString("Layer tree"),
-WebInspector.Linkifier.linkifyUsingRevealer(frame.layerTree,WebInspector.UIString("show")));
-}
-
-
-
-
-
-function onGotImageData(image,data)
-{
-if(data)
-image.src="data:image/jpg;base64,"+data;
-}
-
-
-
-
-function frameClicked(filmStripFrame)
-{
-new WebInspector.FilmStripView.Dialog(filmStripFrame,0);
-}
-
-return contentHelper.fragment;
-};
-
-
-
-
-
-WebInspector.TimelineUIUtils.frameDuration=function(frame)
-{
-var durationText=WebInspector.UIString("%s (at %s)",Number.millisToString(frame.endTime-frame.startTime,true),
-Number.millisToString(frame.startTimeOffset,true));
-var element=createElement("span");
-element.createTextChild(durationText);
-if(!frame.hasWarnings())
-return element;
-element.createTextChild(WebInspector.UIString(". Long frame times are an indication of "));
-element.appendChild(WebInspector.linkifyURLAsNode("https://developers.google.com/web/fundamentals/performance/rendering/",
-WebInspector.UIString("jank"),undefined,true));
-element.createTextChild(".");
-return element;
-};
-
-
-
-
-
-
-
-
-
-
-WebInspector.TimelineUIUtils.createFillStyle=function(context,width,height,color0,color1,color2)
-{
-var gradient=context.createLinearGradient(0,0,width,height);
-gradient.addColorStop(0,color0);
-gradient.addColorStop(0.25,color1);
-gradient.addColorStop(0.75,color1);
-gradient.addColorStop(1,color2);
-return gradient;
-};
-
-
-
-
-
-WebInspector.TimelineUIUtils.quadWidth=function(quad)
-{
-return Math.round(Math.sqrt(Math.pow(quad[0]-quad[2],2)+Math.pow(quad[1]-quad[3],2)));
-};
-
-
-
-
-
-WebInspector.TimelineUIUtils.quadHeight=function(quad)
-{
-return Math.round(Math.sqrt(Math.pow(quad[0]-quad[6],2)+Math.pow(quad[1]-quad[7],2)));
-};
-
-
-
-
-
-
-
-WebInspector.TimelineUIUtils.EventDispatchTypeDescriptor=function(priority,color,eventTypes)
-{
-this.priority=priority;
-this.color=color;
-this.eventTypes=eventTypes;
-};
-
-
-
-
-WebInspector.TimelineUIUtils.eventDispatchDesciptors=function()
-{
-if(WebInspector.TimelineUIUtils._eventDispatchDesciptors)
-return WebInspector.TimelineUIUtils._eventDispatchDesciptors;
-var lightOrange="hsl(40,100%,80%)";
-var orange="hsl(40,100%,50%)";
-var green="hsl(90,100%,40%)";
-var purple="hsl(256,100%,75%)";
-WebInspector.TimelineUIUtils._eventDispatchDesciptors=[
-new WebInspector.TimelineUIUtils.EventDispatchTypeDescriptor(1,lightOrange,["mousemove","mouseenter","mouseleave","mouseout","mouseover"]),
-new WebInspector.TimelineUIUtils.EventDispatchTypeDescriptor(1,lightOrange,["pointerover","pointerout","pointerenter","pointerleave","pointermove"]),
-new WebInspector.TimelineUIUtils.EventDispatchTypeDescriptor(2,green,["wheel"]),
-new WebInspector.TimelineUIUtils.EventDispatchTypeDescriptor(3,orange,["click","mousedown","mouseup"]),
-new WebInspector.TimelineUIUtils.EventDispatchTypeDescriptor(3,orange,["touchstart","touchend","touchmove","touchcancel"]),
-new WebInspector.TimelineUIUtils.EventDispatchTypeDescriptor(3,orange,["pointerdown","pointerup","pointercancel","gotpointercapture","lostpointercapture"]),
-new WebInspector.TimelineUIUtils.EventDispatchTypeDescriptor(3,purple,["keydown","keyup","keypress"])];
-
-return WebInspector.TimelineUIUtils._eventDispatchDesciptors;
-};
-
-
-
-
-
-
-
-
-
-
-WebInspector.TimelineCategory=function(name,title,visible,childColor,color)
-{
-this.name=name;
-this.title=title;
-this.visible=visible;
-this.childColor=childColor;
-this.color=color;
-this.hidden=false;
-};
-
-
-WebInspector.TimelineCategory.Events={
-VisibilityChanged:Symbol("VisibilityChanged")};
-
-
-WebInspector.TimelineCategory.prototype={
-
-
-
-get hidden()
-{
-return this._hidden;
-},
-
-set hidden(hidden)
-{
-this._hidden=hidden;
-this.dispatchEventToListeners(WebInspector.TimelineCategory.Events.VisibilityChanged,this);
-},
-
-__proto__:WebInspector.Object.prototype};
-
-
-
-
-
-
-
-
-
-
-
-
-WebInspector.TimelineMarkerStyle;
-
-
-
-
-
-WebInspector.TimelineUIUtils.markerStyleForEvent=function(event)
-{
-var red="rgb(255, 0, 0)";
-var blue="rgb(0, 0, 255)";
-var orange="rgb(255, 178, 23)";
-var green="rgb(0, 130, 0)";
-var tallMarkerDashStyle=[10,5];
-
-var title=WebInspector.TimelineUIUtils.eventTitle(event);
-
-if(event.hasCategory(WebInspector.TimelineModel.Category.Console)||event.hasCategory(WebInspector.TimelineModel.Category.UserTiming)){
-return{
-title:title,
-dashStyle:tallMarkerDashStyle,
-lineWidth:0.5,
-color:orange,
-tall:false,
-lowPriority:false};
-
-}
-var recordTypes=WebInspector.TimelineModel.RecordType;
-var tall=false;
-var color=green;
-switch(event.name){
-case recordTypes.MarkDOMContent:
-color=blue;
-tall=true;
-break;
-case recordTypes.MarkLoad:
-color=red;
-tall=true;
-break;
-case recordTypes.MarkFirstPaint:
-color=green;
-tall=true;
-break;
-case recordTypes.TimeStamp:
-color=orange;
-break;}
-
-return{
-title:title,
-dashStyle:tallMarkerDashStyle,
-lineWidth:0.5,
-color:color,
-tall:tall,
-lowPriority:false};
-
-};
-
-
-
-
-WebInspector.TimelineUIUtils.markerStyleForFrame=function()
-{
-return{
-title:WebInspector.UIString("Frame"),
-color:"rgba(100, 100, 100, 0.4)",
-lineWidth:3,
-dashStyle:[3],
-tall:true,
-lowPriority:true};
-
-};
-
-
-
-
-
-WebInspector.TimelineUIUtils.colorForURL=function(url)
-{
-if(!WebInspector.TimelineUIUtils.colorForURL._colorGenerator){
-WebInspector.TimelineUIUtils.colorForURL._colorGenerator=new WebInspector.FlameChart.ColorGenerator(
-{min:30,max:330},
-{min:50,max:80,count:3},
-85);
-}
-return WebInspector.TimelineUIUtils.colorForURL._colorGenerator.colorForID(url);
-};
-
-
-
-
-
-WebInspector.TimelinePopupContentHelper=function(title)
-{
-this._contentTable=createElement("table");
-var titleCell=this._createCell(WebInspector.UIString("%s - Details",title),"timeline-details-title");
-titleCell.colSpan=2;
-var titleRow=createElement("tr");
-titleRow.appendChild(titleCell);
-this._contentTable.appendChild(titleRow);
-};
-
-WebInspector.TimelinePopupContentHelper.prototype={
-
-
-
-contentTable:function()
-{
-return this._contentTable;
-},
-
-
-
-
-
-_createCell:function(content,styleName)
-{
-var text=createElement("label");
-text.createTextChild(String(content));
-var cell=createElement("td");
-cell.className="timeline-details";
-if(styleName)
-cell.className+=" "+styleName;
-cell.textContent=content;
-return cell;
-},
-
-
-
-
-
-appendTextRow:function(title,content)
-{
-var row=createElement("tr");
-row.appendChild(this._createCell(title,"timeline-details-row-title"));
-row.appendChild(this._createCell(content,"timeline-details-row-data"));
-this._contentTable.appendChild(row);
-},
-
-
-
-
-
-appendElementRow:function(title,content)
-{
-var row=createElement("tr");
-var titleCell=this._createCell(title,"timeline-details-row-title");
-row.appendChild(titleCell);
-var cell=createElement("td");
-cell.className="details";
-if(content instanceof Node)
-cell.appendChild(content);else
-
-cell.createTextChild(content||"");
-row.appendChild(cell);
-this._contentTable.appendChild(row);
-}};
-
-
-
-
-
-
-
-WebInspector.TimelineDetailsContentHelper=function(target,linkifier)
-{
-this.fragment=createDocumentFragment();
-
-this._linkifier=linkifier;
-this._target=target;
-
-this.element=createElementWithClass("div","timeline-details-view-block");
-this._tableElement=this.element.createChild("div","vbox timeline-details-chip-body");
-this.fragment.appendChild(this.element);
-};
-
-WebInspector.TimelineDetailsContentHelper.prototype={
-
-
-
-
-addSection:function(title,category)
-{
-if(!this._tableElement.hasChildNodes()){
-this.element.removeChildren();
-}else{
-this.element=createElementWithClass("div","timeline-details-view-block");
-this.fragment.appendChild(this.element);
-}
-
-if(title){
-var titleElement=this.element.createChild("div","timeline-details-chip-title");
-if(category)
-titleElement.createChild("div").style.backgroundColor=category.color;
-titleElement.createTextChild(title);
-}
-
-this._tableElement=this.element.createChild("div","vbox timeline-details-chip-body");
-this.fragment.appendChild(this.element);
-},
-
-
-
-
-linkifier:function()
-{
-return this._linkifier;
-},
-
-
-
-
-
-appendTextRow:function(title,value)
-{
-var rowElement=this._tableElement.createChild("div","timeline-details-view-row");
-rowElement.createChild("div","timeline-details-view-row-title").textContent=title;
-rowElement.createChild("div","timeline-details-view-row-value").textContent=value;
-},
-
-
-
-
-
-
-
-appendElementRow:function(title,content,isWarning,isStacked)
-{
-var rowElement=this._tableElement.createChild("div","timeline-details-view-row");
-if(isWarning)
-rowElement.classList.add("timeline-details-warning");
-if(isStacked)
-rowElement.classList.add("timeline-details-stack-values");
-var titleElement=rowElement.createChild("div","timeline-details-view-row-title");
-titleElement.textContent=title;
-var valueElement=rowElement.createChild("div","timeline-details-view-row-value");
-if(content instanceof Node)
-valueElement.appendChild(content);else
-
-valueElement.createTextChild(content||"");
-},
-
-
-
-
-
-
-
-appendLocationRow:function(title,url,startLine,startColumn)
-{
-if(!this._linkifier||!this._target)
-return;
-var link=this._linkifier.maybeLinkifyScriptLocation(this._target,null,url,startLine,startColumn);
-if(!link)
-return;
-this.appendElementRow(title,link);
-},
-
-
-
-
-
-
-
-appendLocationRange:function(title,url,startLine,endLine)
-{
-if(!this._linkifier||!this._target)
-return;
-var locationContent=createElement("span");
-var link=this._linkifier.maybeLinkifyScriptLocation(this._target,null,url,startLine);
-if(!link)
-return;
-locationContent.appendChild(link);
-locationContent.createTextChild(String.sprintf(" [%s\u2026%s]",startLine+1,endLine+1||""));
-this.appendElementRow(title,locationContent);
-},
-
-
-
-
-
-appendStackTrace:function(title,stackTrace)
-{
-if(!this._linkifier||!this._target)
-return;
-
-var rowElement=this._tableElement.createChild("div","timeline-details-view-row");
-rowElement.createChild("div","timeline-details-view-row-title").textContent=title;
-this.createChildStackTraceElement(rowElement,stackTrace);
-},
-
-
-
-
-
-createChildStackTraceElement:function(parentElement,stackTrace)
-{
-if(!this._linkifier||!this._target)
-return;
-parentElement.classList.add("timeline-details-stack-values");
-var stackTraceElement=parentElement.createChild("div","timeline-details-view-row-value timeline-details-view-row-stack-trace");
-var callFrameElem=WebInspector.DOMPresentationUtils.buildStackTracePreviewContents(this._target,this._linkifier,stackTrace);
-stackTraceElement.appendChild(callFrameElem);
-},
-
-
-
-
-
-appendWarningRow:function(event,warningType)
-{
-var warning=WebInspector.TimelineUIUtils.eventWarning(event,warningType);
-if(warning)
-this.appendElementRow(WebInspector.UIString("Warning"),warning,true);
-}};
-
-
-
-
-
-
-
-WebInspector.TimelineUIUtils.eventWarning=function(event,warningType)
-{
-var warning=warningType||event.warning;
-if(!warning)
-return null;
-var warnings=WebInspector.TimelineModel.WarningType;
-var span=createElement("span");
-var eventData=event.args["data"];
-
-switch(warning){
-case warnings.ForcedStyle:
-case warnings.ForcedLayout:
-span.appendChild(WebInspector.linkifyDocumentationURLAsNode("../../fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing#avoid-forced-synchronous-layouts",
-WebInspector.UIString("Forced reflow")));
-span.createTextChild(WebInspector.UIString(" is a likely performance bottleneck."));
-break;
-case warnings.IdleDeadlineExceeded:
-span.textContent=WebInspector.UIString("Idle callback execution extended beyond deadline by "+
-Number.millisToString(event.duration-eventData["allottedMilliseconds"],true));
-break;
-case warnings.V8Deopt:
-span.appendChild(WebInspector.linkifyURLAsNode("https://github.com/GoogleChrome/devtools-docs/issues/53",
-WebInspector.UIString("Not optimized"),undefined,true));
-span.createTextChild(WebInspector.UIString(": %s",eventData["deoptReason"]));
-break;
-default:
-console.assert(false,"Unhandled TimelineModel.WarningType");}
-
-return span;
-};
-
-},{}],226:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-WebInspector.TracingLayerPayload;
-
-
-
-
-
-
-
-
-WebInspector.TracingLayerTile;
-
-
-
-
-
-WebInspector.LayerTreeModel=function(target)
-{
-WebInspector.SDKModel.call(this,WebInspector.LayerTreeModel,target);
-target.registerLayerTreeDispatcher(new WebInspector.LayerTreeDispatcher(this));
-WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Events.MainFrameNavigated,this._onMainFrameNavigated,this);
-
-this._layerTree=null;
-};
-
-
-WebInspector.LayerTreeModel.Events={
-LayerTreeChanged:Symbol("LayerTreeChanged"),
-LayerPainted:Symbol("LayerPainted")};
-
-
-WebInspector.LayerTreeModel.ScrollRectType={
-NonFastScrollable:{name:"NonFastScrollable",description:"Non fast scrollable"},
-TouchEventHandler:{name:"TouchEventHandler",description:"Touch event handler"},
-WheelEventHandler:{name:"WheelEventHandler",description:"Wheel event handler"},
-RepaintsOnScroll:{name:"RepaintsOnScroll",description:"Repaints on scroll"}};
-
-
-WebInspector.LayerTreeModel.prototype={
-disable:function()
-{
-if(!this._enabled)
-return;
-this._enabled=false;
-this._layerTree=null;
-this.target().layerTreeAgent().disable();
-},
-
-enable:function()
-{
-if(this._enabled)
-return;
-this._enabled=true;
-this._forceEnable();
-},
-
-_forceEnable:function()
-{
-this._layerTree=new WebInspector.AgentLayerTree(this.target());
-this._lastPaintRectByLayerId={};
-this.target().layerTreeAgent().enable();
-},
-
-
-
-
-setLayerTree:function(layerTree)
-{
-this.disable();
-this._layerTree=layerTree;
-this.dispatchEventToListeners(WebInspector.LayerTreeModel.Events.LayerTreeChanged);
-},
-
-
-
-
-layerTree:function()
-{
-return this._layerTree;
-},
-
-
-
-
-_layerTreeChanged:function(layers)
-{
-if(!this._enabled)
-return;
-var layerTree=this._layerTree;
-layerTree.setLayers(layers,onLayersSet.bind(this));
-
-
-
-
-function onLayersSet()
-{
-for(var layerId in this._lastPaintRectByLayerId){
-var lastPaintRect=this._lastPaintRectByLayerId[layerId];
-var layer=layerTree.layerById(layerId);
-if(layer)
-layer._lastPaintRect=lastPaintRect;
-}
-this._lastPaintRectByLayerId={};
-
-this.dispatchEventToListeners(WebInspector.LayerTreeModel.Events.LayerTreeChanged);
-}
-},
-
-
-
-
-
-_layerPainted:function(layerId,clipRect)
-{
-if(!this._enabled)
-return;
-var layerTree=this._layerTree;
-var layer=layerTree.layerById(layerId);
-if(!layer){
-this._lastPaintRectByLayerId[layerId]=clipRect;
-return;
-}
-layer._didPaint(clipRect);
-this.dispatchEventToListeners(WebInspector.LayerTreeModel.Events.LayerPainted,layer);
-},
-
-_onMainFrameNavigated:function()
-{
-if(this._enabled)
-this._forceEnable();
-},
-
-__proto__:WebInspector.SDKModel.prototype};
-
-
-
-
-
-
-WebInspector.LayerTreeBase=function(target)
-{
-this._target=target;
-this._domModel=target?WebInspector.DOMModel.fromTarget(target):null;
-this._layersById={};
-
-this._backendNodeIdToNode=new Map();
-this._reset();
-};
-
-WebInspector.LayerTreeBase.prototype={
-_reset:function()
-{
-this._root=null;
-this._contentRoot=null;
-this._layers=null;
-},
-
-
-
-
-target:function()
-{
-return this._target;
-},
-
-
-
-
-root:function()
-{
-return this._root;
-},
-
-
-
-
-contentRoot:function()
-{
-return this._contentRoot;
-},
-
-
-
-
-layers:function()
-{
-return this._layers;
-},
-
-
-
-
-
-
-forEachLayer:function(callback,root)
-{
-if(!root){
-root=this.root();
-if(!root)
-return false;
-}
-return callback(root)||root.children().some(this.forEachLayer.bind(this,callback));
-},
-
-
-
-
-
-layerById:function(id)
-{
-return this._layersById[id]||null;
-},
-
-
-
-
-
-_resolveBackendNodeIds:function(requestedNodeIds,callback)
-{
-if(!requestedNodeIds.size||!this._domModel){
-callback();
-return;
-}
-if(this._domModel)
-this._domModel.pushNodesByBackendIdsToFrontend(requestedNodeIds,populateBackendNodeMap.bind(this));
-
-
-
-
-
-function populateBackendNodeMap(nodesMap)
-{
-if(nodesMap){
-for(var nodeId of nodesMap.keysArray())
-this._backendNodeIdToNode.set(nodeId,nodesMap.get(nodeId)||null);
-}
-callback();
-}
-},
-
-
-
-
-setViewportSize:function(viewportSize)
-{
-this._viewportSize=viewportSize;
-},
-
-
-
-
-viewportSize:function()
-{
-return this._viewportSize;
-},
-
-
-
-
-
-_nodeForId:function(id)
-{
-return this._domModel?this._domModel.nodeForId(id):null;
-}};
-
-
-
-
-
-
-
-WebInspector.TracingLayerTree=function(target)
-{
-WebInspector.LayerTreeBase.call(this,target);
-
-this._tileById=new Map();
-};
-
-WebInspector.TracingLayerTree.prototype={
-
-
-
-
-
-setLayers:function(root,layers,callback)
-{
-var idsToResolve=new Set();
-if(root){
-
-
-this._extractNodeIdsToResolve(idsToResolve,{},root);
-}else{
-for(var i=0;i<layers.length;++i)
-this._extractNodeIdsToResolve(idsToResolve,{},layers[i]);
-}
-this._resolveBackendNodeIds(idsToResolve,onBackendNodeIdsResolved.bind(this));
-
-
-
-
-function onBackendNodeIdsResolved()
-{
-var oldLayersById=this._layersById;
-this._layersById={};
-this._contentRoot=null;
-if(root){
-this._root=this._innerSetLayers(oldLayersById,root);
-}else{
-this._layers=layers.map(this._innerSetLayers.bind(this,oldLayersById));
-this._root=this._contentRoot;
-for(var i=0;i<this._layers.length;++i){
-if(this._layers[i].id()!==this._contentRoot.id()){
-this._contentRoot.addChild(this._layers[i]);
-}
-}
-}
-callback();
-}
-},
-
-
-
-
-setTiles:function(tiles)
-{
-this._tileById=new Map();
-for(var tile of tiles)
-this._tileById.set(tile.id,tile);
-},
-
-
-
-
-
-tileById:function(id)
-{
-return this._tileById.get(id)||null;
-},
-
-
-
-
-
-
-_innerSetLayers:function(oldLayersById,payload)
-{
-var layer=oldLayersById[payload.layer_id];
-if(layer)
-layer._reset(payload);else
-
-layer=new WebInspector.TracingLayer(payload);
-this._layersById[payload.layer_id]=layer;
-if(payload.owner_node)
-layer._setNode(this._backendNodeIdToNode.get(payload.owner_node)||null);
-if(!this._contentRoot&&layer.drawsContent())
-this._contentRoot=layer;
-for(var i=0;payload.children&&i<payload.children.length;++i)
-layer.addChild(this._innerSetLayers(oldLayersById,payload.children[i]));
-return layer;
-},
-
-
-
-
-
-
-_extractNodeIdsToResolve:function(nodeIdsToResolve,seenNodeIds,payload)
-{
-var backendNodeId=payload.owner_node;
-if(backendNodeId&&!this._backendNodeIdToNode.has(backendNodeId))
-nodeIdsToResolve.add(backendNodeId);
-for(var i=0;payload.children&&i<payload.children.length;++i)
-this._extractNodeIdsToResolve(nodeIdsToResolve,seenNodeIds,payload.children[i]);
-},
-
-__proto__:WebInspector.LayerTreeBase.prototype};
-
-
-
-
-
-
-
-WebInspector.AgentLayerTree=function(target)
-{
-WebInspector.LayerTreeBase.call(this,target);
-};
-
-WebInspector.AgentLayerTree.prototype={
-
-
-
-
-setLayers:function(payload,callback)
-{
-if(!payload){
-onBackendNodeIdsResolved.call(this);
-return;
-}
-
-var idsToResolve=new Set();
-for(var i=0;i<payload.length;++i){
-var backendNodeId=payload[i].backendNodeId;
-if(!backendNodeId||this._backendNodeIdToNode.has(backendNodeId))
-continue;
-idsToResolve.add(backendNodeId);
-}
-this._resolveBackendNodeIds(idsToResolve,onBackendNodeIdsResolved.bind(this));
-
-
-
-
-function onBackendNodeIdsResolved()
-{
-this._innerSetLayers(payload);
-callback();
-}
-},
-
-
-
-
-_innerSetLayers:function(layers)
-{
-this._reset();
-
-if(!layers)
-return;
-var oldLayersById=this._layersById;
-this._layersById={};
-for(var i=0;i<layers.length;++i){
-var layerId=layers[i].layerId;
-var layer=oldLayersById[layerId];
-if(layer)
-layer._reset(layers[i]);else
-
-layer=new WebInspector.AgentLayer(this._target,layers[i]);
-this._layersById[layerId]=layer;
-var backendNodeId=layers[i].backendNodeId;
-if(backendNodeId)
-layer._setNode(this._backendNodeIdToNode.get(backendNodeId));
-if(!this._contentRoot&&layer.drawsContent())
-this._contentRoot=layer;
-var parentId=layer.parentId();
-if(parentId){
-var parent=this._layersById[parentId];
-if(!parent)
-console.assert(parent,"missing parent "+parentId+" for layer "+layerId);
-parent.addChild(layer);
-}else{
-if(this._root)
-console.assert(false,"Multiple root layers");
-this._root=layer;
-}
-}
-if(this._root)
-this._root._calculateQuad(new WebKitCSSMatrix());
-},
-
-__proto__:WebInspector.LayerTreeBase.prototype};
-
-
-
-
-
-WebInspector.Layer=function()
-{
-};
-
-WebInspector.Layer.prototype={
-
-
-
-id:function(){},
-
-
-
-
-parentId:function(){},
-
-
-
-
-parent:function(){},
-
-
-
-
-isRoot:function(){},
-
-
-
-
-children:function(){},
-
-
-
-
-addChild:function(child){},
-
-
-
-
-node:function(){},
-
-
-
-
-nodeForSelfOrAncestor:function(){},
-
-
-
-
-offsetX:function(){},
-
-
-
-
-offsetY:function(){},
-
-
-
-
-width:function(){},
-
-
-
-
-height:function(){},
-
-
-
-
-transform:function(){},
-
-
-
-
-quad:function(){},
-
-
-
-
-anchorPoint:function(){},
-
-
-
-
-invisible:function(){},
-
-
-
-
-paintCount:function(){},
-
-
-
-
-lastPaintRect:function(){},
-
-
-
-
-scrollRects:function(){},
-
-
-
-
-gpuMemoryUsage:function(){},
-
-
-
-
-requestCompositingReasons:function(callback){},
-
-
-
-
-drawsContent:function(){}};
-
-
-
-
-
-
-
-
-WebInspector.AgentLayer=function(target,layerPayload)
-{
-this._target=target;
-this._reset(layerPayload);
-};
-
-WebInspector.AgentLayer.prototype={
-
-
-
-
-id:function()
-{
-return this._layerPayload.layerId;
-},
-
-
-
-
-
-parentId:function()
-{
-return this._layerPayload.parentLayerId;
-},
-
-
-
-
-
-parent:function()
-{
-return this._parent;
-},
-
-
-
-
-
-isRoot:function()
-{
-return!this.parentId();
-},
-
-
-
-
-
-children:function()
-{
-return this._children;
-},
-
-
-
-
-
-addChild:function(child)
-{
-if(child._parent)
-console.assert(false,"Child already has a parent");
-this._children.push(child);
-child._parent=this;
-},
-
-
-
-
-_setNode:function(node)
-{
-this._node=node;
-},
-
-
-
-
-
-node:function()
-{
-return this._node;
-},
-
-
-
-
-
-nodeForSelfOrAncestor:function()
-{
-for(var layer=this;layer;layer=layer._parent){
-if(layer._node)
-return layer._node;
-}
-return null;
-},
-
-
-
-
-
-offsetX:function()
-{
-return this._layerPayload.offsetX;
-},
-
-
-
-
-
-offsetY:function()
-{
-return this._layerPayload.offsetY;
-},
-
-
-
-
-
-width:function()
-{
-return this._layerPayload.width;
-},
-
-
-
-
-
-height:function()
-{
-return this._layerPayload.height;
-},
-
-
-
-
-
-transform:function()
-{
-return this._layerPayload.transform;
-},
-
-
-
-
-
-quad:function()
-{
-return this._quad;
-},
-
-
-
-
-
-anchorPoint:function()
-{
-return[
-this._layerPayload.anchorX||0,
-this._layerPayload.anchorY||0,
-this._layerPayload.anchorZ||0];
-
-},
-
-
-
-
-
-invisible:function()
-{
-return this._layerPayload.invisible;
-},
-
-
-
-
-
-paintCount:function()
-{
-return this._paintCount||this._layerPayload.paintCount;
-},
-
-
-
-
-
-lastPaintRect:function()
-{
-return this._lastPaintRect;
-},
-
-
-
-
-
-scrollRects:function()
-{
-return this._scrollRects;
-},
-
-
-
-
-
-requestCompositingReasons:function(callback)
-{
-if(!this._target){
-callback([]);
-return;
-}
-
-var wrappedCallback=InspectorBackend.wrapClientCallback(callback,"LayerTreeAgent.reasonsForCompositingLayer(): ",undefined,[]);
-this._target.layerTreeAgent().compositingReasons(this.id(),wrappedCallback);
-},
-
-
-
-
-
-drawsContent:function()
-{
-return this._layerPayload.drawsContent;
-},
-
-
-
-
-
-gpuMemoryUsage:function()
-{
-
-
-
-var bytesPerPixel=4;
-return this.drawsContent()?this.width()*this.height()*bytesPerPixel:0;
-},
-
-
-
-
-requestSnapshot:function(callback)
-{
-if(!this._target){
-callback();
-return;
-}
-
-var wrappedCallback=InspectorBackend.wrapClientCallback(callback,"LayerTreeAgent.makeSnapshot(): ",WebInspector.PaintProfilerSnapshot.bind(null,this._target));
-this._target.layerTreeAgent().makeSnapshot(this.id(),wrappedCallback);
-},
-
-
-
-
-_didPaint:function(rect)
-{
-this._lastPaintRect=rect;
-this._paintCount=this.paintCount()+1;
-this._image=null;
-},
-
-
-
-
-_reset:function(layerPayload)
-{
-
-this._node=null;
-this._children=[];
-this._parent=null;
-this._paintCount=0;
-this._layerPayload=layerPayload;
-this._image=null;
-this._scrollRects=this._layerPayload.scrollRects||[];
-},
-
-
-
-
-
-_matrixFromArray:function(a)
-{
-function toFixed9(x){return x.toFixed(9);}
-return new WebKitCSSMatrix("matrix3d("+a.map(toFixed9).join(",")+")");
-},
-
-
-
-
-
-_calculateTransformToViewport:function(parentTransform)
-{
-var offsetMatrix=new WebKitCSSMatrix().translate(this._layerPayload.offsetX,this._layerPayload.offsetY);
-var matrix=offsetMatrix;
-
-if(this._layerPayload.transform){
-var transformMatrix=this._matrixFromArray(this._layerPayload.transform);
-var anchorVector=new WebInspector.Geometry.Vector(this._layerPayload.width*this.anchorPoint()[0],this._layerPayload.height*this.anchorPoint()[1],this.anchorPoint()[2]);
-var anchorPoint=WebInspector.Geometry.multiplyVectorByMatrixAndNormalize(anchorVector,matrix);
-var anchorMatrix=new WebKitCSSMatrix().translate(-anchorPoint.x,-anchorPoint.y,-anchorPoint.z);
-matrix=anchorMatrix.inverse().multiply(transformMatrix.multiply(anchorMatrix.multiply(matrix)));
-}
-
-matrix=parentTransform.multiply(matrix);
-return matrix;
-},
-
-
-
-
-
-
-_createVertexArrayForRect:function(width,height)
-{
-return[0,0,0,width,0,0,width,height,0,0,height,0];
-},
-
-
-
-
-_calculateQuad:function(parentTransform)
-{
-var matrix=this._calculateTransformToViewport(parentTransform);
-this._quad=[];
-var vertices=this._createVertexArrayForRect(this._layerPayload.width,this._layerPayload.height);
-for(var i=0;i<4;++i){
-var point=WebInspector.Geometry.multiplyVectorByMatrixAndNormalize(new WebInspector.Geometry.Vector(vertices[i*3],vertices[i*3+1],vertices[i*3+2]),matrix);
-this._quad.push(point.x,point.y);
-}
-
-function calculateQuadForLayer(layer)
-{
-layer._calculateQuad(matrix);
-}
-
-this._children.forEach(calculateQuadForLayer);
-}};
-
-
-
-
-
-
-
-WebInspector.TracingLayer=function(payload)
-{
-this._reset(payload);
-};
-
-WebInspector.TracingLayer.prototype={
-
-
-
-_reset:function(payload)
-{
-
-this._node=null;
-this._layerId=String(payload.layer_id);
-this._offsetX=payload.position[0];
-this._offsetY=payload.position[1];
-this._width=payload.bounds.width;
-this._height=payload.bounds.height;
-this._children=[];
-this._parentLayerId=null;
-this._parent=null;
-this._quad=payload.layer_quad||[];
-this._createScrollRects(payload);
-this._compositingReasons=payload.compositing_reasons||[];
-this._drawsContent=!!payload.draws_content;
-this._gpuMemoryUsage=payload.gpu_memory_usage;
-},
-
-
-
-
-
-id:function()
-{
-return this._layerId;
-},
-
-
-
-
-
-parentId:function()
-{
-return this._parentLayerId;
-},
-
-
-
-
-
-parent:function()
-{
-return this._parent;
-},
-
-
-
-
-
-isRoot:function()
-{
-return!this.parentId();
-},
-
-
-
-
-
-children:function()
-{
-return this._children;
-},
-
-
-
-
-
-addChild:function(child)
-{
-if(child._parent)
-console.assert(false,"Child already has a parent");
-this._children.push(child);
-child._parent=this;
-child._parentLayerId=this._layerId;
-},
-
-
-
-
-
-_setNode:function(node)
-{
-this._node=node;
-},
-
-
-
-
-
-node:function()
-{
-return this._node;
-},
-
-
-
-
-
-nodeForSelfOrAncestor:function()
-{
-for(var layer=this;layer;layer=layer._parent){
-if(layer._node)
-return layer._node;
-}
-return null;
-},
-
-
-
-
-
-offsetX:function()
-{
-return this._offsetX;
-},
-
-
-
-
-
-offsetY:function()
-{
-return this._offsetY;
-},
-
-
-
-
-
-width:function()
-{
-return this._width;
-},
-
-
-
-
-
-height:function()
-{
-return this._height;
-},
-
-
-
-
-
-transform:function()
-{
-return null;
-},
-
-
-
-
-
-quad:function()
-{
-return this._quad;
-},
-
-
-
-
-
-anchorPoint:function()
-{
-return[0.5,0.5,0];
-},
-
-
-
-
-
-invisible:function()
-{
-return false;
-},
-
-
-
-
-
-paintCount:function()
-{
-return 0;
-},
-
-
-
-
-
-lastPaintRect:function()
-{
-return null;
-},
-
-
-
-
-
-scrollRects:function()
-{
-return this._scrollRects;
-},
-
-
-
-
-
-gpuMemoryUsage:function()
-{
-return this._gpuMemoryUsage;
-},
-
-
-
-
-
-
-_scrollRectsFromParams:function(params,type)
-{
-return{rect:{x:params[0],y:params[1],width:params[2],height:params[3]},type:type};
-},
-
-
-
-
-_createScrollRects:function(payload)
-{
-this._scrollRects=[];
-if(payload.non_fast_scrollable_region)
-this._scrollRects.push(this._scrollRectsFromParams(payload.non_fast_scrollable_region,WebInspector.LayerTreeModel.ScrollRectType.NonFastScrollable.name));
-if(payload.touch_event_handler_region)
-this._scrollRects.push(this._scrollRectsFromParams(payload.touch_event_handler_region,WebInspector.LayerTreeModel.ScrollRectType.TouchEventHandler.name));
-if(payload.wheel_event_handler_region)
-this._scrollRects.push(this._scrollRectsFromParams(payload.wheel_event_handler_region,WebInspector.LayerTreeModel.ScrollRectType.WheelEventHandler.name));
-if(payload.scroll_event_handler_region)
-this._scrollRects.push(this._scrollRectsFromParams(payload.scroll_event_handler_region,WebInspector.LayerTreeModel.ScrollRectType.RepaintsOnScroll.name));
-},
-
-
-
-
-
-requestCompositingReasons:function(callback)
-{
-callback(this._compositingReasons);
-},
-
-
-
-
-
-drawsContent:function()
-{
-return this._drawsContent;
-}};
-
-
-
-
-
-
-WebInspector.DeferredLayerTree=function(target)
-{
-this._target=target;
-};
-
-WebInspector.DeferredLayerTree.prototype={
-
-
-
-resolve:function(callback){},
-
-
-
-
-target:function()
-{
-return this._target;
-}};
-
-
-
-
-
-
-
-WebInspector.LayerTreeDispatcher=function(layerTreeModel)
-{
-this._layerTreeModel=layerTreeModel;
-};
-
-WebInspector.LayerTreeDispatcher.prototype={
-
-
-
-
-layerTreeDidChange:function(layers)
-{
-this._layerTreeModel._layerTreeChanged(layers||null);
-},
-
-
-
-
-
-
-layerPainted:function(layerId,clipRect)
-{
-this._layerTreeModel._layerPainted(layerId,clipRect);
-}};
-
-
-
-
-
-
-WebInspector.LayerTreeModel.fromTarget=function(target)
-{
-if(!target.hasDOMCapability())
-return null;
-
-var model=target.model(WebInspector.LayerTreeModel);
-if(!model)
-model=new WebInspector.LayerTreeModel(target);
-return model;
-};
-
-},{}],227:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-WebInspector.TimelineFrameModel=function(categoryMapper)
-{
-this._categoryMapper=categoryMapper;
-this.reset();
-};
-
-WebInspector.TimelineFrameModel._mainFrameMarkers=[
-WebInspector.TimelineModel.RecordType.ScheduleStyleRecalculation,
-WebInspector.TimelineModel.RecordType.InvalidateLayout,
-WebInspector.TimelineModel.RecordType.BeginMainThreadFrame,
-WebInspector.TimelineModel.RecordType.ScrollLayer];
-
-
-WebInspector.TimelineFrameModel.prototype={
-
-
-
-frames:function()
-{
-return this._frames;
-},
-
-
-
-
-
-
-filteredFrames:function(startTime,endTime)
-{
-
-
-
-
-
-function compareStartTime(value,object)
-{
-return value-object.startTime;
-}
-
-
-
-
-
-function compareEndTime(value,object)
-{
-return value-object.endTime;
-}
-var frames=this._frames;
-var firstFrame=frames.lowerBound(startTime,compareEndTime);
-var lastFrame=frames.lowerBound(endTime,compareStartTime);
-return frames.slice(firstFrame,lastFrame);
-},
-
-
-
-
-
-hasRasterTile:function(rasterTask)
-{
-var data=rasterTask.args["tileData"];
-if(!data)
-return false;
-var frameId=data["sourceFrameNumber"];
-var frame=frameId&&this._frameById[frameId];
-if(!frame||!frame.layerTree)
-return false;
-return true;
-},
-
-
-
-
-
-requestRasterTile:function(rasterTask,callback)
-{
-var target=this._target;
-if(!target){
-callback(null,null);
-return;
-}
-var data=rasterTask.args["tileData"];
-var frameId=data["sourceFrameNumber"];
-var frame=frameId&&this._frameById[frameId];
-if(!frame||!frame.layerTree){
-callback(null,null);
-return;
-}
-
-var tileId=data["tileId"]&&data["tileId"]["id_ref"];
-
-var fragments=[];
-
-var tile=null;
-var x0=Infinity;
-var y0=Infinity;
-
-frame.layerTree.resolve(layerTreeResolved);
-
-
-
-function layerTreeResolved(layerTree)
-{
-tile=tileId&&layerTree.tileById("cc::Tile/"+tileId);
-if(!tile){
-console.error("Tile "+tileId+" missing in frame "+frameId);
-callback(null,null);
-return;
-}
-var fetchPictureFragmentsBarrier=new CallbackBarrier();
-for(var paint of frame.paints){
-if(tile.layer_id===paint.layerId())
-paint.loadPicture(fetchPictureFragmentsBarrier.createCallback(pictureLoaded));
-}
-fetchPictureFragmentsBarrier.callWhenDone(allPicturesLoaded);
-}
-
-
-
-
-
-
-
-
-function segmentsOverlap(a1,a2,b1,b2)
-{
-console.assert(a1<=a2&&b1<=b2,"segments should be specified as ordered pairs");
-return a2>b1&&a1<b2;
-}
-
-
-
-
-
-function rectsOverlap(a,b)
-{
-return segmentsOverlap(a[0],a[0]+a[2],b[0],b[0]+b[2])&&segmentsOverlap(a[1],a[1]+a[3],b[1],b[1]+b[3]);
-}
-
-
-
-
-
-function pictureLoaded(rect,picture)
-{
-if(!rect||!picture)
-return;
-if(!rectsOverlap(rect,tile.content_rect))
-return;
-var x=rect[0];
-var y=rect[1];
-x0=Math.min(x0,x);
-y0=Math.min(y0,y);
-fragments.push({x:x,y:y,picture:picture});
-}
-
-function allPicturesLoaded()
-{
-if(!fragments.length){
-callback(null,null);
-return;
-}
-var rectArray=tile.content_rect;
-
-var rect={x:rectArray[0]-x0,y:rectArray[1]-y0,width:rectArray[2],height:rectArray[3]};
-WebInspector.PaintProfilerSnapshot.loadFromFragments(target,fragments,callback.bind(null,rect));
-}
-},
-
-reset:function()
-{
-this._minimumRecordTime=Infinity;
-this._frames=[];
-this._frameById={};
-this._lastFrame=null;
-this._lastLayerTree=null;
-this._mainFrameCommitted=false;
-this._mainFrameRequested=false;
-this._framePendingCommit=null;
-this._lastBeginFrame=null;
-this._lastNeedsBeginFrame=null;
-this._framePendingActivation=null;
-this._lastTaskBeginTime=null;
-this._target=null;
-this._sessionId=null;
-this._currentTaskTimeByCategory={};
-},
-
-
-
-
-handleBeginFrame:function(startTime)
-{
-if(!this._lastFrame)
-this._startFrame(startTime);
-this._lastBeginFrame=startTime;
-},
-
-
-
-
-handleDrawFrame:function(startTime)
-{
-if(!this._lastFrame){
-this._startFrame(startTime);
-return;
-}
-
-
-
-if(this._mainFrameCommitted||!this._mainFrameRequested){
-if(this._lastNeedsBeginFrame){
-var idleTimeEnd=this._framePendingActivation?this._framePendingActivation.triggerTime:this._lastBeginFrame||this._lastNeedsBeginFrame;
-if(idleTimeEnd>this._lastFrame.startTime){
-this._lastFrame.idle=true;
-this._startFrame(idleTimeEnd);
-if(this._framePendingActivation)
-this._commitPendingFrame();
-this._lastBeginFrame=null;
-}
-this._lastNeedsBeginFrame=null;
-}
-this._startFrame(startTime);
-}
-this._mainFrameCommitted=false;
-},
-
-handleActivateLayerTree:function()
-{
-if(!this._lastFrame)
-return;
-if(this._framePendingActivation&&!this._lastNeedsBeginFrame)
-this._commitPendingFrame();
-},
-
-handleRequestMainThreadFrame:function()
-{
-if(!this._lastFrame)
-return;
-this._mainFrameRequested=true;
-},
-
-handleCompositeLayers:function()
-{
-if(!this._framePendingCommit)
-return;
-this._framePendingActivation=this._framePendingCommit;
-this._framePendingCommit=null;
-this._mainFrameRequested=false;
-this._mainFrameCommitted=true;
-},
-
-
-
-
-handleLayerTreeSnapshot:function(layerTree)
-{
-this._lastLayerTree=layerTree;
-},
-
-
-
-
-
-handleNeedFrameChanged:function(startTime,needsBeginFrame)
-{
-if(needsBeginFrame)
-this._lastNeedsBeginFrame=startTime;
-},
-
-
-
-
-_startFrame:function(startTime)
-{
-if(this._lastFrame)
-this._flushFrame(this._lastFrame,startTime);
-this._lastFrame=new WebInspector.TimelineFrame(startTime,startTime-this._minimumRecordTime);
-},
-
-
-
-
-
-_flushFrame:function(frame,endTime)
-{
-frame._setLayerTree(this._lastLayerTree);
-frame._setEndTime(endTime);
-if(this._frames.length&&(frame.startTime!==this._frames.peekLast().endTime||frame.startTime>frame.endTime))
-console.assert(false,`Inconsistent frame time for frame ${this._frames.length} (${frame.startTime} - ${frame.endTime})`);
-this._frames.push(frame);
-if(typeof frame._mainFrameId==="number")
-this._frameById[frame._mainFrameId]=frame;
-},
-
-_commitPendingFrame:function()
-{
-this._lastFrame._addTimeForCategories(this._framePendingActivation.timeByCategory);
-this._lastFrame.paints=this._framePendingActivation.paints;
-this._lastFrame._mainFrameId=this._framePendingActivation.mainFrameId;
-this._framePendingActivation=null;
-},
-
-
-
-
-
-
-_findRecordRecursively:function(types,record)
-{
-if(types.indexOf(record.type())>=0)
-return record;
-if(!record.children())
-return null;
-for(var i=0;i<record.children().length;++i){
-var result=this._findRecordRecursively(types,record.children()[i]);
-if(result)
-return result;
-}
-return null;
-},
-
-
-
-
-
-
-addTraceEvents:function(target,events,sessionId)
-{
-this._target=target;
-this._sessionId=sessionId;
-if(!events.length)
-return;
-if(events[0].startTime<this._minimumRecordTime)
-this._minimumRecordTime=events[0].startTime;
-for(var i=0;i<events.length;++i)
-this._addTraceEvent(events[i]);
-},
-
-
-
-
-_addTraceEvent:function(event)
-{
-var eventNames=WebInspector.TimelineModel.RecordType;
-
-if(event.name===eventNames.SetLayerTreeId){
-var sessionId=event.args["sessionId"]||event.args["data"]["sessionId"];
-if(this._sessionId===sessionId)
-this._layerTreeId=event.args["layerTreeId"]||event.args["data"]["layerTreeId"];
-}else if(event.name===eventNames.TracingStartedInPage){
-this._mainThread=event.thread;
-}else if(event.phase===WebInspector.TracingModel.Phase.SnapshotObject&&event.name===eventNames.LayerTreeHostImplSnapshot&&parseInt(event.id,0)===this._layerTreeId){
-var snapshot=event;
-this.handleLayerTreeSnapshot(new WebInspector.DeferredTracingLayerTree(snapshot,this._target));
-}else{
-this._processCompositorEvents(event);
-if(event.thread===this._mainThread)
-this._addMainThreadTraceEvent(event);else
-if(this._lastFrame&&event.selfTime&&!WebInspector.TracingModel.isTopLevelEvent(event))
-this._lastFrame._addTimeForCategory(this._categoryMapper(event),event.selfTime);
-}
-},
-
-
-
-
-_processCompositorEvents:function(event)
-{
-var eventNames=WebInspector.TimelineModel.RecordType;
-
-if(event.args["layerTreeId"]!==this._layerTreeId)
-return;
-
-var timestamp=event.startTime;
-if(event.name===eventNames.BeginFrame)
-this.handleBeginFrame(timestamp);else
-if(event.name===eventNames.DrawFrame)
-this.handleDrawFrame(timestamp);else
-if(event.name===eventNames.ActivateLayerTree)
-this.handleActivateLayerTree();else
-if(event.name===eventNames.RequestMainThreadFrame)
-this.handleRequestMainThreadFrame();else
-if(event.name===eventNames.NeedsBeginFrameChanged)
-this.handleNeedFrameChanged(timestamp,event.args["data"]&&event.args["data"]["needsBeginFrame"]);
-},
-
-
-
-
-_addMainThreadTraceEvent:function(event)
-{
-var eventNames=WebInspector.TimelineModel.RecordType;
-var timestamp=event.startTime;
-var selfTime=event.selfTime||0;
-
-if(WebInspector.TracingModel.isTopLevelEvent(event)){
-this._currentTaskTimeByCategory={};
-this._lastTaskBeginTime=event.startTime;
-}
-if(!this._framePendingCommit&&WebInspector.TimelineFrameModel._mainFrameMarkers.indexOf(event.name)>=0)
-this._framePendingCommit=new WebInspector.PendingFrame(this._lastTaskBeginTime||event.startTime,this._currentTaskTimeByCategory);
-if(!this._framePendingCommit){
-this._addTimeForCategory(this._currentTaskTimeByCategory,event);
-return;
-}
-this._addTimeForCategory(this._framePendingCommit.timeByCategory,event);
-
-if(event.name===eventNames.BeginMainThreadFrame&&event.args["data"]&&event.args["data"]["frameId"])
-this._framePendingCommit.mainFrameId=event.args["data"]["frameId"];
-if(event.name===eventNames.Paint&&event.args["data"]["layerId"]&&event.picture&&this._target)
-this._framePendingCommit.paints.push(new WebInspector.LayerPaintEvent(event,this._target));
-if(event.name===eventNames.CompositeLayers&&event.args["layerTreeId"]===this._layerTreeId)
-this.handleCompositeLayers();
-},
-
-
-
-
-
-_addTimeForCategory:function(timeByCategory,event)
-{
-if(!event.selfTime)
-return;
-var categoryName=this._categoryMapper(event);
-timeByCategory[categoryName]=(timeByCategory[categoryName]||0)+event.selfTime;
-}};
-
-
-
-
-
-
-
-
-WebInspector.DeferredTracingLayerTree=function(snapshot,target)
-{
-WebInspector.DeferredLayerTree.call(this,target);
-this._snapshot=snapshot;
-};
-
-WebInspector.DeferredTracingLayerTree.prototype={
-
-
-
-
-resolve:function(callback)
-{
-this._snapshot.requestObject(onGotObject.bind(this));
-
-
-
-
-function onGotObject(result)
-{
-if(!result)
-return;
-var viewport=result["device_viewport_size"];
-var tiles=result["active_tiles"];
-var rootLayer=result["active_tree"]["root_layer"];
-var layers=result["active_tree"]["layers"];
-var layerTree=new WebInspector.TracingLayerTree(this._target);
-layerTree.setViewportSize(viewport);
-layerTree.setTiles(tiles);
-layerTree.setLayers(rootLayer,layers,callback.bind(null,layerTree));
-}
-},
-
-__proto__:WebInspector.DeferredLayerTree.prototype};
-
-
-
-
-
-
-
-
-WebInspector.TimelineFrame=function(startTime,startTimeOffset)
-{
-this.startTime=startTime;
-this.startTimeOffset=startTimeOffset;
-this.endTime=this.startTime;
-this.duration=0;
-this.timeByCategory={};
-this.cpuTime=0;
-this.idle=false;
-
-this.layerTree=null;
-
-this.paints=[];
-
-this._mainFrameId=undefined;
-};
-
-WebInspector.TimelineFrame.prototype={
-
-
-
-hasWarnings:function()
-{
-var longFrameDurationThresholdMs=22;
-return!this.idle&&this.duration>longFrameDurationThresholdMs;
-},
-
-
-
-
-_setEndTime:function(endTime)
-{
-this.endTime=endTime;
-this.duration=this.endTime-this.startTime;
-},
-
-
-
-
-_setLayerTree:function(layerTree)
-{
-this.layerTree=layerTree;
-},
-
-
-
-
-_addTimeForCategories:function(timeByCategory)
-{
-for(var category in timeByCategory)
-this._addTimeForCategory(category,timeByCategory[category]);
-},
-
-
-
-
-
-_addTimeForCategory:function(category,time)
-{
-this.timeByCategory[category]=(this.timeByCategory[category]||0)+time;
-this.cpuTime+=time;
-}};
-
-
-
-
-
-
-
-WebInspector.LayerPaintEvent=function(event,target)
-{
-this._event=event;
-this._target=target;
-};
-
-WebInspector.LayerPaintEvent.prototype={
-
-
-
-layerId:function()
-{
-return this._event.args["data"]["layerId"];
-},
-
-
-
-
-event:function()
-{
-return this._event;
-},
-
-
-
-
-loadPicture:function(callback)
-{
-this._event.picture.requestObject(onGotObject);
-
-
-
-function onGotObject(result)
-{
-if(!result||!result["skp64"]){
-callback(null,null);
-return;
-}
-var rect=result["params"]&&result["params"]["layer_rect"];
-callback(rect,result["skp64"]);
-}
-},
-
-
-
-
-loadSnapshot:function(callback)
-{
-this.loadPicture(onGotPicture.bind(this));
-
-
-
-
-
-function onGotPicture(rect,picture)
-{
-if(!rect||!picture||!this._target){
-callback(null,null);
-return;
-}
-WebInspector.PaintProfilerSnapshot.load(this._target,picture,callback.bind(null,rect));
-}
-}};
-
-
-
-
-
-
-
-WebInspector.PendingFrame=function(triggerTime,timeByCategory)
-{
-
-this.timeByCategory=timeByCategory;
-
-this.paints=[];
-
-this.mainFrameId=undefined;
-this.triggerTime=triggerTime;
-};
-
-},{}],228:[function(require,module,exports){
-
-
-
-
-
-
-
-WebInspector.TimelineIRModel=function()
-{
-this.reset();
-};
-
-
-
-
-WebInspector.TimelineIRModel.Phases={
-Idle:"Idle",
-Response:"Response",
-Scroll:"Scroll",
-Fling:"Fling",
-Drag:"Drag",
-Animation:"Animation",
-Uncategorized:"Uncategorized"};
-
-
-
-
-
-WebInspector.TimelineIRModel.InputEvents={
-Char:"Char",
-Click:"GestureClick",
-ContextMenu:"ContextMenu",
-FlingCancel:"GestureFlingCancel",
-FlingStart:"GestureFlingStart",
-ImplSideFling:WebInspector.TimelineModel.RecordType.ImplSideFling,
-KeyDown:"KeyDown",
-KeyDownRaw:"RawKeyDown",
-KeyUp:"KeyUp",
-LatencyScrollUpdate:"ScrollUpdate",
-MouseDown:"MouseDown",
-MouseMove:"MouseMove",
-MouseUp:"MouseUp",
-MouseWheel:"MouseWheel",
-PinchBegin:"GesturePinchBegin",
-PinchEnd:"GesturePinchEnd",
-PinchUpdate:"GesturePinchUpdate",
-ScrollBegin:"GestureScrollBegin",
-ScrollEnd:"GestureScrollEnd",
-ScrollUpdate:"GestureScrollUpdate",
-ScrollUpdateRenderer:"ScrollUpdate",
-ShowPress:"GestureShowPress",
-Tap:"GestureTap",
-TapCancel:"GestureTapCancel",
-TapDown:"GestureTapDown",
-TouchCancel:"TouchCancel",
-TouchEnd:"TouchEnd",
-TouchMove:"TouchMove",
-TouchStart:"TouchStart"};
-
-
-WebInspector.TimelineIRModel._mergeThresholdsMs={
-animation:1,
-mouse:40};
-
-
-WebInspector.TimelineIRModel._eventIRPhase=Symbol("eventIRPhase");
-
-
-
-
-
-WebInspector.TimelineIRModel.phaseForEvent=function(event)
-{
-return event[WebInspector.TimelineIRModel._eventIRPhase];
-};
-
-WebInspector.TimelineIRModel.prototype={
-
-
-
-
-populate:function(inputLatencies,animations)
-{
-var eventTypes=WebInspector.TimelineIRModel.InputEvents;
-var phases=WebInspector.TimelineIRModel.Phases;
-
-this.reset();
-if(!inputLatencies)
-return;
-this._processInputLatencies(inputLatencies);
-if(animations)
-this._processAnimations(animations);
-var range=new WebInspector.SegmentedRange();
-range.appendRange(this._drags);
-range.appendRange(this._cssAnimations);
-range.appendRange(this._scrolls);
-range.appendRange(this._responses);
-this._segments=range.segments();
-},
-
-
-
-
-_processInputLatencies:function(events)
-{
-var eventTypes=WebInspector.TimelineIRModel.InputEvents;
-var phases=WebInspector.TimelineIRModel.Phases;
-var thresholdsMs=WebInspector.TimelineIRModel._mergeThresholdsMs;
-
-var scrollStart;
-var flingStart;
-var touchStart;
-var firstTouchMove;
-var mouseWheel;
-var mouseDown;
-var mouseMove;
-
-for(var i=0;i<events.length;++i){
-var event=events[i];
-if(i>0&&events[i].startTime<events[i-1].startTime)
-console.assert(false,"Unordered input events");
-var type=this._inputEventType(event.name);
-switch(type){
-
-case eventTypes.ScrollBegin:
-this._scrolls.append(this._segmentForEvent(event,phases.Scroll));
-scrollStart=event;
-break;
-
-case eventTypes.ScrollEnd:
-if(scrollStart)
-this._scrolls.append(this._segmentForEventRange(scrollStart,event,phases.Scroll));else
-
-this._scrolls.append(this._segmentForEvent(event,phases.Scroll));
-scrollStart=null;
-break;
-
-case eventTypes.ScrollUpdate:
-touchStart=null;
-this._scrolls.append(this._segmentForEvent(event,phases.Scroll));
-break;
-
-case eventTypes.FlingStart:
-if(flingStart){
-WebInspector.console.error(WebInspector.UIString("Two flings at the same time? %s vs %s",flingStart.startTime,event.startTime));
-break;
-}
-flingStart=event;
-break;
-
-case eventTypes.FlingCancel:
-
-if(!flingStart)
-break;
-this._scrolls.append(this._segmentForEventRange(flingStart,event,phases.Fling));
-flingStart=null;
-break;
-
-case eventTypes.ImplSideFling:
-this._scrolls.append(this._segmentForEvent(event,phases.Fling));
-break;
-
-case eventTypes.ShowPress:
-case eventTypes.Tap:
-case eventTypes.KeyDown:
-case eventTypes.KeyDownRaw:
-case eventTypes.KeyUp:
-case eventTypes.Char:
-case eventTypes.Click:
-case eventTypes.ContextMenu:
-this._responses.append(this._segmentForEvent(event,phases.Response));
-break;
-
-case eventTypes.TouchStart:
-
-
-if(touchStart){
-WebInspector.console.error(WebInspector.UIString("Two touches at the same time? %s vs %s",touchStart.startTime,event.startTime));
-break;
-}
-touchStart=event;
-event.steps[0][WebInspector.TimelineIRModel._eventIRPhase]=phases.Response;
-firstTouchMove=null;
-break;
-
-case eventTypes.TouchCancel:
-touchStart=null;
-break;
-
-case eventTypes.TouchMove:
-if(firstTouchMove){
-this._drags.append(this._segmentForEvent(event,phases.Drag));
-}else if(touchStart){
-firstTouchMove=event;
-this._responses.append(this._segmentForEventRange(touchStart,event,phases.Response));
-}
-break;
-
-case eventTypes.TouchEnd:
-touchStart=null;
-break;
-
-case eventTypes.MouseDown:
-mouseDown=event;
-mouseMove=null;
-break;
-
-case eventTypes.MouseMove:
-if(mouseDown&&!mouseMove&&mouseDown.startTime+thresholdsMs.mouse>event.startTime){
-this._responses.append(this._segmentForEvent(mouseDown,phases.Response));
-this._responses.append(this._segmentForEvent(event,phases.Response));
-}else if(mouseDown){
-this._drags.append(this._segmentForEvent(event,phases.Drag));
-}
-mouseMove=event;
-break;
-
-case eventTypes.MouseUp:
-this._responses.append(this._segmentForEvent(event,phases.Response));
-mouseDown=null;
-break;
-
-case eventTypes.MouseWheel:
-
-if(mouseWheel&&canMerge(thresholdsMs.mouse,mouseWheel,event))
-this._scrolls.append(this._segmentForEventRange(mouseWheel,event,phases.Scroll));else
-
-this._scrolls.append(this._segmentForEvent(event,phases.Scroll));
-mouseWheel=event;
-break;}
-
-}
-
-
-
-
-
-
-
-function canMerge(threshold,first,second)
-{
-return first.endTime<second.startTime&&second.startTime<first.endTime+threshold;
-}
-},
-
-
-
-
-_processAnimations:function(events)
-{
-for(var i=0;i<events.length;++i)
-this._cssAnimations.append(this._segmentForEvent(events[i],WebInspector.TimelineIRModel.Phases.Animation));
-},
-
-
-
-
-
-
-_segmentForEvent:function(event,phase)
-{
-this._setPhaseForEvent(event,phase);
-return new WebInspector.Segment(event.startTime,event.endTime,phase);
-},
-
-
-
-
-
-
-
-_segmentForEventRange:function(startEvent,endEvent,phase)
-{
-this._setPhaseForEvent(startEvent,phase);
-this._setPhaseForEvent(endEvent,phase);
-return new WebInspector.Segment(startEvent.startTime,endEvent.endTime,phase);
-},
-
-
-
-
-
-_setPhaseForEvent:function(asyncEvent,phase)
-{
-asyncEvent.steps[0][WebInspector.TimelineIRModel._eventIRPhase]=phase;
-},
-
-
-
-
-interactionRecords:function()
-{
-return this._segments;
-},
-
-reset:function()
-{
-var thresholdsMs=WebInspector.TimelineIRModel._mergeThresholdsMs;
-
-this._segments=[];
-this._drags=new WebInspector.SegmentedRange(merge.bind(null,thresholdsMs.mouse));
-this._cssAnimations=new WebInspector.SegmentedRange(merge.bind(null,thresholdsMs.animation));
-this._responses=new WebInspector.SegmentedRange(merge.bind(null,0));
-this._scrolls=new WebInspector.SegmentedRange(merge.bind(null,thresholdsMs.animation));
-
-
-
-
-
-
-function merge(threshold,first,second)
-{
-return first.end+threshold>=second.begin&&first.data===second.data?first:null;
-}
-},
-
-
-
-
-
-_inputEventType:function(eventName)
-{
-var prefix="InputLatency::";
-if(!eventName.startsWith(prefix)){
-if(eventName===WebInspector.TimelineIRModel.InputEvents.ImplSideFling)
-return eventName;
-console.error("Unrecognized input latency event: "+eventName);
-return null;
-}
-return eventName.substr(prefix.length);
-}};
-
-
-
-},{}],229:[function(require,module,exports){
-
-
-
-
-
-WebInspector.TimelineJSProfileProcessor={};
-
-
-
-
-
-
-WebInspector.TimelineJSProfileProcessor.generateTracingEventsFromCpuProfile=function(jsProfileModel,thread)
-{
-var idleNode=jsProfileModel.idleNode;
-var programNode=jsProfileModel.programNode;
-var gcNode=jsProfileModel.gcNode;
-var samples=jsProfileModel.samples;
-var timestamps=jsProfileModel.timestamps;
-var jsEvents=[];
-
-var nodeToStackMap=new Map();
-nodeToStackMap.set(programNode,[]);
-for(var i=0;i<samples.length;++i){
-var node=jsProfileModel.nodeByIndex(i);
-if(!node){
-console.error(`Node with unknown id ${samples[i]} at index ${i}`);
-continue;
-}
-if(node===gcNode||node===idleNode)
-continue;
-var callFrames=nodeToStackMap.get(node);
-if(!callFrames){
-callFrames=new Array(node.depth+1);
-nodeToStackMap.set(node,callFrames);
-for(var j=0;node.parent;node=node.parent)
-callFrames[j++]=node;
-}
-var jsSampleEvent=new WebInspector.TracingModel.Event(WebInspector.TracingModel.DevToolsTimelineEventCategory,
-WebInspector.TimelineModel.RecordType.JSSample,
-WebInspector.TracingModel.Phase.Instant,timestamps[i],thread);
-jsSampleEvent.args["data"]={stackTrace:callFrames};
-jsEvents.push(jsSampleEvent);
-}
-return jsEvents;
-};
-
-
-
-
-
-WebInspector.TimelineJSProfileProcessor.generateJSFrameEvents=function(events)
-{
-
-
-
-
-
-function equalFrames(frame1,frame2)
-{
-return frame1.scriptId===frame2.scriptId&&frame1.functionName===frame2.functionName;
-}
-
-
-
-
-
-function eventEndTime(e)
-{
-return e.endTime||e.startTime;
-}
-
-
-
-
-
-function isJSInvocationEvent(e)
-{
-switch(e.name){
-case WebInspector.TimelineModel.RecordType.RunMicrotasks:
-case WebInspector.TimelineModel.RecordType.FunctionCall:
-case WebInspector.TimelineModel.RecordType.EvaluateScript:
-return true;}
-
-return false;
-}
-
-var jsFrameEvents=[];
-var jsFramesStack=[];
-var lockedJsStackDepth=[];
-var ordinal=0;
-var filterNativeFunctions=!WebInspector.moduleSetting("showNativeFunctionsInJSProfile").get();
-
-
-
-
-function onStartEvent(e)
-{
-e.ordinal=++ordinal;
-extractStackTrace(e);
-
-lockedJsStackDepth.push(jsFramesStack.length);
-}
-
-
-
-
-
-function onInstantEvent(e,parent)
-{
-e.ordinal=++ordinal;
-if(parent&&isJSInvocationEvent(parent))
-extractStackTrace(e);
-}
-
-
-
-
-function onEndEvent(e)
-{
-truncateJSStack(lockedJsStackDepth.pop(),e.endTime);
-}
-
-
-
-
-
-function truncateJSStack(depth,time)
-{
-if(lockedJsStackDepth.length){
-var lockedDepth=lockedJsStackDepth.peekLast();
-if(depth<lockedDepth){
-console.error("Child stack is shallower ("+depth+") than the parent stack ("+lockedDepth+") at "+time);
-depth=lockedDepth;
-}
-}
-if(jsFramesStack.length<depth){
-console.error("Trying to truncate higher than the current stack size at "+time);
-depth=jsFramesStack.length;
-}
-for(var k=0;k<jsFramesStack.length;++k)
-jsFramesStack[k].setEndTime(time);
-jsFramesStack.length=depth;
-}
-
-
-
-
-function filterStackFrames(stack)
-{
-for(var i=0,j=0;i<stack.length;++i){
-var url=stack[i].url;
-if(url&&url.startsWith("native "))
-continue;
-stack[j++]=stack[i];
-}
-stack.length=j;
-}
-
-
-
-
-function extractStackTrace(e)
-{
-var recordTypes=WebInspector.TimelineModel.RecordType;
-var callFrames;
-if(e.name===recordTypes.JSSample){
-var eventData=e.args["data"]||e.args["beginData"];
-callFrames=eventData&&eventData["stackTrace"];
-}else{
-callFrames=jsFramesStack.map(frameEvent=>frameEvent.args["data"]).reverse();
-}
-if(filterNativeFunctions)
-filterStackFrames(callFrames);
-var endTime=eventEndTime(e);
-var numFrames=callFrames.length;
-var minFrames=Math.min(numFrames,jsFramesStack.length);
-var i;
-for(i=lockedJsStackDepth.peekLast()||0;i<minFrames;++i){
-var newFrame=callFrames[numFrames-1-i];
-var oldFrame=jsFramesStack[i].args["data"];
-if(!equalFrames(newFrame,oldFrame))
-break;
-jsFramesStack[i].setEndTime(Math.max(jsFramesStack[i].endTime,endTime));
-}
-truncateJSStack(i,e.startTime);
-for(;i<numFrames;++i){
-var frame=callFrames[numFrames-1-i];
-var jsFrameEvent=new WebInspector.TracingModel.Event(WebInspector.TracingModel.DevToolsTimelineEventCategory,recordTypes.JSFrame,
-WebInspector.TracingModel.Phase.Complete,e.startTime,e.thread);
-jsFrameEvent.ordinal=e.ordinal;
-jsFrameEvent.addArgs({data:frame});
-jsFrameEvent.setEndTime(endTime);
-jsFramesStack.push(jsFrameEvent);
-jsFrameEvents.push(jsFrameEvent);
-}
-}
-
-
-
-
-
-function findFirstTopLevelEvent(events)
-{
-for(var i=0;i<events.length;++i){
-if(WebInspector.TracingModel.isTopLevelEvent(events[i]))
-return events[i];
-}
-return null;
-}
-
-var firstTopLevelEvent=findFirstTopLevelEvent(events);
-if(firstTopLevelEvent)
-WebInspector.TimelineModel.forEachEvent(events,onStartEvent,onEndEvent,onInstantEvent,firstTopLevelEvent.startTime);
-return jsFrameEvents;
-};
-
-
-
-
-WebInspector.TimelineJSProfileProcessor.CodeMap=function()
-{
-
-this._banks=new Map();
-};
-
-
-
-
-
-
-
-WebInspector.TimelineJSProfileProcessor.CodeMap.Entry=function(address,size,callFrame)
-{
-this.address=address;
-this.size=size;
-this.callFrame=callFrame;
-};
-
-
-
-
-
-
-WebInspector.TimelineJSProfileProcessor.CodeMap.comparator=function(address,entry)
-{
-return address-entry.address;
-};
-
-WebInspector.TimelineJSProfileProcessor.CodeMap.prototype={
-
-
-
-
-
-addEntry:function(addressHex,size,callFrame)
-{
-var entry=new WebInspector.TimelineJSProfileProcessor.CodeMap.Entry(this._getAddress(addressHex),size,callFrame);
-this._addEntry(addressHex,entry);
-},
-
-
-
-
-
-
-moveEntry:function(oldAddressHex,newAddressHex,size)
-{
-var entry=this._getBank(oldAddressHex).removeEntry(this._getAddress(oldAddressHex));
-if(!entry){
-console.error("Entry at address "+oldAddressHex+" not found");
-return;
-}
-entry.address=this._getAddress(newAddressHex);
-entry.size=size;
-this._addEntry(newAddressHex,entry);
-},
-
-
-
-
-
-lookupEntry:function(addressHex)
-{
-return this._getBank(addressHex).lookupEntry(this._getAddress(addressHex));
-},
-
-
-
-
-
-_addEntry:function(addressHex,entry)
-{
-
-this._getBank(addressHex).addEntry(entry);
-},
-
-
-
-
-
-_getBank:function(addressHex)
-{
-addressHex=addressHex.slice(2);
-
-var bankSizeHexDigits=13;
-var maxHexDigits=16;
-var bankName=addressHex.slice(-maxHexDigits,-bankSizeHexDigits);
-var bank=this._banks.get(bankName);
-if(!bank){
-bank=new WebInspector.TimelineJSProfileProcessor.CodeMap.Bank();
-this._banks.set(bankName,bank);
-}
-return bank;
-},
-
-
-
-
-
-_getAddress:function(addressHex)
-{
-
-var bankSizeHexDigits=13;
-addressHex=addressHex.slice(2);
-return parseInt(addressHex.slice(-bankSizeHexDigits),16);
-}};
-
-
-
-
-
-WebInspector.TimelineJSProfileProcessor.CodeMap.Bank=function()
-{
-
-this._entries=[];
-};
-
-WebInspector.TimelineJSProfileProcessor.CodeMap.Bank.prototype={
-
-
-
-
-removeEntry:function(address)
-{
-var index=this._entries.lowerBound(address,WebInspector.TimelineJSProfileProcessor.CodeMap.comparator);
-var entry=this._entries[index];
-if(!entry||entry.address!==address)
-return null;
-this._entries.splice(index,1);
-return entry;
-},
-
-
-
-
-
-lookupEntry:function(address)
-{
-var index=this._entries.upperBound(address,WebInspector.TimelineJSProfileProcessor.CodeMap.comparator)-1;
-var entry=this._entries[index];
-return entry&&address<entry.address+entry.size?entry.callFrame:null;
-},
-
-
-
-
-addEntry:function(newEntry)
-{
-var endAddress=newEntry.address+newEntry.size;
-var lastIndex=this._entries.lowerBound(endAddress,WebInspector.TimelineJSProfileProcessor.CodeMap.comparator);
-var index;
-for(index=lastIndex-1;index>=0;--index){
-var entry=this._entries[index];
-var entryEndAddress=entry.address+entry.size;
-if(entryEndAddress<=newEntry.address)
-break;
-}
-++index;
-this._entries.splice(index,lastIndex-index,newEntry);
-}};
-
-
-
-
-
-
-
-WebInspector.TimelineJSProfileProcessor._buildCallFrame=function(name,scriptId)
-{
-
-
-
-
-
-
-
-
-
-function createFrame(functionName,url,scriptId,line,column,isNative)
-{
-return{
-"functionName":functionName,
-"url":url||"",
-"scriptId":scriptId||"0",
-"lineNumber":line||0,
-"columnNumber":column||0,
-"isNative":isNative||false};
-
-}
-
-
-
-
-
-var rePrefix=/^(\w*:)?[*~]?(.*)$/m;
-var tokens=rePrefix.exec(name);
-var prefix=tokens[1];
-var body=tokens[2];
-var rawName;
-var rawUrl;
-if(prefix==="Script:"){
-rawName="";
-rawUrl=body;
-}else{
-var spacePos=body.lastIndexOf(" ");
-rawName=spacePos!==-1?body.substr(0,spacePos):body;
-rawUrl=spacePos!==-1?body.substr(spacePos+1):"";
-}
-var nativeSuffix=" native";
-var isNative=rawName.endsWith(nativeSuffix);
-var functionName=isNative?rawName.slice(0,-nativeSuffix.length):rawName;
-var urlData=WebInspector.ParsedURL.splitLineAndColumn(rawUrl);
-var url=urlData.url||"";
-var line=urlData.lineNumber||0;
-var column=urlData.columnNumber||0;
-return createFrame(functionName,url,String(scriptId),line,column,isNative);
-};
-
-
-
-
-
-WebInspector.TimelineJSProfileProcessor.processRawV8Samples=function(events)
-{
-var missingAddesses=new Set();
-
-
-
-
-
-function convertRawFrame(address)
-{
-var entry=codeMap.lookupEntry(address);
-if(entry)
-return entry.isNative?null:entry;
-if(!missingAddesses.has(address)){
-missingAddesses.add(address);
-console.error("Address "+address+" has missing code entry");
-}
-return null;
-}
-
-var recordTypes=WebInspector.TimelineModel.RecordType;
-var samples=[];
-var codeMap=new WebInspector.TimelineJSProfileProcessor.CodeMap();
-for(var i=0;i<events.length;++i){
-var e=events[i];
-var data=e.args["data"];
-switch(e.name){
-case recordTypes.JitCodeAdded:
-var frame=WebInspector.TimelineJSProfileProcessor._buildCallFrame(data["name"],data["script_id"]);
-codeMap.addEntry(data["code_start"],data["code_len"],frame);
-break;
-case recordTypes.JitCodeMoved:
-codeMap.moveEntry(data["code_start"],data["new_code_start"],data["code_len"]);
-break;
-case recordTypes.V8Sample:
-var rawStack=data["stack"];
-
-
-if(data["vm_state"]==="js"&&!rawStack.length)
-break;
-var stack=rawStack.map(convertRawFrame);
-stack.remove(null);
-var sampleEvent=new WebInspector.TracingModel.Event(
-WebInspector.TracingModel.DevToolsTimelineEventCategory,
-WebInspector.TimelineModel.RecordType.JSSample,
-WebInspector.TracingModel.Phase.Instant,e.startTime,e.thread);
-sampleEvent.ordinal=e.ordinal;
-sampleEvent.args={"data":{"stackTrace":stack}};
-samples.push(sampleEvent);
-break;}
-
-}
-
-return samples;
-};
-
-},{}],230:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-WebInspector.TimelineModel=function(eventFilter)
-{
-this._eventFilter=eventFilter;
-this.reset();
-};
-
-
-
-
-WebInspector.TimelineModel.RecordType={
-Task:"Task",
-Program:"Program",
-EventDispatch:"EventDispatch",
-
-GPUTask:"GPUTask",
-
-Animation:"Animation",
-RequestMainThreadFrame:"RequestMainThreadFrame",
-BeginFrame:"BeginFrame",
-NeedsBeginFrameChanged:"NeedsBeginFrameChanged",
-BeginMainThreadFrame:"BeginMainThreadFrame",
-ActivateLayerTree:"ActivateLayerTree",
-DrawFrame:"DrawFrame",
-HitTest:"HitTest",
-ScheduleStyleRecalculation:"ScheduleStyleRecalculation",
-RecalculateStyles:"RecalculateStyles",
-UpdateLayoutTree:"UpdateLayoutTree",
-InvalidateLayout:"InvalidateLayout",
-Layout:"Layout",
-UpdateLayer:"UpdateLayer",
-UpdateLayerTree:"UpdateLayerTree",
-PaintSetup:"PaintSetup",
-Paint:"Paint",
-PaintImage:"PaintImage",
-Rasterize:"Rasterize",
-RasterTask:"RasterTask",
-ScrollLayer:"ScrollLayer",
-CompositeLayers:"CompositeLayers",
-
-ScheduleStyleInvalidationTracking:"ScheduleStyleInvalidationTracking",
-StyleRecalcInvalidationTracking:"StyleRecalcInvalidationTracking",
-StyleInvalidatorInvalidationTracking:"StyleInvalidatorInvalidationTracking",
-LayoutInvalidationTracking:"LayoutInvalidationTracking",
-LayerInvalidationTracking:"LayerInvalidationTracking",
-PaintInvalidationTracking:"PaintInvalidationTracking",
-ScrollInvalidationTracking:"ScrollInvalidationTracking",
-
-ParseHTML:"ParseHTML",
-ParseAuthorStyleSheet:"ParseAuthorStyleSheet",
-
-TimerInstall:"TimerInstall",
-TimerRemove:"TimerRemove",
-TimerFire:"TimerFire",
-
-XHRReadyStateChange:"XHRReadyStateChange",
-XHRLoad:"XHRLoad",
-CompileScript:"v8.compile",
-EvaluateScript:"EvaluateScript",
-
-CommitLoad:"CommitLoad",
-MarkLoad:"MarkLoad",
-MarkDOMContent:"MarkDOMContent",
-MarkFirstPaint:"MarkFirstPaint",
-
-TimeStamp:"TimeStamp",
-ConsoleTime:"ConsoleTime",
-UserTiming:"UserTiming",
-
-ResourceSendRequest:"ResourceSendRequest",
-ResourceReceiveResponse:"ResourceReceiveResponse",
-ResourceReceivedData:"ResourceReceivedData",
-ResourceFinish:"ResourceFinish",
-
-RunMicrotasks:"RunMicrotasks",
-FunctionCall:"FunctionCall",
-GCEvent:"GCEvent",
-MajorGC:"MajorGC",
-MinorGC:"MinorGC",
-JSFrame:"JSFrame",
-JSSample:"JSSample",
-
-
-
-V8Sample:"V8Sample",
-JitCodeAdded:"JitCodeAdded",
-JitCodeMoved:"JitCodeMoved",
-ParseScriptOnBackground:"v8.parseOnBackground",
-
-UpdateCounters:"UpdateCounters",
-
-RequestAnimationFrame:"RequestAnimationFrame",
-CancelAnimationFrame:"CancelAnimationFrame",
-FireAnimationFrame:"FireAnimationFrame",
-
-RequestIdleCallback:"RequestIdleCallback",
-CancelIdleCallback:"CancelIdleCallback",
-FireIdleCallback:"FireIdleCallback",
-
-WebSocketCreate:"WebSocketCreate",
-WebSocketSendHandshakeRequest:"WebSocketSendHandshakeRequest",
-WebSocketReceiveHandshakeResponse:"WebSocketReceiveHandshakeResponse",
-WebSocketDestroy:"WebSocketDestroy",
-
-EmbedderCallback:"EmbedderCallback",
-
-SetLayerTreeId:"SetLayerTreeId",
-TracingStartedInPage:"TracingStartedInPage",
-TracingSessionIdForWorker:"TracingSessionIdForWorker",
-
-DecodeImage:"Decode Image",
-ResizeImage:"Resize Image",
-DrawLazyPixelRef:"Draw LazyPixelRef",
-DecodeLazyPixelRef:"Decode LazyPixelRef",
-
-LazyPixelRef:"LazyPixelRef",
-LayerTreeHostImplSnapshot:"cc::LayerTreeHostImpl",
-PictureSnapshot:"cc::Picture",
-DisplayItemListSnapshot:"cc::DisplayItemList",
-LatencyInfo:"LatencyInfo",
-LatencyInfoFlow:"LatencyInfo.Flow",
-InputLatencyMouseMove:"InputLatency::MouseMove",
-InputLatencyMouseWheel:"InputLatency::MouseWheel",
-ImplSideFling:"InputHandlerProxy::HandleGestureFling::started",
-GCIdleLazySweep:"ThreadState::performIdleLazySweep",
-GCCompleteSweep:"ThreadState::completeSweep",
-GCCollectGarbage:"BlinkGCMarking",
-
-
-
-CpuProfile:"CpuProfile"};
-
-
-WebInspector.TimelineModel.Category={
-Console:"blink.console",
-UserTiming:"blink.user_timing",
-LatencyInfo:"latencyInfo"};
-
-
-
-
-
-WebInspector.TimelineModel.WarningType={
-ForcedStyle:"ForcedStyle",
-ForcedLayout:"ForcedLayout",
-IdleDeadlineExceeded:"IdleDeadlineExceeded",
-V8Deopt:"V8Deopt"};
-
-
-WebInspector.TimelineModel.MainThreadName="main";
-WebInspector.TimelineModel.WorkerThreadName="DedicatedWorker Thread";
-WebInspector.TimelineModel.RendererMainThreadName="CrRendererMain";
-
-
-
-
-WebInspector.TimelineModel.AsyncEventGroup={
-animation:Symbol("animation"),
-console:Symbol("console"),
-userTiming:Symbol("userTiming"),
-input:Symbol("input")};
-
-
-
-
-
-
-
-
-
-
-WebInspector.TimelineModel.forEachEvent=function(events,onStartEvent,onEndEvent,onInstantEvent,startTime,endTime)
-{
-startTime=startTime||0;
-endTime=endTime||Infinity;
-var stack=[];
-for(var i=0;i<events.length;++i){
-var e=events[i];
-if((e.endTime||e.startTime)<startTime)
-continue;
-if(e.startTime>=endTime)
-break;
-if(WebInspector.TracingModel.isAsyncPhase(e.phase)||WebInspector.TracingModel.isFlowPhase(e.phase))
-continue;
-while(stack.length&&stack.peekLast().endTime<=e.startTime)
-onEndEvent(stack.pop());
-if(e.duration){
-onStartEvent(e);
-stack.push(e);
-}else{
-onInstantEvent&&onInstantEvent(e,stack.peekLast()||null);
-}
-}
-while(stack.length)
-onEndEvent(stack.pop());
-};
-
-WebInspector.TimelineModel.DevToolsMetadataEvent={
-TracingStartedInBrowser:"TracingStartedInBrowser",
-TracingStartedInPage:"TracingStartedInPage",
-TracingSessionIdForWorker:"TracingSessionIdForWorker"};
-
-
-
-
-
-
-WebInspector.TimelineModel.VirtualThread=function(name)
-{
-this.name=name;
-
-this.events=[];
-
-this.asyncEventsByGroup=new Map();
-};
-
-WebInspector.TimelineModel.VirtualThread.prototype={
-
-
-
-isWorker:function()
-{
-return this.name===WebInspector.TimelineModel.WorkerThreadName;
-}};
-
-
-
-
-
-
-WebInspector.TimelineModel.Record=function(traceEvent)
-{
-this._event=traceEvent;
-this._children=[];
-};
-
-
-
-
-
-
-WebInspector.TimelineModel.Record._compareStartTime=function(a,b)
-{
-
-return a.startTime()<=b.startTime()?-1:1;
-};
-
-WebInspector.TimelineModel.Record.prototype={
-
-
-
-target:function()
-{
-var threadName=this._event.thread.name();
-
-return threadName===WebInspector.TimelineModel.RendererMainThreadName?WebInspector.targetManager.targets()[0]||null:null;
-},
-
-
-
-
-children:function()
-{
-return this._children;
-},
-
-
-
-
-startTime:function()
-{
-return this._event.startTime;
-},
-
-
-
-
-endTime:function()
-{
-return this._event.endTime||this._event.startTime;
-},
-
-
-
-
-thread:function()
-{
-if(this._event.thread.name()===WebInspector.TimelineModel.RendererMainThreadName)
-return WebInspector.TimelineModel.MainThreadName;
-return this._event.thread.name();
-},
-
-
-
-
-type:function()
-{
-return WebInspector.TimelineModel._eventType(this._event);
-},
-
-
-
-
-
-getUserObject:function(key)
-{
-if(key==="TimelineUIUtils::preview-element")
-return this._event.previewElement;
-throw new Error("Unexpected key: "+key);
-},
-
-
-
-
-
-setUserObject:function(key,value)
-{
-if(key!=="TimelineUIUtils::preview-element")
-throw new Error("Unexpected key: "+key);
-this._event.previewElement=value;
-},
-
-
-
-
-traceEvent:function()
-{
-return this._event;
-},
-
-
-
-
-_addChild:function(child)
-{
-this._children.push(child);
-child.parent=this;
-}};
-
-
-
-WebInspector.TimelineModel.MetadataEvents;
-
-
-
-
-WebInspector.TimelineModel._eventType=function(event)
-{
-if(event.hasCategory(WebInspector.TimelineModel.Category.Console))
-return WebInspector.TimelineModel.RecordType.ConsoleTime;
-if(event.hasCategory(WebInspector.TimelineModel.Category.UserTiming))
-return WebInspector.TimelineModel.RecordType.UserTiming;
-if(event.hasCategory(WebInspector.TimelineModel.Category.LatencyInfo))
-return WebInspector.TimelineModel.RecordType.LatencyInfo;
-return event.name;
-};
-
-WebInspector.TimelineModel.prototype={
-
-
-
-
-
-
-forAllRecords:function(preOrderCallback,postOrderCallback)
-{
-
-
-
-
-
-function processRecords(records,depth)
-{
-for(var i=0;i<records.length;++i){
-var record=records[i];
-if(preOrderCallback&&preOrderCallback(record,depth))
-return true;
-if(processRecords(record.children(),depth+1))
-return true;
-if(postOrderCallback&&postOrderCallback(record,depth))
-return true;
-}
-return false;
-}
-return processRecords(this._records,0);
-},
-
-
-
-
-
-forAllFilteredRecords:function(filters,callback)
-{
-
-
-
-
-
-
-function processRecord(record,depth)
-{
-var visible=WebInspector.TimelineModel.isVisible(filters,record.traceEvent());
-if(visible&&callback(record,depth))
-return true;
-
-for(var i=0;i<record.children().length;++i){
-if(processRecord.call(this,record.children()[i],visible?depth+1:depth))
-return true;
-}
-return false;
-}
-
-for(var i=0;i<this._records.length;++i)
-processRecord.call(this,this._records[i],0);
-},
-
-
-
-
-records:function()
-{
-return this._records;
-},
-
-
-
-
-cpuProfiles:function()
-{
-return this._cpuProfiles;
-},
-
-
-
-
-sessionId:function()
-{
-return this._sessionId;
-},
-
-
-
-
-
-targetByEvent:function(event)
-{
-
-var workerId=this._workerIdByThread.get(event.thread);
-var mainTarget=WebInspector.targetManager.mainTarget();
-return workerId?mainTarget.workerManager.targetByWorkerId(workerId):mainTarget;
-},
-
-
-
-
-
-setEvents:function(tracingModel,produceTraceStartedInPage)
-{
-this.reset();
-this._resetProcessingState();
-
-this._minimumRecordTime=tracingModel.minimumRecordTime();
-this._maximumRecordTime=tracingModel.maximumRecordTime();
-
-var metadataEvents=this._processMetadataEvents(tracingModel,!!produceTraceStartedInPage);
-if(Runtime.experiments.isEnabled("timelineShowAllProcesses")){
-var lastPageMetaEvent=metadataEvents.page.peekLast();
-for(var process of tracingModel.sortedProcesses()){
-for(var thread of process.sortedThreads())
-this._processThreadEvents(0,Infinity,thread,thread===lastPageMetaEvent.thread);
-}
-}else{
-var startTime=0;
-for(var i=0,length=metadataEvents.page.length;i<length;i++){
-var metaEvent=metadataEvents.page[i];
-var process=metaEvent.thread.process();
-var endTime=i+1<length?metadataEvents.page[i+1].startTime:Infinity;
-this._currentPage=metaEvent.args["data"]&&metaEvent.args["data"]["page"];
-for(var thread of process.sortedThreads()){
-if(thread.name()===WebInspector.TimelineModel.WorkerThreadName){
-var workerMetaEvent=metadataEvents.workers.find(e=>e.args["data"]["workerThreadId"]===thread.id());
-if(!workerMetaEvent)
-continue;
-var workerId=workerMetaEvent.args["data"]["workerId"];
-if(workerId)
-this._workerIdByThread.set(thread,workerId);
-}
-this._processThreadEvents(startTime,endTime,thread,thread===metaEvent.thread);
-}
-startTime=endTime;
-}
-}
-this._inspectedTargetEvents.sort(WebInspector.TracingModel.Event.compareStartTime);
-
-this._processBrowserEvents(tracingModel);
-this._buildTimelineRecords();
-this._buildGPUEvents(tracingModel);
-this._insertFirstPaintEvent();
-this._resetProcessingState();
-},
-
-
-
-
-
-
-_processMetadataEvents:function(tracingModel,produceTraceStartedInPage)
-{
-var metadataEvents=tracingModel.devToolsMetadataEvents();
-
-var pageDevToolsMetadataEvents=[];
-var workersDevToolsMetadataEvents=[];
-for(var event of metadataEvents){
-if(event.name===WebInspector.TimelineModel.DevToolsMetadataEvent.TracingStartedInPage){
-pageDevToolsMetadataEvents.push(event);
-}else if(event.name===WebInspector.TimelineModel.DevToolsMetadataEvent.TracingSessionIdForWorker){
-workersDevToolsMetadataEvents.push(event);
-}else if(event.name===WebInspector.TimelineModel.DevToolsMetadataEvent.TracingStartedInBrowser){
-console.assert(!this._mainFrameNodeId,"Multiple sessions in trace");
-this._mainFrameNodeId=event.args["frameTreeNodeId"];
-}
-}
-if(!pageDevToolsMetadataEvents.length){
-
-var pageMetaEvent=produceTraceStartedInPage?this._makeMockPageMetadataEvent(tracingModel):null;
-if(!pageMetaEvent){
-console.error(WebInspector.TimelineModel.DevToolsMetadataEvent.TracingStartedInPage+" event not found.");
-return{page:[],workers:[]};
-}
-pageDevToolsMetadataEvents.push(pageMetaEvent);
-}
-var sessionId=pageDevToolsMetadataEvents[0].args["sessionId"]||pageDevToolsMetadataEvents[0].args["data"]["sessionId"];
-this._sessionId=sessionId;
-
-var mismatchingIds=new Set();
-
-
-
-
-function checkSessionId(event)
-{
-var args=event.args;
-
-if(args["data"])
-args=args["data"];
-var id=args["sessionId"];
-if(id===sessionId)
-return true;
-mismatchingIds.add(id);
-return false;
-}
-var result={
-page:pageDevToolsMetadataEvents.filter(checkSessionId).sort(WebInspector.TracingModel.Event.compareStartTime),
-workers:workersDevToolsMetadataEvents.filter(checkSessionId).sort(WebInspector.TracingModel.Event.compareStartTime)};
-
-if(mismatchingIds.size)
-WebInspector.console.error("Timeline recording was started in more than one page simultaneously. Session id mismatch: "+this._sessionId+" and "+mismatchingIds.valuesArray()+".");
-return result;
-},
-
-
-
-
-
-_makeMockPageMetadataEvent:function(tracingModel)
-{
-var rendererMainThreadName=WebInspector.TimelineModel.RendererMainThreadName;
-
-var process=tracingModel.sortedProcesses().filter(function(p){return p.threadByName(rendererMainThreadName);})[0];
-var thread=process&&process.threadByName(rendererMainThreadName);
-if(!thread)
-return null;
-var pageMetaEvent=new WebInspector.TracingModel.Event(
-WebInspector.TracingModel.DevToolsMetadataEventCategory,
-WebInspector.TimelineModel.DevToolsMetadataEvent.TracingStartedInPage,
-WebInspector.TracingModel.Phase.Metadata,
-tracingModel.minimumRecordTime(),thread);
-pageMetaEvent.addArgs({"data":{"sessionId":"mockSessionId"}});
-return pageMetaEvent;
-},
-
-_insertFirstPaintEvent:function()
-{
-if(!this._firstCompositeLayers)
-return;
-
-
-var recordTypes=WebInspector.TimelineModel.RecordType;
-var i=this._inspectedTargetEvents.lowerBound(this._firstCompositeLayers,WebInspector.TracingModel.Event.compareStartTime);
-for(;i<this._inspectedTargetEvents.length&&this._inspectedTargetEvents[i].name!==recordTypes.DrawFrame;++i){}
-if(i>=this._inspectedTargetEvents.length)
-return;
-var drawFrameEvent=this._inspectedTargetEvents[i];
-var firstPaintEvent=new WebInspector.TracingModel.Event(drawFrameEvent.categoriesString,recordTypes.MarkFirstPaint,WebInspector.TracingModel.Phase.Instant,drawFrameEvent.startTime,drawFrameEvent.thread);
-this._mainThreadEvents.splice(this._mainThreadEvents.lowerBound(firstPaintEvent,WebInspector.TracingModel.Event.compareStartTime),0,firstPaintEvent);
-var firstPaintRecord=new WebInspector.TimelineModel.Record(firstPaintEvent);
-this._eventDividerRecords.splice(this._eventDividerRecords.lowerBound(firstPaintRecord,WebInspector.TimelineModel.Record._compareStartTime),0,firstPaintRecord);
-},
-
-
-
-
-_processBrowserEvents:function(tracingModel)
-{
-var browserMain=WebInspector.TracingModel.browserMainThread(tracingModel);
-if(!browserMain)
-return;
-
-
-browserMain.events().forEach(this._processBrowserEvent,this);
-
-var asyncEventsByGroup=new Map();
-this._processAsyncEvents(asyncEventsByGroup,browserMain.asyncEvents());
-this._mergeAsyncEvents(this._mainThreadAsyncEventsByGroup,asyncEventsByGroup);
-},
-
-_buildTimelineRecords:function()
-{
-var topLevelRecords=this._buildTimelineRecordsForThread(this.mainThreadEvents());
-for(var i=0;i<topLevelRecords.length;i++){
-var record=topLevelRecords[i];
-if(WebInspector.TracingModel.isTopLevelEvent(record.traceEvent()))
-this._mainThreadTasks.push(record);
-}
-
-
-
-
-
-function processVirtualThreadEvents(virtualThread)
-{
-var threadRecords=this._buildTimelineRecordsForThread(virtualThread.events);
-topLevelRecords=topLevelRecords.mergeOrdered(threadRecords,WebInspector.TimelineModel.Record._compareStartTime);
-}
-this.virtualThreads().forEach(processVirtualThreadEvents.bind(this));
-this._records=topLevelRecords;
-},
-
-
-
-
-_buildGPUEvents:function(tracingModel)
-{
-var thread=tracingModel.threadByName("GPU Process","CrGpuMain");
-if(!thread)
-return;
-var gpuEventName=WebInspector.TimelineModel.RecordType.GPUTask;
-this._gpuEvents=thread.events().filter(event=>event.name===gpuEventName);
-},
-
-
-
-
-
-_buildTimelineRecordsForThread:function(threadEvents)
-{
-var recordStack=[];
-var topLevelRecords=[];
-
-for(var i=0,size=threadEvents.length;i<size;++i){
-var event=threadEvents[i];
-for(var top=recordStack.peekLast();top&&top._event.endTime<=event.startTime;top=recordStack.peekLast())
-recordStack.pop();
-if(event.phase===WebInspector.TracingModel.Phase.AsyncEnd||event.phase===WebInspector.TracingModel.Phase.NestableAsyncEnd)
-continue;
-var parentRecord=recordStack.peekLast();
-
-if(WebInspector.TracingModel.isAsyncBeginPhase(event.phase)&&parentRecord&&event.endTime>parentRecord._event.endTime)
-continue;
-var record=new WebInspector.TimelineModel.Record(event);
-if(WebInspector.TimelineModel.isMarkerEvent(event))
-this._eventDividerRecords.push(record);
-if(!this._eventFilter.accept(event)&&!WebInspector.TracingModel.isTopLevelEvent(event))
-continue;
-if(parentRecord)
-parentRecord._addChild(record);else
-
-topLevelRecords.push(record);
-if(event.endTime)
-recordStack.push(record);
-}
-
-return topLevelRecords;
-},
-
-_resetProcessingState:function()
-{
-this._asyncEventTracker=new WebInspector.TimelineAsyncEventTracker();
-this._invalidationTracker=new WebInspector.InvalidationTracker();
-this._layoutInvalidate={};
-this._lastScheduleStyleRecalculation={};
-this._paintImageEventByPixelRefId={};
-this._lastPaintForLayer={};
-this._lastRecalculateStylesEvent=null;
-this._currentScriptEvent=null;
-this._eventStack=[];
-this._hadCommitLoad=false;
-this._firstCompositeLayers=null;
-
-this._knownInputEvents=new Set();
-this._currentPage=null;
-},
-
-
-
-
-
-
-
-_processThreadEvents:function(startTime,endTime,thread,isMainThread)
-{
-var events=thread.events();
-var asyncEvents=thread.asyncEvents();
-
-var jsSamples;
-if(Runtime.experiments.isEnabled("timelineTracingJSProfile")){
-jsSamples=WebInspector.TimelineJSProfileProcessor.processRawV8Samples(events);
-}else{
-var cpuProfileEvent=events.peekLast();
-if(cpuProfileEvent&&cpuProfileEvent.name===WebInspector.TimelineModel.RecordType.CpuProfile){
-var cpuProfile=cpuProfileEvent.args["data"]["cpuProfile"];
-if(cpuProfile){
-var jsProfileModel=new WebInspector.CPUProfileDataModel(cpuProfile);
-this._cpuProfiles.push(jsProfileModel);
-jsSamples=WebInspector.TimelineJSProfileProcessor.generateTracingEventsFromCpuProfile(jsProfileModel,thread);
-}
-}
-}
-
-if(jsSamples&&jsSamples.length)
-events=events.mergeOrdered(jsSamples,WebInspector.TracingModel.Event.orderedCompareStartTime);
-if(jsSamples||events.some(function(e){return e.name===WebInspector.TimelineModel.RecordType.JSSample;})){
-var jsFrameEvents=WebInspector.TimelineJSProfileProcessor.generateJSFrameEvents(events);
-if(jsFrameEvents&&jsFrameEvents.length)
-events=jsFrameEvents.mergeOrdered(events,WebInspector.TracingModel.Event.orderedCompareStartTime);
-}
-
-var threadEvents;
-var threadAsyncEventsByGroup;
-if(isMainThread){
-threadEvents=this._mainThreadEvents;
-threadAsyncEventsByGroup=this._mainThreadAsyncEventsByGroup;
-}else{
-var virtualThread=new WebInspector.TimelineModel.VirtualThread(thread.name());
-this._virtualThreads.push(virtualThread);
-threadEvents=virtualThread.events;
-threadAsyncEventsByGroup=virtualThread.asyncEventsByGroup;
-}
-
-this._eventStack=[];
-var i=events.lowerBound(startTime,function(time,event){return time-event.startTime;});
-var length=events.length;
-for(;i<length;i++){
-var event=events[i];
-if(endTime&&event.startTime>=endTime)
-break;
-if(!this._processEvent(event))
-continue;
-threadEvents.push(event);
-this._inspectedTargetEvents.push(event);
-}
-this._processAsyncEvents(threadAsyncEventsByGroup,asyncEvents,startTime,endTime);
-
-if(thread.name()==="Compositor"){
-this._mergeAsyncEvents(this._mainThreadAsyncEventsByGroup,threadAsyncEventsByGroup);
-threadAsyncEventsByGroup.clear();
-}
-},
-
-
-
-
-
-
-
-_processAsyncEvents:function(asyncEventsByGroup,asyncEvents,startTime,endTime)
-{
-var i=startTime?asyncEvents.lowerBound(startTime,function(time,asyncEvent){return time-asyncEvent.startTime;}):0;
-for(;i<asyncEvents.length;++i){
-var asyncEvent=asyncEvents[i];
-if(endTime&&asyncEvent.startTime>=endTime)
-break;
-var asyncGroup=this._processAsyncEvent(asyncEvent);
-if(!asyncGroup)
-continue;
-var groupAsyncEvents=asyncEventsByGroup.get(asyncGroup);
-if(!groupAsyncEvents){
-groupAsyncEvents=[];
-asyncEventsByGroup.set(asyncGroup,groupAsyncEvents);
-}
-groupAsyncEvents.push(asyncEvent);
-}
-},
-
-
-
-
-
-_processEvent:function(event)
-{
-var eventStack=this._eventStack;
-while(eventStack.length&&eventStack.peekLast().endTime<=event.startTime)
-eventStack.pop();
-
-var recordTypes=WebInspector.TimelineModel.RecordType;
-
-if(this._currentScriptEvent&&event.startTime>this._currentScriptEvent.endTime)
-this._currentScriptEvent=null;
-
-var eventData=event.args["data"]||event.args["beginData"]||{};
-if(eventData["stackTrace"])
-event.stackTrace=eventData["stackTrace"];
-if(event.stackTrace&&event.name!==recordTypes.JSSample){
-
-
-for(var i=0;i<event.stackTrace.length;++i){
---event.stackTrace[i].lineNumber;
---event.stackTrace[i].columnNumber;
-}
-}
-
-if(eventStack.length&&eventStack.peekLast().name===recordTypes.EventDispatch)
-eventStack.peekLast().hasChildren=true;
-this._asyncEventTracker.processEvent(event);
-if(event.initiator&&event.initiator.url)
-event.url=event.initiator.url;
-switch(event.name){
-case recordTypes.ResourceSendRequest:
-case recordTypes.WebSocketCreate:
-event.url=eventData["url"];
-event.initiator=eventStack.peekLast()||null;
-break;
-
-case recordTypes.ScheduleStyleRecalculation:
-this._lastScheduleStyleRecalculation[eventData["frame"]]=event;
-break;
-
-case recordTypes.UpdateLayoutTree:
-case recordTypes.RecalculateStyles:
-this._invalidationTracker.didRecalcStyle(event);
-if(event.args["beginData"])
-event.initiator=this._lastScheduleStyleRecalculation[event.args["beginData"]["frame"]];
-this._lastRecalculateStylesEvent=event;
-if(this._currentScriptEvent)
-event.warning=WebInspector.TimelineModel.WarningType.ForcedStyle;
-break;
-
-case recordTypes.ScheduleStyleInvalidationTracking:
-case recordTypes.StyleRecalcInvalidationTracking:
-case recordTypes.StyleInvalidatorInvalidationTracking:
-case recordTypes.LayoutInvalidationTracking:
-case recordTypes.LayerInvalidationTracking:
-case recordTypes.PaintInvalidationTracking:
-case recordTypes.ScrollInvalidationTracking:
-this._invalidationTracker.addInvalidation(new WebInspector.InvalidationTrackingEvent(event));
-break;
-
-case recordTypes.InvalidateLayout:
-
-
-var layoutInitator=event;
-var frameId=eventData["frame"];
-if(!this._layoutInvalidate[frameId]&&this._lastRecalculateStylesEvent&&this._lastRecalculateStylesEvent.endTime>event.startTime)
-layoutInitator=this._lastRecalculateStylesEvent.initiator;
-this._layoutInvalidate[frameId]=layoutInitator;
-break;
-
-case recordTypes.Layout:
-this._invalidationTracker.didLayout(event);
-var frameId=event.args["beginData"]["frame"];
-event.initiator=this._layoutInvalidate[frameId];
-
-if(event.args["endData"]){
-event.backendNodeId=event.args["endData"]["rootNode"];
-event.highlightQuad=event.args["endData"]["root"];
-}
-this._layoutInvalidate[frameId]=null;
-if(this._currentScriptEvent)
-event.warning=WebInspector.TimelineModel.WarningType.ForcedLayout;
-break;
-
-case recordTypes.FunctionCall:
-
-if(typeof eventData["scriptName"]==="string")
-eventData["url"]=eventData["scriptName"];
-if(typeof eventData["scriptLine"]==="number")
-eventData["lineNumber"]=eventData["scriptLine"];
-
-case recordTypes.EvaluateScript:
-case recordTypes.CompileScript:
-if(typeof eventData["lineNumber"]==="number")
---eventData["lineNumber"];
-if(typeof eventData["columnNumber"]==="number")
---eventData["columnNumber"];
-if(!this._currentScriptEvent)
-this._currentScriptEvent=event;
-break;
-
-case recordTypes.SetLayerTreeId:
-this._inspectedTargetLayerTreeId=event.args["layerTreeId"]||event.args["data"]["layerTreeId"];
-break;
-
-case recordTypes.Paint:
-this._invalidationTracker.didPaint(event);
-event.highlightQuad=eventData["clip"];
-event.backendNodeId=eventData["nodeId"];
-
-if(!eventData["layerId"])
-break;
-var layerId=eventData["layerId"];
-this._lastPaintForLayer[layerId]=event;
-break;
-
-case recordTypes.DisplayItemListSnapshot:
-case recordTypes.PictureSnapshot:
-var layerUpdateEvent=this._findAncestorEvent(recordTypes.UpdateLayer);
-if(!layerUpdateEvent||layerUpdateEvent.args["layerTreeId"]!==this._inspectedTargetLayerTreeId)
-break;
-var paintEvent=this._lastPaintForLayer[layerUpdateEvent.args["layerId"]];
-if(paintEvent)
-paintEvent.picture=event;
-break;
-
-case recordTypes.ScrollLayer:
-event.backendNodeId=eventData["nodeId"];
-break;
-
-case recordTypes.PaintImage:
-event.backendNodeId=eventData["nodeId"];
-event.url=eventData["url"];
-break;
-
-case recordTypes.DecodeImage:
-case recordTypes.ResizeImage:
-var paintImageEvent=this._findAncestorEvent(recordTypes.PaintImage);
-if(!paintImageEvent){
-var decodeLazyPixelRefEvent=this._findAncestorEvent(recordTypes.DecodeLazyPixelRef);
-paintImageEvent=decodeLazyPixelRefEvent&&this._paintImageEventByPixelRefId[decodeLazyPixelRefEvent.args["LazyPixelRef"]];
-}
-if(!paintImageEvent)
-break;
-event.backendNodeId=paintImageEvent.backendNodeId;
-event.url=paintImageEvent.url;
-break;
-
-case recordTypes.DrawLazyPixelRef:
-var paintImageEvent=this._findAncestorEvent(recordTypes.PaintImage);
-if(!paintImageEvent)
-break;
-this._paintImageEventByPixelRefId[event.args["LazyPixelRef"]]=paintImageEvent;
-event.backendNodeId=paintImageEvent.backendNodeId;
-event.url=paintImageEvent.url;
-break;
-
-case recordTypes.MarkDOMContent:
-case recordTypes.MarkLoad:
-var page=eventData["page"];
-if(page&&page!==this._currentPage)
-return false;
-break;
-
-case recordTypes.CommitLoad:
-var page=eventData["page"];
-if(page&&page!==this._currentPage)
-return false;
-if(!eventData["isMainFrame"])
-break;
-this._hadCommitLoad=true;
-this._firstCompositeLayers=null;
-break;
-
-case recordTypes.CompositeLayers:
-if(!this._firstCompositeLayers&&this._hadCommitLoad)
-this._firstCompositeLayers=event;
-break;
-
-case recordTypes.FireIdleCallback:
-if(event.duration>eventData["allottedMilliseconds"]){
-event.warning=WebInspector.TimelineModel.WarningType.IdleDeadlineExceeded;
-}
-break;}
-
-if(WebInspector.TracingModel.isAsyncPhase(event.phase))
-return true;
-var duration=event.duration;
-if(!duration)
-return true;
-if(eventStack.length){
-var parent=eventStack.peekLast();
-parent.selfTime-=duration;
-if(parent.selfTime<0){
-var epsilon=1e-3;
-if(parent.selfTime<-epsilon)
-console.error("Children are longer than parent at "+event.startTime+" ("+(event.startTime-this.minimumRecordTime()).toFixed(3)+") by "+parent.selfTime.toFixed(3));
-parent.selfTime=0;
-}
-}
-event.selfTime=duration;
-eventStack.push(event);
-return true;
-},
-
-
-
-
-_processBrowserEvent:function(event)
-{
-if(event.name!==WebInspector.TimelineModel.RecordType.LatencyInfoFlow)
-return;
-var frameId=event.args["frameTreeNodeId"];
-if(typeof frameId==="number"&&frameId===this._mainFrameNodeId)
-this._knownInputEvents.add(event.bind_id);
-},
-
-
-
-
-
-_processAsyncEvent:function(asyncEvent)
-{
-var groups=WebInspector.TimelineModel.AsyncEventGroup;
-if(asyncEvent.hasCategory(WebInspector.TimelineModel.Category.Console))
-return groups.console;
-if(asyncEvent.hasCategory(WebInspector.TimelineModel.Category.UserTiming))
-return groups.userTiming;
-if(asyncEvent.name===WebInspector.TimelineModel.RecordType.Animation)
-return groups.animation;
-if(asyncEvent.hasCategory(WebInspector.TimelineModel.Category.LatencyInfo)||asyncEvent.name===WebInspector.TimelineModel.RecordType.ImplSideFling){
-var lastStep=asyncEvent.steps.peekLast();
-
-if(lastStep.phase!==WebInspector.TracingModel.Phase.AsyncEnd)
-return null;
-var data=lastStep.args["data"];
-asyncEvent.causedFrame=!!(data&&data["INPUT_EVENT_LATENCY_RENDERER_SWAP_COMPONENT"]);
-if(asyncEvent.hasCategory(WebInspector.TimelineModel.Category.LatencyInfo)){
-if(!this._knownInputEvents.has(lastStep.id))
-return null;
-if(asyncEvent.name===WebInspector.TimelineModel.RecordType.InputLatencyMouseMove&&!asyncEvent.causedFrame)
-return null;
-var rendererMain=data["INPUT_EVENT_LATENCY_RENDERER_MAIN_COMPONENT"];
-if(rendererMain){
-var time=rendererMain["time"]/1000;
-asyncEvent.steps[0].timeWaitingForMainThread=time-asyncEvent.steps[0].startTime;
-}
-}
-return groups.input;
-}
-return null;
-},
-
-
-
-
-
-_findAncestorEvent:function(name)
-{
-for(var i=this._eventStack.length-1;i>=0;--i){
-var event=this._eventStack[i];
-if(event.name===name)
-return event;
-}
-return null;
-},
-
-
-
-
-
-_mergeAsyncEvents:function(target,source)
-{
-for(var group of source.keys()){
-var events=target.get(group)||[];
-events=events.mergeOrdered(source.get(group)||[],WebInspector.TracingModel.Event.compareStartAndEndTime);
-target.set(group,events);
-}
-},
-
-reset:function()
-{
-this._virtualThreads=[];
-
-this._mainThreadEvents=[];
-
-this._mainThreadAsyncEventsByGroup=new Map();
-
-this._inspectedTargetEvents=[];
-
-this._records=[];
-
-this._mainThreadTasks=[];
-
-this._gpuEvents=[];
-
-this._eventDividerRecords=[];
-
-this._sessionId=null;
-
-this._mainFrameNodeId=null;
-
-this._cpuProfiles=[];
-
-this._workerIdByThread=new WeakMap();
-this._minimumRecordTime=0;
-this._maximumRecordTime=0;
-},
-
-
-
-
-minimumRecordTime:function()
-{
-return this._minimumRecordTime;
-},
-
-
-
-
-maximumRecordTime:function()
-{
-return this._maximumRecordTime;
-},
-
-
-
-
-inspectedTargetEvents:function()
-{
-return this._inspectedTargetEvents;
-},
-
-
-
-
-mainThreadEvents:function()
-{
-return this._mainThreadEvents;
-},
-
-
-
-
-_setMainThreadEvents:function(events)
-{
-this._mainThreadEvents=events;
-},
-
-
-
-
-mainThreadAsyncEvents:function()
-{
-return this._mainThreadAsyncEventsByGroup;
-},
-
-
-
-
-virtualThreads:function()
-{
-return this._virtualThreads;
-},
-
-
-
-
-isEmpty:function()
-{
-return this.minimumRecordTime()===0&&this.maximumRecordTime()===0;
-},
-
-
-
-
-mainThreadTasks:function()
-{
-return this._mainThreadTasks;
-},
-
-
-
-
-gpuEvents:function()
-{
-return this._gpuEvents;
-},
-
-
-
-
-eventDividerRecords:function()
-{
-return this._eventDividerRecords;
-},
-
-
-
-
-networkRequests:function()
-{
-
-var requests=new Map();
-
-var requestsList=[];
-
-var zeroStartRequestsList=[];
-var types=WebInspector.TimelineModel.RecordType;
-var resourceTypes=new Set([
-types.ResourceSendRequest,
-types.ResourceReceiveResponse,
-types.ResourceReceivedData,
-types.ResourceFinish]);
-
-var events=this.mainThreadEvents();
-for(var i=0;i<events.length;++i){
-var e=events[i];
-if(!resourceTypes.has(e.name))
-continue;
-var id=e.args["data"]["requestId"];
-var request=requests.get(id);
-if(request){
-request.addEvent(e);
-}else{
-request=new WebInspector.TimelineModel.NetworkRequest(e);
-requests.set(id,request);
-if(request.startTime)
-requestsList.push(request);else
-
-zeroStartRequestsList.push(request);
-}
-}
-return zeroStartRequestsList.concat(requestsList);
-}};
-
-
-
-
-
-
-
-WebInspector.TimelineModel.isVisible=function(filters,event)
-{
-for(var i=0;i<filters.length;++i){
-if(!filters[i].accept(event))
-return false;
-}
-return true;
-};
-
-
-
-
-
-WebInspector.TimelineModel.isMarkerEvent=function(event)
-{
-var recordTypes=WebInspector.TimelineModel.RecordType;
-switch(event.name){
-case recordTypes.TimeStamp:
-case recordTypes.MarkFirstPaint:
-return true;
-case recordTypes.MarkDOMContent:
-case recordTypes.MarkLoad:
-return event.args["data"]["isMainFrame"];
-default:
-return false;}
-
-};
-
-
-
-
-
-WebInspector.TimelineModel.NetworkRequest=function(event)
-{
-this.startTime=event.name===WebInspector.TimelineModel.RecordType.ResourceSendRequest?event.startTime:0;
-this.endTime=Infinity;
-
-this.children=[];
-this.addEvent(event);
-};
-
-WebInspector.TimelineModel.NetworkRequest.prototype={
-
-
-
-addEvent:function(event)
-{
-this.children.push(event);
-var recordType=WebInspector.TimelineModel.RecordType;
-this.startTime=Math.min(this.startTime,event.startTime);
-var eventData=event.args["data"];
-if(eventData["mimeType"])
-this.mimeType=eventData["mimeType"];
-if("priority"in eventData)
-this.priority=eventData["priority"];
-if(event.name===recordType.ResourceFinish)
-this.endTime=event.startTime;
-if(!this.responseTime&&(event.name===recordType.ResourceReceiveResponse||event.name===recordType.ResourceReceivedData))
-this.responseTime=event.startTime;
-if(!this.url)
-this.url=eventData["url"];
-if(!this.requestMethod)
-this.requestMethod=eventData["requestMethod"];
-}};
-
-
-
-
-
-WebInspector.TimelineModel.Filter=function()
-{
-};
-
-WebInspector.TimelineModel.Filter.prototype={
-
-
-
-
-accept:function(event)
-{
-return true;
-}};
-
-
-
-
-
-
-
-WebInspector.TimelineVisibleEventsFilter=function(visibleTypes)
-{
-WebInspector.TimelineModel.Filter.call(this);
-this._visibleTypes=new Set(visibleTypes);
-};
-
-WebInspector.TimelineVisibleEventsFilter.prototype={
-
-
-
-
-
-accept:function(event)
-{
-return this._visibleTypes.has(WebInspector.TimelineModel._eventType(event));
-},
-
-__proto__:WebInspector.TimelineModel.Filter.prototype};
-
-
-
-
-
-
-
-WebInspector.ExclusiveNameFilter=function(excludeNames)
-{
-WebInspector.TimelineModel.Filter.call(this);
-this._excludeNames=new Set(excludeNames);
-};
-
-WebInspector.ExclusiveNameFilter.prototype={
-
-
-
-
-
-accept:function(event)
-{
-return!this._excludeNames.has(event.name);
-},
-
-__proto__:WebInspector.TimelineModel.Filter.prototype};
-
-
-
-
-
-
-WebInspector.ExcludeTopLevelFilter=function()
-{
-WebInspector.TimelineModel.Filter.call(this);
-};
-
-WebInspector.ExcludeTopLevelFilter.prototype={
-
-
-
-
-
-accept:function(event)
-{
-return!WebInspector.TracingModel.isTopLevelEvent(event);
-},
-
-__proto__:WebInspector.TimelineModel.Filter.prototype};
-
-
-
-
-
-
-WebInspector.InvalidationTrackingEvent=function(event)
-{
-
-this.type=event.name;
-
-this.startTime=event.startTime;
-
-this._tracingEvent=event;
-
-var eventData=event.args["data"];
-
-
-this.frame=eventData["frame"];
-
-this.nodeId=eventData["nodeId"];
-
-this.nodeName=eventData["nodeName"];
-
-this.paintId=eventData["paintId"];
-
-this.invalidationSet=eventData["invalidationSet"];
-
-this.invalidatedSelectorId=eventData["invalidatedSelectorId"];
-
-this.changedId=eventData["changedId"];
-
-this.changedClass=eventData["changedClass"];
-
-this.changedAttribute=eventData["changedAttribute"];
-
-this.changedPseudo=eventData["changedPseudo"];
-
-this.selectorPart=eventData["selectorPart"];
-
-this.extraData=eventData["extraData"];
-
-this.invalidationList=eventData["invalidationList"];
-
-this.cause={reason:eventData["reason"],stackTrace:eventData["stackTrace"]};
-
-
-if(!this.cause.reason&&this.cause.stackTrace&&this.type===WebInspector.TimelineModel.RecordType.LayoutInvalidationTracking)
-this.cause.reason="Layout forced";
-};
-
-
-WebInspector.InvalidationCause;
-
-
-
-
-WebInspector.InvalidationTracker=function()
-{
-this._initializePerFrameState();
-};
-
-WebInspector.InvalidationTracker.prototype={
-
-
-
-addInvalidation:function(invalidation)
-{
-this._startNewFrameIfNeeded();
-
-if(!invalidation.nodeId&&!invalidation.paintId){
-console.error("Invalidation lacks node information.");
-console.error(invalidation);
-return;
-}
-
-
-
-
-var recordTypes=WebInspector.TimelineModel.RecordType;
-if(invalidation.type===recordTypes.PaintInvalidationTracking&&invalidation.nodeId){
-var invalidations=this._invalidationsByNodeId[invalidation.nodeId]||[];
-for(var i=0;i<invalidations.length;++i)
-invalidations[i].paintId=invalidation.paintId;
-
-
-return;
-}
-
-
-
-
-if(invalidation.type===recordTypes.StyleRecalcInvalidationTracking&&invalidation.cause.reason==="StyleInvalidator")
-return;
-
-
-
-
-var styleRecalcInvalidation=invalidation.type===recordTypes.ScheduleStyleInvalidationTracking||
-invalidation.type===recordTypes.StyleInvalidatorInvalidationTracking||
-invalidation.type===recordTypes.StyleRecalcInvalidationTracking;
-if(styleRecalcInvalidation){
-var duringRecalcStyle=invalidation.startTime&&this._lastRecalcStyle&&
-invalidation.startTime>=this._lastRecalcStyle.startTime&&
-invalidation.startTime<=this._lastRecalcStyle.endTime;
-if(duringRecalcStyle)
-this._associateWithLastRecalcStyleEvent(invalidation);
-}
-
-
-if(this._invalidations[invalidation.type])
-this._invalidations[invalidation.type].push(invalidation);else
-
-this._invalidations[invalidation.type]=[invalidation];
-if(invalidation.nodeId){
-if(this._invalidationsByNodeId[invalidation.nodeId])
-this._invalidationsByNodeId[invalidation.nodeId].push(invalidation);else
-
-this._invalidationsByNodeId[invalidation.nodeId]=[invalidation];
-}
-},
-
-
-
-
-didRecalcStyle:function(recalcStyleEvent)
-{
-this._lastRecalcStyle=recalcStyleEvent;
-var types=[WebInspector.TimelineModel.RecordType.ScheduleStyleInvalidationTracking,
-WebInspector.TimelineModel.RecordType.StyleInvalidatorInvalidationTracking,
-WebInspector.TimelineModel.RecordType.StyleRecalcInvalidationTracking];
-for(var invalidation of this._invalidationsOfTypes(types))
-this._associateWithLastRecalcStyleEvent(invalidation);
-},
-
-
-
-
-_associateWithLastRecalcStyleEvent:function(invalidation)
-{
-if(invalidation.linkedRecalcStyleEvent)
-return;
-
-var recordTypes=WebInspector.TimelineModel.RecordType;
-var recalcStyleFrameId=this._lastRecalcStyle.args["beginData"]["frame"];
-if(invalidation.type===recordTypes.StyleInvalidatorInvalidationTracking){
-
-
-this._addSyntheticStyleRecalcInvalidations(this._lastRecalcStyle,recalcStyleFrameId,invalidation);
-}else if(invalidation.type===recordTypes.ScheduleStyleInvalidationTracking){
-
-
-}else{
-this._addInvalidationToEvent(this._lastRecalcStyle,recalcStyleFrameId,invalidation);
-}
-
-invalidation.linkedRecalcStyleEvent=true;
-},
-
-
-
-
-
-
-_addSyntheticStyleRecalcInvalidations:function(event,frameId,styleInvalidatorInvalidation)
-{
-if(!styleInvalidatorInvalidation.invalidationList){
-this._addSyntheticStyleRecalcInvalidation(styleInvalidatorInvalidation._tracingEvent,styleInvalidatorInvalidation);
-return;
-}
-if(!styleInvalidatorInvalidation.nodeId){
-console.error("Invalidation lacks node information.");
-console.error(invalidation);
-return;
-}
-for(var i=0;i<styleInvalidatorInvalidation.invalidationList.length;i++){
-var setId=styleInvalidatorInvalidation.invalidationList[i]["id"];
-var lastScheduleStyleRecalculation;
-var nodeInvalidations=this._invalidationsByNodeId[styleInvalidatorInvalidation.nodeId]||[];
-for(var j=0;j<nodeInvalidations.length;j++){
-var invalidation=nodeInvalidations[j];
-if(invalidation.frame!==frameId||invalidation.invalidationSet!==setId||invalidation.type!==WebInspector.TimelineModel.RecordType.ScheduleStyleInvalidationTracking)
-continue;
-lastScheduleStyleRecalculation=invalidation;
-}
-if(!lastScheduleStyleRecalculation){
-console.error("Failed to lookup the event that scheduled a style invalidator invalidation.");
-continue;
-}
-this._addSyntheticStyleRecalcInvalidation(lastScheduleStyleRecalculation._tracingEvent,styleInvalidatorInvalidation);
-}
-},
-
-
-
-
-
-_addSyntheticStyleRecalcInvalidation:function(baseEvent,styleInvalidatorInvalidation)
-{
-var invalidation=new WebInspector.InvalidationTrackingEvent(baseEvent);
-invalidation.type=WebInspector.TimelineModel.RecordType.StyleRecalcInvalidationTracking;
-invalidation.synthetic=true;
-if(styleInvalidatorInvalidation.cause.reason)
-invalidation.cause.reason=styleInvalidatorInvalidation.cause.reason;
-if(styleInvalidatorInvalidation.selectorPart)
-invalidation.selectorPart=styleInvalidatorInvalidation.selectorPart;
-
-this.addInvalidation(invalidation);
-if(!invalidation.linkedRecalcStyleEvent)
-this._associateWithLastRecalcStyleEvent(invalidation);
-},
-
-
-
-
-didLayout:function(layoutEvent)
-{
-var layoutFrameId=layoutEvent.args["beginData"]["frame"];
-for(var invalidation of this._invalidationsOfTypes([WebInspector.TimelineModel.RecordType.LayoutInvalidationTracking])){
-if(invalidation.linkedLayoutEvent)
-continue;
-this._addInvalidationToEvent(layoutEvent,layoutFrameId,invalidation);
-invalidation.linkedLayoutEvent=true;
-}
-},
-
-
-
-
-didPaint:function(paintEvent)
-{
-this._didPaint=true;
-
-
-
-var layerId=paintEvent.args["data"]["layerId"];
-if(layerId)
-this._lastPaintWithLayer=paintEvent;
-
-
-if(!this._lastPaintWithLayer)
-return;
-
-var effectivePaintId=this._lastPaintWithLayer.args["data"]["nodeId"];
-var paintFrameId=paintEvent.args["data"]["frame"];
-var types=[WebInspector.TimelineModel.RecordType.StyleRecalcInvalidationTracking,
-WebInspector.TimelineModel.RecordType.LayoutInvalidationTracking,
-WebInspector.TimelineModel.RecordType.PaintInvalidationTracking,
-WebInspector.TimelineModel.RecordType.ScrollInvalidationTracking];
-for(var invalidation of this._invalidationsOfTypes(types)){
-if(invalidation.paintId===effectivePaintId)
-this._addInvalidationToEvent(paintEvent,paintFrameId,invalidation);
-}
-},
-
-
-
-
-
-
-_addInvalidationToEvent:function(event,eventFrameId,invalidation)
-{
-if(eventFrameId!==invalidation.frame)
-return;
-if(!event.invalidationTrackingEvents)
-event.invalidationTrackingEvents=[invalidation];else
-
-event.invalidationTrackingEvents.push(invalidation);
-},
-
-
-
-
-
-_invalidationsOfTypes:function(types)
-{
-var invalidations=this._invalidations;
-if(!types)
-types=Object.keys(invalidations);
-function*generator()
-{
-for(var i=0;i<types.length;++i){
-var invalidationList=invalidations[types[i]]||[];
-for(var j=0;j<invalidationList.length;++j)
-yield invalidationList[j];
-}
-}
-return generator();
-},
-
-_startNewFrameIfNeeded:function()
-{
-if(!this._didPaint)
-return;
-
-this._initializePerFrameState();
-},
-
-_initializePerFrameState:function()
-{
-
-this._invalidations={};
-
-this._invalidationsByNodeId={};
-
-this._lastRecalcStyle=undefined;
-this._lastPaintWithLayer=undefined;
-this._didPaint=false;
-}};
-
-
-
-
-
-WebInspector.TimelineAsyncEventTracker=function()
-{
-WebInspector.TimelineAsyncEventTracker._initialize();
-
-this._initiatorByType=new Map();
-for(var initiator of WebInspector.TimelineAsyncEventTracker._asyncEvents.keys())
-this._initiatorByType.set(initiator,new Map());
-};
-
-WebInspector.TimelineAsyncEventTracker._initialize=function()
-{
-if(WebInspector.TimelineAsyncEventTracker._asyncEvents)
-return;
-var events=new Map();
-var type=WebInspector.TimelineModel.RecordType;
-
-events.set(type.TimerInstall,{causes:[type.TimerFire],joinBy:"timerId"});
-events.set(type.ResourceSendRequest,{causes:[type.ResourceReceiveResponse,type.ResourceReceivedData,type.ResourceFinish],joinBy:"requestId"});
-events.set(type.RequestAnimationFrame,{causes:[type.FireAnimationFrame],joinBy:"id"});
-events.set(type.RequestIdleCallback,{causes:[type.FireIdleCallback],joinBy:"id"});
-events.set(type.WebSocketCreate,{causes:[type.WebSocketSendHandshakeRequest,type.WebSocketReceiveHandshakeResponse,type.WebSocketDestroy],joinBy:"identifier"});
-
-WebInspector.TimelineAsyncEventTracker._asyncEvents=events;
-
-WebInspector.TimelineAsyncEventTracker._typeToInitiator=new Map();
-for(var entry of events){
-var types=entry[1].causes;
-for(type of types)
-WebInspector.TimelineAsyncEventTracker._typeToInitiator.set(type,entry[0]);
-}
-};
-
-WebInspector.TimelineAsyncEventTracker.prototype={
-
-
-
-processEvent:function(event)
-{
-var initiatorType=WebInspector.TimelineAsyncEventTracker._typeToInitiator.get(event.name);
-var isInitiator=!initiatorType;
-if(!initiatorType)
-initiatorType=event.name;
-var initiatorInfo=WebInspector.TimelineAsyncEventTracker._asyncEvents.get(initiatorType);
-if(!initiatorInfo)
-return;
-var id=event.args["data"][initiatorInfo.joinBy];
-if(!id)
-return;
-
-var initiatorMap=this._initiatorByType.get(initiatorType);
-if(isInitiator)
-initiatorMap.set(id,event);else
-
-event.initiator=initiatorMap.get(id)||null;
-}};
-
-
-},{}],231:[function(require,module,exports){
-
-
-
-
-WebInspector.TimelineProfileTree={};
-
-
-
-
-WebInspector.TimelineProfileTree.Node=function()
-{
-
-this.totalTime;
-
-this.selfTime;
-
-this.id;
-
-this.event;
-
-this.children;
-
-this.parent;
-this._isGroupNode=false;
-};
-
-WebInspector.TimelineProfileTree.Node.prototype={
-
-
-
-isGroupNode:function()
-{
-return this._isGroupNode;
-}};
-
-
-
-
-
-
-
-
-
-
-WebInspector.TimelineProfileTree.buildTopDown=function(events,filters,startTime,endTime,eventIdCallback)
-{
-
-var initialTime=1e7;
-var root=new WebInspector.TimelineProfileTree.Node();
-root.totalTime=initialTime;
-root.selfTime=initialTime;
-root.children=new Map();
-var parent=root;
-
-
-
-
-function onStartEvent(e)
-{
-if(!WebInspector.TimelineModel.isVisible(filters,e))
-return;
-var time=e.endTime?Math.min(endTime,e.endTime)-Math.max(startTime,e.startTime):0;
-var id=eventIdCallback?eventIdCallback(e):Symbol("uniqueEventId");
-if(!parent.children)
-parent.children=new Map();
-var node=parent.children.get(id);
-if(node){
-node.selfTime+=time;
-node.totalTime+=time;
-}else{
-node=new WebInspector.TimelineProfileTree.Node();
-node.totalTime=time;
-node.selfTime=time;
-node.parent=parent;
-node.id=id;
-node.event=e;
-parent.children.set(id,node);
-}
-parent.selfTime-=time;
-if(parent.selfTime<0){
-console.log("Error: Negative self of "+parent.selfTime,e);
-parent.selfTime=0;
-}
-if(e.endTime)
-parent=node;
-}
-
-
-
-
-function onEndEvent(e)
-{
-if(!WebInspector.TimelineModel.isVisible(filters,e))
-return;
-parent=parent.parent;
-}
-
-var instantEventCallback=eventIdCallback?undefined:onStartEvent;
-WebInspector.TimelineModel.forEachEvent(events,onStartEvent,onEndEvent,instantEventCallback,startTime,endTime);
-root.totalTime-=root.selfTime;
-root.selfTime=0;
-return root;
-};
-
-
-
-
-
-
-WebInspector.TimelineProfileTree.buildBottomUp=function(topDownTree,groupingCallback)
-{
-var buRoot=new WebInspector.TimelineProfileTree.Node();
-buRoot.selfTime=0;
-buRoot.totalTime=0;
-
-buRoot.children=new Map();
-var nodesOnStack=new Set();
-if(topDownTree.children)
-topDownTree.children.forEach(processNode);
-buRoot.totalTime=topDownTree.totalTime;
-
-
-
-
-function processNode(tdNode)
-{
-var buParent=groupingCallback&&groupingCallback(tdNode)||buRoot;
-if(buParent!==buRoot){
-buRoot.children.set(buParent.id,buParent);
-buParent.parent=buRoot;
-}
-appendNode(tdNode,buParent);
-var hadNode=nodesOnStack.has(tdNode.id);
-if(!hadNode)
-nodesOnStack.add(tdNode.id);
-if(tdNode.children)
-tdNode.children.forEach(processNode);
-if(!hadNode)
-nodesOnStack.delete(tdNode.id);
-}
-
-
-
-
-
-function appendNode(tdNode,buParent)
-{
-var selfTime=tdNode.selfTime;
-var totalTime=tdNode.totalTime;
-buParent.selfTime+=selfTime;
-buParent.totalTime+=selfTime;
-while(tdNode.parent){
-if(!buParent.children)
-buParent.children=new Map();
-var id=tdNode.id;
-var buNode=buParent.children.get(id);
-if(!buNode){
-buNode=new WebInspector.TimelineProfileTree.Node();
-buNode.selfTime=selfTime;
-buNode.totalTime=totalTime;
-buNode.event=tdNode.event;
-buNode.id=id;
-buNode.parent=buParent;
-buParent.children.set(id,buNode);
-}else{
-buNode.selfTime+=selfTime;
-if(!nodesOnStack.has(id))
-buNode.totalTime+=totalTime;
-}
-tdNode=tdNode.parent;
-buParent=buNode;
-}
-}
-
-
-var rootChildren=buRoot.children;
-for(var item of rootChildren.entries()){
-if(item[1].selfTime===0)
-rootChildren.delete(item[0]);
-}
-
-return buRoot;
-};
-
-
-
-
-
-WebInspector.TimelineProfileTree.eventURL=function(event)
-{
-var data=event.args["data"]||event.args["beginData"];
-if(data&&data["url"])
-return data["url"];
-var frame=WebInspector.TimelineProfileTree.eventStackFrame(event);
-while(frame){
-var url=frame["url"];
-if(url)
-return url;
-frame=frame.parent;
-}
-return null;
-};
-
-
-
-
-
-WebInspector.TimelineProfileTree.eventStackFrame=function(event)
-{
-if(event.name===WebInspector.TimelineModel.RecordType.JSFrame)
-return event.args["data"]||null;
-var topFrame=event.stackTrace&&event.stackTrace[0];
-if(topFrame)
-return topFrame;
-var initiator=event.initiator;
-return initiator&&initiator.stackTrace&&initiator.stackTrace[0]||null;
-};
-
-
-
-
-
-
-WebInspector.TimelineAggregator=function(titleMapper,categoryMapper)
-{
-this._titleMapper=titleMapper;
-this._categoryMapper=categoryMapper;
-
-this._groupNodes=new Map();
-};
-
-
-
-
-WebInspector.TimelineAggregator.GroupBy={
-None:"None",
-EventName:"EventName",
-Category:"Category",
-Domain:"Domain",
-Subdomain:"Subdomain",
-URL:"URL"};
-
-
-
-
-
-
-WebInspector.TimelineAggregator.eventId=function(event)
-{
-if(event.name===WebInspector.TimelineModel.RecordType.JSFrame){
-var data=event.args["data"];
-return"f:"+data["functionName"]+"@"+(data["scriptId"]||data["url"]||"");
-}
-return event.name+":@"+WebInspector.TimelineProfileTree.eventURL(event);
-};
-
-WebInspector.TimelineAggregator._extensionInternalPrefix="extensions::";
-WebInspector.TimelineAggregator._groupNodeFlag=Symbol("groupNode");
-
-
-
-
-
-WebInspector.TimelineAggregator.isExtensionInternalURL=function(url)
-{
-return url.startsWith(WebInspector.TimelineAggregator._extensionInternalPrefix);
-};
-
-WebInspector.TimelineAggregator.prototype={
-
-
-
-
-groupFunction:function(groupBy)
-{
-var idMapper=this._nodeToGroupIdFunction(groupBy);
-return idMapper&&this._nodeToGroupNode.bind(this,idMapper);
-},
-
-
-
-
-
-
-performGrouping:function(root,groupBy)
-{
-var nodeMapper=this.groupFunction(groupBy);
-if(!nodeMapper)
-return root;
-for(var node of root.children.values()){
-var groupNode=nodeMapper(node);
-groupNode.parent=root;
-groupNode.selfTime+=node.selfTime;
-groupNode.totalTime+=node.totalTime;
-groupNode.children.set(node.id,node);
-node.parent=root;
-}
-root.children=this._groupNodes;
-return root;
-},
-
-
-
-
-
-_nodeToGroupIdFunction:function(groupBy)
-{
-
-
-
-
-function groupByURL(node)
-{
-return WebInspector.TimelineProfileTree.eventURL(node.event)||"";
-}
-
-
-
-
-
-
-function groupByDomain(groupSubdomains,node)
-{
-var url=WebInspector.TimelineProfileTree.eventURL(node.event)||"";
-if(WebInspector.TimelineAggregator.isExtensionInternalURL(url))
-return WebInspector.TimelineAggregator._extensionInternalPrefix;
-var parsedURL=url.asParsedURL();
-if(!parsedURL)
-return"";
-if(parsedURL.scheme==="chrome-extension")
-return parsedURL.scheme+"://"+parsedURL.host;
-if(!groupSubdomains)
-return parsedURL.host;
-if(/^[.0-9]+$/.test(parsedURL.host))
-return parsedURL.host;
-var domainMatch=/([^.]*\.)?[^.]*$/.exec(parsedURL.host);
-return domainMatch&&domainMatch[0]||"";
-}
-
-switch(groupBy){
-case WebInspector.TimelineAggregator.GroupBy.None:return null;
-case WebInspector.TimelineAggregator.GroupBy.EventName:return node=>node.event?this._titleMapper(node.event):"";
-case WebInspector.TimelineAggregator.GroupBy.Category:return node=>node.event?this._categoryMapper(node.event):"";
-case WebInspector.TimelineAggregator.GroupBy.Subdomain:return groupByDomain.bind(null,false);
-case WebInspector.TimelineAggregator.GroupBy.Domain:return groupByDomain.bind(null,true);
-case WebInspector.TimelineAggregator.GroupBy.URL:return groupByURL;
-default:return null;}
-
-},
-
-
-
-
-
-
-_buildGroupNode:function(id,event)
-{
-var groupNode=new WebInspector.TimelineProfileTree.Node();
-groupNode.id=id;
-groupNode.selfTime=0;
-groupNode.totalTime=0;
-groupNode.children=new Map();
-groupNode.event=event;
-groupNode._isGroupNode=true;
-this._groupNodes.set(id,groupNode);
-return groupNode;
-},
-
-
-
-
-
-
-_nodeToGroupNode:function(nodeToGroupId,node)
-{
-var id=nodeToGroupId(node);
-return this._groupNodes.get(id)||this._buildGroupNode(id,node.event);
-}};
-
-
-},{}],232:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-WebInspector.SortableDataGrid=function(columnsArray,editCallback,deleteCallback,refreshCallback,contextMenuCallback)
-{
-WebInspector.ViewportDataGrid.call(this,columnsArray,editCallback,deleteCallback,refreshCallback,contextMenuCallback);
-
-this._sortingFunction=WebInspector.SortableDataGrid.TrivialComparator;
-this.setRootNode(new WebInspector.SortableDataGridNode());
-};
-
-
-WebInspector.SortableDataGrid.NodeComparator;
-
-
-
-
-
-
-WebInspector.SortableDataGrid.TrivialComparator=function(a,b)
-{
-return 0;
-};
-
-
-
-
-
-
-
-WebInspector.SortableDataGrid.NumericComparator=function(columnIdentifier,a,b)
-{
-var aValue=a.data[columnIdentifier];
-var bValue=b.data[columnIdentifier];
-var aNumber=Number(aValue instanceof Node?aValue.textContent:aValue);
-var bNumber=Number(bValue instanceof Node?bValue.textContent:bValue);
-return aNumber<bNumber?-1:aNumber>bNumber?1:0;
-};
-
-
-
-
-
-
-
-WebInspector.SortableDataGrid.StringComparator=function(columnIdentifier,a,b)
-{
-var aValue=a.data[columnIdentifier];
-var bValue=b.data[columnIdentifier];
-var aString=aValue instanceof Node?aValue.textContent:String(aValue);
-var bString=bValue instanceof Node?bValue.textContent:String(bValue);
-return aString<bString?-1:aString>bString?1:0;
-};
-
-
-
-
-
-
-
-
-WebInspector.SortableDataGrid.Comparator=function(comparator,reverseMode,a,b)
-{
-return reverseMode?comparator(b,a):comparator(a,b);
-};
-
-
-
-
-
-
-WebInspector.SortableDataGrid.create=function(columnNames,values)
-{
-var numColumns=columnNames.length;
-if(!numColumns)
-return null;
-
-var columns=[];
-for(var i=0;i<columnNames.length;++i)
-columns.push({title:columnNames[i],width:columnNames[i].length,sortable:true});
-
-var nodes=[];
-for(var i=0;i<values.length/numColumns;++i){
-var data={};
-for(var j=0;j<columnNames.length;++j)
-data[j]=values[numColumns*i+j];
-
-var node=new WebInspector.SortableDataGridNode(data);
-node.selectable=false;
-nodes.push(node);
-}
-
-var dataGrid=new WebInspector.SortableDataGrid(columns);
-var length=nodes.length;
-var rootNode=dataGrid.rootNode();
-for(var i=0;i<length;++i)
-rootNode.appendChild(nodes[i]);
-
-dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged,sortDataGrid);
-
-function sortDataGrid()
-{
-var nodes=dataGrid.rootNode().children;
-var sortColumnIdentifier=dataGrid.sortColumnIdentifier();
-if(!sortColumnIdentifier)
-return;
-
-var columnIsNumeric=true;
-for(var i=0;i<nodes.length;i++){
-var value=nodes[i].data[sortColumnIdentifier];
-if(isNaN(value instanceof Node?value.textContent:value)){
-columnIsNumeric=false;
-break;
-}
-}
-
-var comparator=columnIsNumeric?WebInspector.SortableDataGrid.NumericComparator:WebInspector.SortableDataGrid.StringComparator;
-dataGrid.sortNodes(comparator.bind(null,sortColumnIdentifier),!dataGrid.isSortOrderAscending());
-}
-return dataGrid;
-};
-
-WebInspector.SortableDataGrid.prototype={
-
-
-
-insertChild:function(node)
-{
-var root=this.rootNode();
-root.insertChildOrdered(node);
-},
-
-
-
-
-
-sortNodes:function(comparator,reverseMode)
-{
-this._sortingFunction=WebInspector.SortableDataGrid.Comparator.bind(null,comparator,reverseMode);
-this._rootNode._sortChildren(reverseMode);
-this.scheduleUpdateStructure();
-},
-
-__proto__:WebInspector.ViewportDataGrid.prototype};
-
-
-
-
-
-
-
-
-WebInspector.SortableDataGridNode=function(data,hasChildren)
-{
-WebInspector.ViewportDataGridNode.call(this,data,hasChildren);
-};
-
-WebInspector.SortableDataGridNode.prototype={
-
-
-
-insertChildOrdered:function(node)
-{
-this.insertChild(node,this.children.upperBound(node,this.dataGrid._sortingFunction));
-},
-
-_sortChildren:function()
-{
-this.children.sort(this.dataGrid._sortingFunction);
-for(var i=0;i<this.children.length;++i)
-this.children[i].recalculateSiblings(i);
-for(var child of this.children)
-child._sortChildren();
-},
-
-__proto__:WebInspector.ViewportDataGridNode.prototype};
-
-
-},{}],233:[function(require,module,exports){
-
-
-
-
-
-
-
-exports=module.exports=require('./debug');
-exports.log=log;
-exports.formatArgs=formatArgs;
-exports.save=save;
-exports.load=load;
-exports.useColors=useColors;
-exports.storage='undefined'!=typeof chrome&&
-'undefined'!=typeof chrome.storage?
-chrome.storage.local:
-localstorage();
-
-
-
-
-
-exports.colors=[
-'lightseagreen',
-'forestgreen',
-'goldenrod',
-'dodgerblue',
-'darkorchid',
-'crimson'];
-
-
-
-
-
-
-
-
-
-
-function useColors(){
-
-return'WebkitAppearance'in document.documentElement.style||
-
-window.console&&(console.firebug||console.exception&&console.table)||
-
-
-navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31;
-}
-
-
-
-
-
-exports.formatters.j=function(v){
-return JSON.stringify(v);
-};
-
-
-
-
-
-
-
-
-function formatArgs(){
-var args=arguments;
-var useColors=this.useColors;
-
-args[0]=(useColors?'%c':'')+
-this.namespace+(
-useColors?' %c':' ')+
-args[0]+(
-useColors?'%c ':' ')+
-'+'+exports.humanize(this.diff);
-
-if(!useColors)return args;
-
-var c='color: '+this.color;
-args=[args[0],c,'color: inherit'].concat(Array.prototype.slice.call(args,1));
-
-
-
-
-var index=0;
-var lastC=0;
-args[0].replace(/%[a-z%]/g,function(match){
-if('%%'===match)return;
-index++;
-if('%c'===match){
-
-
-lastC=index;
-}
-});
-
-args.splice(lastC,0,c);
-return args;
-}
-
-
-
-
-
-
-
-
-function log(){
-
-
-return'object'===typeof console&&
-console.log&&
-Function.prototype.apply.call(console.log,console,arguments);
-}
-
-
-
-
-
-
-
-
-function save(namespaces){
-try{
-if(null==namespaces){
-exports.storage.removeItem('debug');
-}else{
-exports.storage.debug=namespaces;
-}
-}catch(e){}
-}
-
-
-
-
-
-
-
-
-function load(){
-var r;
-try{
-r=exports.storage.debug;
-}catch(e){}
-return r;
-}
-
-
-
-
-
-exports.enable(load());
-
-
-
-
-
-
-
-
-
-
-
-
-function localstorage(){
-try{
-return window.localStorage;
-}catch(e){}
-}
-
-},{"./debug":234}],234:[function(require,module,exports){
-
-
-
-
-
-
-
-
-exports=module.exports=debug;
-exports.coerce=coerce;
-exports.disable=disable;
-exports.enable=enable;
-exports.enabled=enabled;
-exports.humanize=require('ms');
-
-
-
-
-
-exports.names=[];
-exports.skips=[];
-
-
-
-
-
-
-
-exports.formatters={};
-
-
-
-
-
-var prevColor=0;
-
-
-
-
-
-var prevTime;
-
-
-
-
-
-
-
-
-function selectColor(){
-return exports.colors[prevColor++%exports.colors.length];
-}
-
-
-
-
-
-
-
-
-
-function debug(namespace){
-
-
-function disabled(){
-}
-disabled.enabled=false;
-
-
-function enabled(){
-
-var self=enabled;
-
-
-var curr=+new Date();
-var ms=curr-(prevTime||curr);
-self.diff=ms;
-self.prev=prevTime;
-self.curr=curr;
-prevTime=curr;
-
-
-if(null==self.useColors)self.useColors=exports.useColors();
-if(null==self.color&&self.useColors)self.color=selectColor();
-
-var args=Array.prototype.slice.call(arguments);
-
-args[0]=exports.coerce(args[0]);
-
-if('string'!==typeof args[0]){
-
-args=['%o'].concat(args);
-}
-
-
-var index=0;
-args[0]=args[0].replace(/%([a-z%])/g,function(match,format){
-
-if(match==='%%')return match;
-index++;
-var formatter=exports.formatters[format];
-if('function'===typeof formatter){
-var val=args[index];
-match=formatter.call(self,val);
-
-
-args.splice(index,1);
-index--;
-}
-return match;
-});
-
-if('function'===typeof exports.formatArgs){
-args=exports.formatArgs.apply(self,args);
-}
-var logFn=enabled.log||exports.log||console.log.bind(console);
-logFn.apply(self,args);
-}
-enabled.enabled=true;
-
-var fn=exports.enabled(namespace)?enabled:disabled;
-
-fn.namespace=namespace;
-
-return fn;
-}
-
-
-
-
-
-
-
-
-
-function enable(namespaces){
-exports.save(namespaces);
-
-var split=(namespaces||'').split(/[\s,]+/);
-var len=split.length;
-
-for(var i=0;i<len;i++){
-if(!split[i])continue;
-namespaces=split[i].replace(/\*/g,'.*?');
-if(namespaces[0]==='-'){
-exports.skips.push(new RegExp('^'+namespaces.substr(1)+'$'));
-}else{
-exports.names.push(new RegExp('^'+namespaces+'$'));
-}
-}
-}
-
-
-
-
-
-
-
-function disable(){
-exports.enable('');
-}
-
-
-
-
-
-
-
-
-
-function enabled(name){
-var i,len;
-for(i=0,len=exports.skips.length;i<len;i++){
-if(exports.skips[i].test(name)){
-return false;
-}
-}
-for(i=0,len=exports.names.length;i<len;i++){
-if(exports.names[i].test(name)){
-return true;
-}
-}
-return false;
-}
-
-
-
-
-
-
-
-
-
-function coerce(val){
-if(val instanceof Error)return val.stack||val.message;
-return val;
-}
-
-},{"ms":271}],235:[function(require,module,exports){
-
-
-
-
-module.exports=function(WebInspector){
-
-function TimelineModelTreeView(model){
-this._rootNode=model;
-}
-
-TimelineModelTreeView.prototype.sortingChanged=function(sortItem,sortOrder){
-if(!sortItem)
-return;
-var sortFunction;
-switch(sortItem){
-case'startTime':
-sortFunction=compareStartTime;
-break;
-case'self':
-sortFunction=compareNumericField.bind(null,'selfTime');
-break;
-case'total':
-sortFunction=compareNumericField.bind(null,'totalTime');
-break;
-case'activity':
-sortFunction=compareName;
-break;
-default:
-console.assert(false,'Unknown sort field: '+sortItem);
-return;}
-
-return this.sortNodes(sortFunction,sortOrder!=='asc');
-
-function compareNumericField(field,a,b){
-var nodeA=a[1];
-var nodeB=b[1];
-return nodeA[field]-nodeB[field];
-}
-
-function compareStartTime(a,b){
-var nodeA=a[1];
-var nodeB=b[1];
-return nodeA.event.startTime-nodeB.event.startTime;
-}
-
-function compareName(a,b){
-var nodeA=a[1];
-var nodeB=b[1];
-var nameA=WebInspector.TimelineTreeView.eventNameForSorting(nodeA.event);
-var nameB=WebInspector.TimelineTreeView.eventNameForSorting(nodeB.event);
-return nameA.localeCompare(nameB);
-}
-};
-
-TimelineModelTreeView.prototype.sortNodes=function(comparator,reverseMode){
-this._sortingFunction=WebInspector.SortableDataGrid.Comparator.bind(null,comparator,reverseMode);
-sortChildren(this._rootNode,this._sortingFunction,reverseMode);
-};
-
-
-
-
-
-
-function sortChildren(parent,sortingFunction){
-if(!parent.children)return;
-parent.children=new Map([...parent.children.entries()].sort(sortingFunction));
-for(var i=0;i<parent.children.length;++i)
-recalculateSiblings(parent.children[i],i);
-for(var child of parent.children.values())
-sortChildren(child,sortingFunction);
-}
-
-
-
-
-
-function recalculateSiblings(node,myIndex){
-if(!node.parent)
-return;
-
-var previousChild=node.parent.children[myIndex-1]||null;
-if(previousChild)
-previousChild.nextSibling=node;
-node.previousSibling=previousChild;
-
-var nextChild=node.parent.children[myIndex+1]||null;
-if(nextChild)
-nextChild.previousSibling=node;
-node.nextSibling=nextChild;
-}
-
-return TimelineModelTreeView;
-
-};
-
-},{}],236:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-exports.glMatrix=require("./gl-matrix/common.js");
-exports.mat2=require("./gl-matrix/mat2.js");
-exports.mat2d=require("./gl-matrix/mat2d.js");
-exports.mat3=require("./gl-matrix/mat3.js");
-exports.mat4=require("./gl-matrix/mat4.js");
-exports.quat=require("./gl-matrix/quat.js");
-exports.vec2=require("./gl-matrix/vec2.js");
-exports.vec3=require("./gl-matrix/vec3.js");
-exports.vec4=require("./gl-matrix/vec4.js");
-},{"./gl-matrix/common.js":237,"./gl-matrix/mat2.js":238,"./gl-matrix/mat2d.js":239,"./gl-matrix/mat3.js":240,"./gl-matrix/mat4.js":241,"./gl-matrix/quat.js":242,"./gl-matrix/vec2.js":243,"./gl-matrix/vec3.js":244,"./gl-matrix/vec4.js":245}],237:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-var glMatrix={};
-
-
-glMatrix.EPSILON=0.000001;
-glMatrix.ARRAY_TYPE=typeof Float32Array!=='undefined'?Float32Array:Array;
-glMatrix.RANDOM=Math.random;
-glMatrix.ENABLE_SIMD=false;
-
-
-glMatrix.SIMD_AVAILABLE=glMatrix.ARRAY_TYPE===Float32Array&&'SIMD'in this;
-glMatrix.USE_SIMD=glMatrix.ENABLE_SIMD&&glMatrix.SIMD_AVAILABLE;
-
-
-
-
-
-
-glMatrix.setMatrixArrayType=function(type){
-glMatrix.ARRAY_TYPE=type;
-};
-
-var degree=Math.PI/180;
-
-
-
-
-
-
-glMatrix.toRadian=function(a){
-return a*degree;
-};
-
-
-
-
-
-
-
-
-
-
-glMatrix.equals=function(a,b){
-return Math.abs(a-b)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a),Math.abs(b));
-};
-
-module.exports=glMatrix;
-
-},{}],238:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-var glMatrix=require("./common.js");
-
-
-
-
-
-var mat2={};
-
-
-
-
-
-
-mat2.create=function(){
-var out=new glMatrix.ARRAY_TYPE(4);
-out[0]=1;
-out[1]=0;
-out[2]=0;
-out[3]=1;
-return out;
-};
-
-
-
-
-
-
-
-mat2.clone=function(a){
-var out=new glMatrix.ARRAY_TYPE(4);
-out[0]=a[0];
-out[1]=a[1];
-out[2]=a[2];
-out[3]=a[3];
-return out;
-};
-
-
-
-
-
-
-
-
-mat2.copy=function(out,a){
-out[0]=a[0];
-out[1]=a[1];
-out[2]=a[2];
-out[3]=a[3];
-return out;
-};
-
-
-
-
-
-
-
-mat2.identity=function(out){
-out[0]=1;
-out[1]=0;
-out[2]=0;
-out[3]=1;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-mat2.fromValues=function(m00,m01,m10,m11){
-var out=new glMatrix.ARRAY_TYPE(4);
-out[0]=m00;
-out[1]=m01;
-out[2]=m10;
-out[3]=m11;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-mat2.set=function(out,m00,m01,m10,m11){
-out[0]=m00;
-out[1]=m01;
-out[2]=m10;
-out[3]=m11;
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat2.transpose=function(out,a){
-
-if(out===a){
-var a1=a[1];
-out[1]=a[2];
-out[2]=a1;
-}else{
-out[0]=a[0];
-out[1]=a[2];
-out[2]=a[1];
-out[3]=a[3];
-}
-
-return out;
-};
-
-
-
-
-
-
-
-
-mat2.invert=function(out,a){
-var a0=a[0],a1=a[1],a2=a[2],a3=a[3],
-
-
-det=a0*a3-a2*a1;
-
-if(!det){
-return null;
-}
-det=1.0/det;
-
-out[0]=a3*det;
-out[1]=-a1*det;
-out[2]=-a2*det;
-out[3]=a0*det;
-
-return out;
-};
-
-
-
-
-
-
-
-
-mat2.adjoint=function(out,a){
-
-var a0=a[0];
-out[0]=a[3];
-out[1]=-a[1];
-out[2]=-a[2];
-out[3]=a0;
-
-return out;
-};
-
-
-
-
-
-
-
-mat2.determinant=function(a){
-return a[0]*a[3]-a[2]*a[1];
-};
-
-
-
-
-
-
-
-
-
-mat2.multiply=function(out,a,b){
-var a0=a[0],a1=a[1],a2=a[2],a3=a[3];
-var b0=b[0],b1=b[1],b2=b[2],b3=b[3];
-out[0]=a0*b0+a2*b1;
-out[1]=a1*b0+a3*b1;
-out[2]=a0*b2+a2*b3;
-out[3]=a1*b2+a3*b3;
-return out;
-};
-
-
-
-
-
-mat2.mul=mat2.multiply;
-
-
-
-
-
-
-
-
-
-mat2.rotate=function(out,a,rad){
-var a0=a[0],a1=a[1],a2=a[2],a3=a[3],
-s=Math.sin(rad),
-c=Math.cos(rad);
-out[0]=a0*c+a2*s;
-out[1]=a1*c+a3*s;
-out[2]=a0*-s+a2*c;
-out[3]=a1*-s+a3*c;
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat2.scale=function(out,a,v){
-var a0=a[0],a1=a[1],a2=a[2],a3=a[3],
-v0=v[0],v1=v[1];
-out[0]=a0*v0;
-out[1]=a1*v0;
-out[2]=a2*v1;
-out[3]=a3*v1;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-mat2.fromRotation=function(out,rad){
-var s=Math.sin(rad),
-c=Math.cos(rad);
-out[0]=c;
-out[1]=s;
-out[2]=-s;
-out[3]=c;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-mat2.fromScaling=function(out,v){
-out[0]=v[0];
-out[1]=0;
-out[2]=0;
-out[3]=v[1];
-return out;
-};
-
-
-
-
-
-
-
-mat2.str=function(a){
-return'mat2('+a[0]+', '+a[1]+', '+a[2]+', '+a[3]+')';
-};
-
-
-
-
-
-
-
-mat2.frob=function(a){
-return Math.sqrt(Math.pow(a[0],2)+Math.pow(a[1],2)+Math.pow(a[2],2)+Math.pow(a[3],2));
-};
-
-
-
-
-
-
-
-
-
-mat2.LDU=function(L,D,U,a){
-L[2]=a[2]/a[0];
-U[0]=a[0];
-U[1]=a[1];
-U[3]=a[3]-L[2]*U[1];
-return[L,D,U];
-};
-
-
-
-
-
-
-
-
-
-mat2.add=function(out,a,b){
-out[0]=a[0]+b[0];
-out[1]=a[1]+b[1];
-out[2]=a[2]+b[2];
-out[3]=a[3]+b[3];
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat2.subtract=function(out,a,b){
-out[0]=a[0]-b[0];
-out[1]=a[1]-b[1];
-out[2]=a[2]-b[2];
-out[3]=a[3]-b[3];
-return out;
-};
-
-
-
-
-
-mat2.sub=mat2.subtract;
-
-
-
-
-
-
-
-
-mat2.exactEquals=function(a,b){
-return a[0]===b[0]&&a[1]===b[1]&&a[2]===b[2]&&a[3]===b[3];
-};
-
-
-
-
-
-
-
-
-mat2.equals=function(a,b){
-var a0=a[0],a1=a[1],a2=a[2],a3=a[3];
-var b0=b[0],b1=b[1],b2=b[2],b3=b[3];
-return Math.abs(a0-b0)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a0),Math.abs(b0))&&
-Math.abs(a1-b1)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a1),Math.abs(b1))&&
-Math.abs(a2-b2)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a2),Math.abs(b2))&&
-Math.abs(a3-b3)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a3),Math.abs(b3));
-};
-
-
-
-
-
-
-
-
-
-mat2.multiplyScalar=function(out,a,b){
-out[0]=a[0]*b;
-out[1]=a[1]*b;
-out[2]=a[2]*b;
-out[3]=a[3]*b;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-mat2.multiplyScalarAndAdd=function(out,a,b,scale){
-out[0]=a[0]+b[0]*scale;
-out[1]=a[1]+b[1]*scale;
-out[2]=a[2]+b[2]*scale;
-out[3]=a[3]+b[3]*scale;
-return out;
-};
-
-module.exports=mat2;
-
-},{"./common.js":237}],239:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-var glMatrix=require("./common.js");
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-var mat2d={};
-
-
-
-
-
-
-mat2d.create=function(){
-var out=new glMatrix.ARRAY_TYPE(6);
-out[0]=1;
-out[1]=0;
-out[2]=0;
-out[3]=1;
-out[4]=0;
-out[5]=0;
-return out;
-};
-
-
-
-
-
-
-
-mat2d.clone=function(a){
-var out=new glMatrix.ARRAY_TYPE(6);
-out[0]=a[0];
-out[1]=a[1];
-out[2]=a[2];
-out[3]=a[3];
-out[4]=a[4];
-out[5]=a[5];
-return out;
-};
-
-
-
-
-
-
-
-
-mat2d.copy=function(out,a){
-out[0]=a[0];
-out[1]=a[1];
-out[2]=a[2];
-out[3]=a[3];
-out[4]=a[4];
-out[5]=a[5];
-return out;
-};
-
-
-
-
-
-
-
-mat2d.identity=function(out){
-out[0]=1;
-out[1]=0;
-out[2]=0;
-out[3]=1;
-out[4]=0;
-out[5]=0;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-mat2d.fromValues=function(a,b,c,d,tx,ty){
-var out=new glMatrix.ARRAY_TYPE(6);
-out[0]=a;
-out[1]=b;
-out[2]=c;
-out[3]=d;
-out[4]=tx;
-out[5]=ty;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-
-mat2d.set=function(out,a,b,c,d,tx,ty){
-out[0]=a;
-out[1]=b;
-out[2]=c;
-out[3]=d;
-out[4]=tx;
-out[5]=ty;
-return out;
-};
-
-
-
-
-
-
-
-
-mat2d.invert=function(out,a){
-var aa=a[0],ab=a[1],ac=a[2],ad=a[3],
-atx=a[4],aty=a[5];
-
-var det=aa*ad-ab*ac;
-if(!det){
-return null;
-}
-det=1.0/det;
-
-out[0]=ad*det;
-out[1]=-ab*det;
-out[2]=-ac*det;
-out[3]=aa*det;
-out[4]=(ac*aty-ad*atx)*det;
-out[5]=(ab*atx-aa*aty)*det;
-return out;
-};
-
-
-
-
-
-
-
-mat2d.determinant=function(a){
-return a[0]*a[3]-a[1]*a[2];
-};
-
-
-
-
-
-
-
-
-
-mat2d.multiply=function(out,a,b){
-var a0=a[0],a1=a[1],a2=a[2],a3=a[3],a4=a[4],a5=a[5],
-b0=b[0],b1=b[1],b2=b[2],b3=b[3],b4=b[4],b5=b[5];
-out[0]=a0*b0+a2*b1;
-out[1]=a1*b0+a3*b1;
-out[2]=a0*b2+a2*b3;
-out[3]=a1*b2+a3*b3;
-out[4]=a0*b4+a2*b5+a4;
-out[5]=a1*b4+a3*b5+a5;
-return out;
-};
-
-
-
-
-
-mat2d.mul=mat2d.multiply;
-
-
-
-
-
-
-
-
-
-mat2d.rotate=function(out,a,rad){
-var a0=a[0],a1=a[1],a2=a[2],a3=a[3],a4=a[4],a5=a[5],
-s=Math.sin(rad),
-c=Math.cos(rad);
-out[0]=a0*c+a2*s;
-out[1]=a1*c+a3*s;
-out[2]=a0*-s+a2*c;
-out[3]=a1*-s+a3*c;
-out[4]=a4;
-out[5]=a5;
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat2d.scale=function(out,a,v){
-var a0=a[0],a1=a[1],a2=a[2],a3=a[3],a4=a[4],a5=a[5],
-v0=v[0],v1=v[1];
-out[0]=a0*v0;
-out[1]=a1*v0;
-out[2]=a2*v1;
-out[3]=a3*v1;
-out[4]=a4;
-out[5]=a5;
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat2d.translate=function(out,a,v){
-var a0=a[0],a1=a[1],a2=a[2],a3=a[3],a4=a[4],a5=a[5],
-v0=v[0],v1=v[1];
-out[0]=a0;
-out[1]=a1;
-out[2]=a2;
-out[3]=a3;
-out[4]=a0*v0+a2*v1+a4;
-out[5]=a1*v0+a3*v1+a5;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-mat2d.fromRotation=function(out,rad){
-var s=Math.sin(rad),c=Math.cos(rad);
-out[0]=c;
-out[1]=s;
-out[2]=-s;
-out[3]=c;
-out[4]=0;
-out[5]=0;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-mat2d.fromScaling=function(out,v){
-out[0]=v[0];
-out[1]=0;
-out[2]=0;
-out[3]=v[1];
-out[4]=0;
-out[5]=0;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-mat2d.fromTranslation=function(out,v){
-out[0]=1;
-out[1]=0;
-out[2]=0;
-out[3]=1;
-out[4]=v[0];
-out[5]=v[1];
-return out;
-};
-
-
-
-
-
-
-
-mat2d.str=function(a){
-return'mat2d('+a[0]+', '+a[1]+', '+a[2]+', '+
-a[3]+', '+a[4]+', '+a[5]+')';
-};
-
-
-
-
-
-
-
-mat2d.frob=function(a){
-return Math.sqrt(Math.pow(a[0],2)+Math.pow(a[1],2)+Math.pow(a[2],2)+Math.pow(a[3],2)+Math.pow(a[4],2)+Math.pow(a[5],2)+1);
-};
-
-
-
-
-
-
-
-
-
-mat2d.add=function(out,a,b){
-out[0]=a[0]+b[0];
-out[1]=a[1]+b[1];
-out[2]=a[2]+b[2];
-out[3]=a[3]+b[3];
-out[4]=a[4]+b[4];
-out[5]=a[5]+b[5];
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat2d.subtract=function(out,a,b){
-out[0]=a[0]-b[0];
-out[1]=a[1]-b[1];
-out[2]=a[2]-b[2];
-out[3]=a[3]-b[3];
-out[4]=a[4]-b[4];
-out[5]=a[5]-b[5];
-return out;
-};
-
-
-
-
-
-mat2d.sub=mat2d.subtract;
-
-
-
-
-
-
-
-
-
-mat2d.multiplyScalar=function(out,a,b){
-out[0]=a[0]*b;
-out[1]=a[1]*b;
-out[2]=a[2]*b;
-out[3]=a[3]*b;
-out[4]=a[4]*b;
-out[5]=a[5]*b;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-mat2d.multiplyScalarAndAdd=function(out,a,b,scale){
-out[0]=a[0]+b[0]*scale;
-out[1]=a[1]+b[1]*scale;
-out[2]=a[2]+b[2]*scale;
-out[3]=a[3]+b[3]*scale;
-out[4]=a[4]+b[4]*scale;
-out[5]=a[5]+b[5]*scale;
-return out;
-};
-
-
-
-
-
-
-
-
-mat2d.exactEquals=function(a,b){
-return a[0]===b[0]&&a[1]===b[1]&&a[2]===b[2]&&a[3]===b[3]&&a[4]===b[4]&&a[5]===b[5];
-};
-
-
-
-
-
-
-
-
-mat2d.equals=function(a,b){
-var a0=a[0],a1=a[1],a2=a[2],a3=a[3],a4=a[4],a5=a[5];
-var b0=b[0],b1=b[1],b2=b[2],b3=b[3],b4=b[4],b5=b[5];
-return Math.abs(a0-b0)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a0),Math.abs(b0))&&
-Math.abs(a1-b1)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a1),Math.abs(b1))&&
-Math.abs(a2-b2)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a2),Math.abs(b2))&&
-Math.abs(a3-b3)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a3),Math.abs(b3))&&
-Math.abs(a4-b4)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a4),Math.abs(b4))&&
-Math.abs(a5-b5)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a5),Math.abs(b5));
-};
-
-module.exports=mat2d;
-
-},{"./common.js":237}],240:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-var glMatrix=require("./common.js");
-
-
-
-
-
-var mat3={};
-
-
-
-
-
-
-mat3.create=function(){
-var out=new glMatrix.ARRAY_TYPE(9);
-out[0]=1;
-out[1]=0;
-out[2]=0;
-out[3]=0;
-out[4]=1;
-out[5]=0;
-out[6]=0;
-out[7]=0;
-out[8]=1;
-return out;
-};
-
-
-
-
-
-
-
-
-mat3.fromMat4=function(out,a){
-out[0]=a[0];
-out[1]=a[1];
-out[2]=a[2];
-out[3]=a[4];
-out[4]=a[5];
-out[5]=a[6];
-out[6]=a[8];
-out[7]=a[9];
-out[8]=a[10];
-return out;
-};
-
-
-
-
-
-
-
-mat3.clone=function(a){
-var out=new glMatrix.ARRAY_TYPE(9);
-out[0]=a[0];
-out[1]=a[1];
-out[2]=a[2];
-out[3]=a[3];
-out[4]=a[4];
-out[5]=a[5];
-out[6]=a[6];
-out[7]=a[7];
-out[8]=a[8];
-return out;
-};
-
-
-
-
-
-
-
-
-mat3.copy=function(out,a){
-out[0]=a[0];
-out[1]=a[1];
-out[2]=a[2];
-out[3]=a[3];
-out[4]=a[4];
-out[5]=a[5];
-out[6]=a[6];
-out[7]=a[7];
-out[8]=a[8];
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-mat3.fromValues=function(m00,m01,m02,m10,m11,m12,m20,m21,m22){
-var out=new glMatrix.ARRAY_TYPE(9);
-out[0]=m00;
-out[1]=m01;
-out[2]=m02;
-out[3]=m10;
-out[4]=m11;
-out[5]=m12;
-out[6]=m20;
-out[7]=m21;
-out[8]=m22;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-mat3.set=function(out,m00,m01,m02,m10,m11,m12,m20,m21,m22){
-out[0]=m00;
-out[1]=m01;
-out[2]=m02;
-out[3]=m10;
-out[4]=m11;
-out[5]=m12;
-out[6]=m20;
-out[7]=m21;
-out[8]=m22;
-return out;
-};
-
-
-
-
-
-
-
-mat3.identity=function(out){
-out[0]=1;
-out[1]=0;
-out[2]=0;
-out[3]=0;
-out[4]=1;
-out[5]=0;
-out[6]=0;
-out[7]=0;
-out[8]=1;
-return out;
-};
-
-
-
-
-
-
-
-
-mat3.transpose=function(out,a){
-
-if(out===a){
-var a01=a[1],a02=a[2],a12=a[5];
-out[1]=a[3];
-out[2]=a[6];
-out[3]=a01;
-out[5]=a[7];
-out[6]=a02;
-out[7]=a12;
-}else{
-out[0]=a[0];
-out[1]=a[3];
-out[2]=a[6];
-out[3]=a[1];
-out[4]=a[4];
-out[5]=a[7];
-out[6]=a[2];
-out[7]=a[5];
-out[8]=a[8];
-}
-
-return out;
-};
-
-
-
-
-
-
-
-
-mat3.invert=function(out,a){
-var a00=a[0],a01=a[1],a02=a[2],
-a10=a[3],a11=a[4],a12=a[5],
-a20=a[6],a21=a[7],a22=a[8],
-
-b01=a22*a11-a12*a21,
-b11=-a22*a10+a12*a20,
-b21=a21*a10-a11*a20,
-
-
-det=a00*b01+a01*b11+a02*b21;
-
-if(!det){
-return null;
-}
-det=1.0/det;
-
-out[0]=b01*det;
-out[1]=(-a22*a01+a02*a21)*det;
-out[2]=(a12*a01-a02*a11)*det;
-out[3]=b11*det;
-out[4]=(a22*a00-a02*a20)*det;
-out[5]=(-a12*a00+a02*a10)*det;
-out[6]=b21*det;
-out[7]=(-a21*a00+a01*a20)*det;
-out[8]=(a11*a00-a01*a10)*det;
-return out;
-};
-
-
-
-
-
-
-
-
-mat3.adjoint=function(out,a){
-var a00=a[0],a01=a[1],a02=a[2],
-a10=a[3],a11=a[4],a12=a[5],
-a20=a[6],a21=a[7],a22=a[8];
-
-out[0]=a11*a22-a12*a21;
-out[1]=a02*a21-a01*a22;
-out[2]=a01*a12-a02*a11;
-out[3]=a12*a20-a10*a22;
-out[4]=a00*a22-a02*a20;
-out[5]=a02*a10-a00*a12;
-out[6]=a10*a21-a11*a20;
-out[7]=a01*a20-a00*a21;
-out[8]=a00*a11-a01*a10;
-return out;
-};
-
-
-
-
-
-
-
-mat3.determinant=function(a){
-var a00=a[0],a01=a[1],a02=a[2],
-a10=a[3],a11=a[4],a12=a[5],
-a20=a[6],a21=a[7],a22=a[8];
-
-return a00*(a22*a11-a12*a21)+a01*(-a22*a10+a12*a20)+a02*(a21*a10-a11*a20);
-};
-
-
-
-
-
-
-
-
-
-mat3.multiply=function(out,a,b){
-var a00=a[0],a01=a[1],a02=a[2],
-a10=a[3],a11=a[4],a12=a[5],
-a20=a[6],a21=a[7],a22=a[8],
-
-b00=b[0],b01=b[1],b02=b[2],
-b10=b[3],b11=b[4],b12=b[5],
-b20=b[6],b21=b[7],b22=b[8];
-
-out[0]=b00*a00+b01*a10+b02*a20;
-out[1]=b00*a01+b01*a11+b02*a21;
-out[2]=b00*a02+b01*a12+b02*a22;
-
-out[3]=b10*a00+b11*a10+b12*a20;
-out[4]=b10*a01+b11*a11+b12*a21;
-out[5]=b10*a02+b11*a12+b12*a22;
-
-out[6]=b20*a00+b21*a10+b22*a20;
-out[7]=b20*a01+b21*a11+b22*a21;
-out[8]=b20*a02+b21*a12+b22*a22;
-return out;
-};
-
-
-
-
-
-mat3.mul=mat3.multiply;
-
-
-
-
-
-
-
-
-
-mat3.translate=function(out,a,v){
-var a00=a[0],a01=a[1],a02=a[2],
-a10=a[3],a11=a[4],a12=a[5],
-a20=a[6],a21=a[7],a22=a[8],
-x=v[0],y=v[1];
-
-out[0]=a00;
-out[1]=a01;
-out[2]=a02;
-
-out[3]=a10;
-out[4]=a11;
-out[5]=a12;
-
-out[6]=x*a00+y*a10+a20;
-out[7]=x*a01+y*a11+a21;
-out[8]=x*a02+y*a12+a22;
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat3.rotate=function(out,a,rad){
-var a00=a[0],a01=a[1],a02=a[2],
-a10=a[3],a11=a[4],a12=a[5],
-a20=a[6],a21=a[7],a22=a[8],
-
-s=Math.sin(rad),
-c=Math.cos(rad);
-
-out[0]=c*a00+s*a10;
-out[1]=c*a01+s*a11;
-out[2]=c*a02+s*a12;
-
-out[3]=c*a10-s*a00;
-out[4]=c*a11-s*a01;
-out[5]=c*a12-s*a02;
-
-out[6]=a20;
-out[7]=a21;
-out[8]=a22;
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat3.scale=function(out,a,v){
-var x=v[0],y=v[1];
-
-out[0]=x*a[0];
-out[1]=x*a[1];
-out[2]=x*a[2];
-
-out[3]=y*a[3];
-out[4]=y*a[4];
-out[5]=y*a[5];
-
-out[6]=a[6];
-out[7]=a[7];
-out[8]=a[8];
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-mat3.fromTranslation=function(out,v){
-out[0]=1;
-out[1]=0;
-out[2]=0;
-out[3]=0;
-out[4]=1;
-out[5]=0;
-out[6]=v[0];
-out[7]=v[1];
-out[8]=1;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-mat3.fromRotation=function(out,rad){
-var s=Math.sin(rad),c=Math.cos(rad);
-
-out[0]=c;
-out[1]=s;
-out[2]=0;
-
-out[3]=-s;
-out[4]=c;
-out[5]=0;
-
-out[6]=0;
-out[7]=0;
-out[8]=1;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-mat3.fromScaling=function(out,v){
-out[0]=v[0];
-out[1]=0;
-out[2]=0;
-
-out[3]=0;
-out[4]=v[1];
-out[5]=0;
-
-out[6]=0;
-out[7]=0;
-out[8]=1;
-return out;
-};
-
-
-
-
-
-
-
-
-mat3.fromMat2d=function(out,a){
-out[0]=a[0];
-out[1]=a[1];
-out[2]=0;
-
-out[3]=a[2];
-out[4]=a[3];
-out[5]=0;
-
-out[6]=a[4];
-out[7]=a[5];
-out[8]=1;
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat3.fromQuat=function(out,q){
-var x=q[0],y=q[1],z=q[2],w=q[3],
-x2=x+x,
-y2=y+y,
-z2=z+z,
-
-xx=x*x2,
-yx=y*x2,
-yy=y*y2,
-zx=z*x2,
-zy=z*y2,
-zz=z*z2,
-wx=w*x2,
-wy=w*y2,
-wz=w*z2;
-
-out[0]=1-yy-zz;
-out[3]=yx-wz;
-out[6]=zx+wy;
-
-out[1]=yx+wz;
-out[4]=1-xx-zz;
-out[7]=zy-wx;
-
-out[2]=zx-wy;
-out[5]=zy+wx;
-out[8]=1-xx-yy;
-
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat3.normalFromMat4=function(out,a){
-var a00=a[0],a01=a[1],a02=a[2],a03=a[3],
-a10=a[4],a11=a[5],a12=a[6],a13=a[7],
-a20=a[8],a21=a[9],a22=a[10],a23=a[11],
-a30=a[12],a31=a[13],a32=a[14],a33=a[15],
-
-b00=a00*a11-a01*a10,
-b01=a00*a12-a02*a10,
-b02=a00*a13-a03*a10,
-b03=a01*a12-a02*a11,
-b04=a01*a13-a03*a11,
-b05=a02*a13-a03*a12,
-b06=a20*a31-a21*a30,
-b07=a20*a32-a22*a30,
-b08=a20*a33-a23*a30,
-b09=a21*a32-a22*a31,
-b10=a21*a33-a23*a31,
-b11=a22*a33-a23*a32,
-
-
-det=b00*b11-b01*b10+b02*b09+b03*b08-b04*b07+b05*b06;
-
-if(!det){
-return null;
-}
-det=1.0/det;
-
-out[0]=(a11*b11-a12*b10+a13*b09)*det;
-out[1]=(a12*b08-a10*b11-a13*b07)*det;
-out[2]=(a10*b10-a11*b08+a13*b06)*det;
-
-out[3]=(a02*b10-a01*b11-a03*b09)*det;
-out[4]=(a00*b11-a02*b08+a03*b07)*det;
-out[5]=(a01*b08-a00*b10-a03*b06)*det;
-
-out[6]=(a31*b05-a32*b04+a33*b03)*det;
-out[7]=(a32*b02-a30*b05-a33*b01)*det;
-out[8]=(a30*b04-a31*b02+a33*b00)*det;
-
-return out;
-};
-
-
-
-
-
-
-
-mat3.str=function(a){
-return'mat3('+a[0]+', '+a[1]+', '+a[2]+', '+
-a[3]+', '+a[4]+', '+a[5]+', '+
-a[6]+', '+a[7]+', '+a[8]+')';
-};
-
-
-
-
-
-
-
-mat3.frob=function(a){
-return Math.sqrt(Math.pow(a[0],2)+Math.pow(a[1],2)+Math.pow(a[2],2)+Math.pow(a[3],2)+Math.pow(a[4],2)+Math.pow(a[5],2)+Math.pow(a[6],2)+Math.pow(a[7],2)+Math.pow(a[8],2));
-};
-
-
-
-
-
-
-
-
-
-mat3.add=function(out,a,b){
-out[0]=a[0]+b[0];
-out[1]=a[1]+b[1];
-out[2]=a[2]+b[2];
-out[3]=a[3]+b[3];
-out[4]=a[4]+b[4];
-out[5]=a[5]+b[5];
-out[6]=a[6]+b[6];
-out[7]=a[7]+b[7];
-out[8]=a[8]+b[8];
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat3.subtract=function(out,a,b){
-out[0]=a[0]-b[0];
-out[1]=a[1]-b[1];
-out[2]=a[2]-b[2];
-out[3]=a[3]-b[3];
-out[4]=a[4]-b[4];
-out[5]=a[5]-b[5];
-out[6]=a[6]-b[6];
-out[7]=a[7]-b[7];
-out[8]=a[8]-b[8];
-return out;
-};
-
-
-
-
-
-mat3.sub=mat3.subtract;
-
-
-
-
-
-
-
-
-
-mat3.multiplyScalar=function(out,a,b){
-out[0]=a[0]*b;
-out[1]=a[1]*b;
-out[2]=a[2]*b;
-out[3]=a[3]*b;
-out[4]=a[4]*b;
-out[5]=a[5]*b;
-out[6]=a[6]*b;
-out[7]=a[7]*b;
-out[8]=a[8]*b;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-mat3.multiplyScalarAndAdd=function(out,a,b,scale){
-out[0]=a[0]+b[0]*scale;
-out[1]=a[1]+b[1]*scale;
-out[2]=a[2]+b[2]*scale;
-out[3]=a[3]+b[3]*scale;
-out[4]=a[4]+b[4]*scale;
-out[5]=a[5]+b[5]*scale;
-out[6]=a[6]+b[6]*scale;
-out[7]=a[7]+b[7]*scale;
-out[8]=a[8]+b[8]*scale;
-return out;
-};
-
-
-
-
-
-
-
-
-mat3.exactEquals=function(a,b){
-return a[0]===b[0]&&a[1]===b[1]&&a[2]===b[2]&&
-a[3]===b[3]&&a[4]===b[4]&&a[5]===b[5]&&
-a[6]===b[6]&&a[7]===b[7]&&a[8]===b[8];
-};
-
-
-
-
-
-
-
-
-mat3.equals=function(a,b){
-var a0=a[0],a1=a[1],a2=a[2],a3=a[3],a4=a[4],a5=a[5],a6=a[6],a7=a[7],a8=a[8];
-var b0=b[0],b1=b[1],b2=b[2],b3=b[3],b4=b[4],b5=b[5],b6=a[6],b7=b[7],b8=b[8];
-return Math.abs(a0-b0)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a0),Math.abs(b0))&&
-Math.abs(a1-b1)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a1),Math.abs(b1))&&
-Math.abs(a2-b2)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a2),Math.abs(b2))&&
-Math.abs(a3-b3)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a3),Math.abs(b3))&&
-Math.abs(a4-b4)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a4),Math.abs(b4))&&
-Math.abs(a5-b5)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a5),Math.abs(b5))&&
-Math.abs(a6-b6)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a6),Math.abs(b6))&&
-Math.abs(a7-b7)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a7),Math.abs(b7))&&
-Math.abs(a8-b8)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a8),Math.abs(b8));
-};
-
-
-module.exports=mat3;
-
-},{"./common.js":237}],241:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-var glMatrix=require("./common.js");
-
-
-
-
-
-var mat4={
-scalar:{},
-SIMD:{}};
-
-
-
-
-
-
-
-mat4.create=function(){
-var out=new glMatrix.ARRAY_TYPE(16);
-out[0]=1;
-out[1]=0;
-out[2]=0;
-out[3]=0;
-out[4]=0;
-out[5]=1;
-out[6]=0;
-out[7]=0;
-out[8]=0;
-out[9]=0;
-out[10]=1;
-out[11]=0;
-out[12]=0;
-out[13]=0;
-out[14]=0;
-out[15]=1;
-return out;
-};
-
-
-
-
-
-
-
-mat4.clone=function(a){
-var out=new glMatrix.ARRAY_TYPE(16);
-out[0]=a[0];
-out[1]=a[1];
-out[2]=a[2];
-out[3]=a[3];
-out[4]=a[4];
-out[5]=a[5];
-out[6]=a[6];
-out[7]=a[7];
-out[8]=a[8];
-out[9]=a[9];
-out[10]=a[10];
-out[11]=a[11];
-out[12]=a[12];
-out[13]=a[13];
-out[14]=a[14];
-out[15]=a[15];
-return out;
-};
-
-
-
-
-
-
-
-
-mat4.copy=function(out,a){
-out[0]=a[0];
-out[1]=a[1];
-out[2]=a[2];
-out[3]=a[3];
-out[4]=a[4];
-out[5]=a[5];
-out[6]=a[6];
-out[7]=a[7];
-out[8]=a[8];
-out[9]=a[9];
-out[10]=a[10];
-out[11]=a[11];
-out[12]=a[12];
-out[13]=a[13];
-out[14]=a[14];
-out[15]=a[15];
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-mat4.fromValues=function(m00,m01,m02,m03,m10,m11,m12,m13,m20,m21,m22,m23,m30,m31,m32,m33){
-var out=new glMatrix.ARRAY_TYPE(16);
-out[0]=m00;
-out[1]=m01;
-out[2]=m02;
-out[3]=m03;
-out[4]=m10;
-out[5]=m11;
-out[6]=m12;
-out[7]=m13;
-out[8]=m20;
-out[9]=m21;
-out[10]=m22;
-out[11]=m23;
-out[12]=m30;
-out[13]=m31;
-out[14]=m32;
-out[15]=m33;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-mat4.set=function(out,m00,m01,m02,m03,m10,m11,m12,m13,m20,m21,m22,m23,m30,m31,m32,m33){
-out[0]=m00;
-out[1]=m01;
-out[2]=m02;
-out[3]=m03;
-out[4]=m10;
-out[5]=m11;
-out[6]=m12;
-out[7]=m13;
-out[8]=m20;
-out[9]=m21;
-out[10]=m22;
-out[11]=m23;
-out[12]=m30;
-out[13]=m31;
-out[14]=m32;
-out[15]=m33;
-return out;
-};
-
-
-
-
-
-
-
-
-mat4.identity=function(out){
-out[0]=1;
-out[1]=0;
-out[2]=0;
-out[3]=0;
-out[4]=0;
-out[5]=1;
-out[6]=0;
-out[7]=0;
-out[8]=0;
-out[9]=0;
-out[10]=1;
-out[11]=0;
-out[12]=0;
-out[13]=0;
-out[14]=0;
-out[15]=1;
-return out;
-};
-
-
-
-
-
-
-
-
-mat4.scalar.transpose=function(out,a){
-
-if(out===a){
-var a01=a[1],a02=a[2],a03=a[3],
-a12=a[6],a13=a[7],
-a23=a[11];
-
-out[1]=a[4];
-out[2]=a[8];
-out[3]=a[12];
-out[4]=a01;
-out[6]=a[9];
-out[7]=a[13];
-out[8]=a02;
-out[9]=a12;
-out[11]=a[14];
-out[12]=a03;
-out[13]=a13;
-out[14]=a23;
-}else{
-out[0]=a[0];
-out[1]=a[4];
-out[2]=a[8];
-out[3]=a[12];
-out[4]=a[1];
-out[5]=a[5];
-out[6]=a[9];
-out[7]=a[13];
-out[8]=a[2];
-out[9]=a[6];
-out[10]=a[10];
-out[11]=a[14];
-out[12]=a[3];
-out[13]=a[7];
-out[14]=a[11];
-out[15]=a[15];
-}
-
-return out;
-};
-
-
-
-
-
-
-
-
-mat4.SIMD.transpose=function(out,a){
-var a0,a1,a2,a3,
-tmp01,tmp23,
-out0,out1,out2,out3;
-
-a0=SIMD.Float32x4.load(a,0);
-a1=SIMD.Float32x4.load(a,4);
-a2=SIMD.Float32x4.load(a,8);
-a3=SIMD.Float32x4.load(a,12);
-
-tmp01=SIMD.Float32x4.shuffle(a0,a1,0,1,4,5);
-tmp23=SIMD.Float32x4.shuffle(a2,a3,0,1,4,5);
-out0=SIMD.Float32x4.shuffle(tmp01,tmp23,0,2,4,6);
-out1=SIMD.Float32x4.shuffle(tmp01,tmp23,1,3,5,7);
-SIMD.Float32x4.store(out,0,out0);
-SIMD.Float32x4.store(out,4,out1);
-
-tmp01=SIMD.Float32x4.shuffle(a0,a1,2,3,6,7);
-tmp23=SIMD.Float32x4.shuffle(a2,a3,2,3,6,7);
-out2=SIMD.Float32x4.shuffle(tmp01,tmp23,0,2,4,6);
-out3=SIMD.Float32x4.shuffle(tmp01,tmp23,1,3,5,7);
-SIMD.Float32x4.store(out,8,out2);
-SIMD.Float32x4.store(out,12,out3);
-
-return out;
-};
-
-
-
-
-
-
-
-
-mat4.transpose=glMatrix.USE_SIMD?mat4.SIMD.transpose:mat4.scalar.transpose;
-
-
-
-
-
-
-
-
-mat4.scalar.invert=function(out,a){
-var a00=a[0],a01=a[1],a02=a[2],a03=a[3],
-a10=a[4],a11=a[5],a12=a[6],a13=a[7],
-a20=a[8],a21=a[9],a22=a[10],a23=a[11],
-a30=a[12],a31=a[13],a32=a[14],a33=a[15],
-
-b00=a00*a11-a01*a10,
-b01=a00*a12-a02*a10,
-b02=a00*a13-a03*a10,
-b03=a01*a12-a02*a11,
-b04=a01*a13-a03*a11,
-b05=a02*a13-a03*a12,
-b06=a20*a31-a21*a30,
-b07=a20*a32-a22*a30,
-b08=a20*a33-a23*a30,
-b09=a21*a32-a22*a31,
-b10=a21*a33-a23*a31,
-b11=a22*a33-a23*a32,
-
-
-det=b00*b11-b01*b10+b02*b09+b03*b08-b04*b07+b05*b06;
-
-if(!det){
-return null;
-}
-det=1.0/det;
-
-out[0]=(a11*b11-a12*b10+a13*b09)*det;
-out[1]=(a02*b10-a01*b11-a03*b09)*det;
-out[2]=(a31*b05-a32*b04+a33*b03)*det;
-out[3]=(a22*b04-a21*b05-a23*b03)*det;
-out[4]=(a12*b08-a10*b11-a13*b07)*det;
-out[5]=(a00*b11-a02*b08+a03*b07)*det;
-out[6]=(a32*b02-a30*b05-a33*b01)*det;
-out[7]=(a20*b05-a22*b02+a23*b01)*det;
-out[8]=(a10*b10-a11*b08+a13*b06)*det;
-out[9]=(a01*b08-a00*b10-a03*b06)*det;
-out[10]=(a30*b04-a31*b02+a33*b00)*det;
-out[11]=(a21*b02-a20*b04-a23*b00)*det;
-out[12]=(a11*b07-a10*b09-a12*b06)*det;
-out[13]=(a00*b09-a01*b07+a02*b06)*det;
-out[14]=(a31*b01-a30*b03-a32*b00)*det;
-out[15]=(a20*b03-a21*b01+a22*b00)*det;
-
-return out;
-};
-
-
-
-
-
-
-
-
-mat4.SIMD.invert=function(out,a){
-var row0,row1,row2,row3,
-tmp1,
-minor0,minor1,minor2,minor3,
-det,
-a0=SIMD.Float32x4.load(a,0),
-a1=SIMD.Float32x4.load(a,4),
-a2=SIMD.Float32x4.load(a,8),
-a3=SIMD.Float32x4.load(a,12);
-
-
-tmp1=SIMD.Float32x4.shuffle(a0,a1,0,1,4,5);
-row1=SIMD.Float32x4.shuffle(a2,a3,0,1,4,5);
-row0=SIMD.Float32x4.shuffle(tmp1,row1,0,2,4,6);
-row1=SIMD.Float32x4.shuffle(row1,tmp1,1,3,5,7);
-tmp1=SIMD.Float32x4.shuffle(a0,a1,2,3,6,7);
-row3=SIMD.Float32x4.shuffle(a2,a3,2,3,6,7);
-row2=SIMD.Float32x4.shuffle(tmp1,row3,0,2,4,6);
-row3=SIMD.Float32x4.shuffle(row3,tmp1,1,3,5,7);
-
-tmp1=SIMD.Float32x4.mul(row2,row3);
-tmp1=SIMD.Float32x4.swizzle(tmp1,1,0,3,2);
-minor0=SIMD.Float32x4.mul(row1,tmp1);
-minor1=SIMD.Float32x4.mul(row0,tmp1);
-tmp1=SIMD.Float32x4.swizzle(tmp1,2,3,0,1);
-minor0=SIMD.Float32x4.sub(SIMD.Float32x4.mul(row1,tmp1),minor0);
-minor1=SIMD.Float32x4.sub(SIMD.Float32x4.mul(row0,tmp1),minor1);
-minor1=SIMD.Float32x4.swizzle(minor1,2,3,0,1);
-
-tmp1=SIMD.Float32x4.mul(row1,row2);
-tmp1=SIMD.Float32x4.swizzle(tmp1,1,0,3,2);
-minor0=SIMD.Float32x4.add(SIMD.Float32x4.mul(row3,tmp1),minor0);
-minor3=SIMD.Float32x4.mul(row0,tmp1);
-tmp1=SIMD.Float32x4.swizzle(tmp1,2,3,0,1);
-minor0=SIMD.Float32x4.sub(minor0,SIMD.Float32x4.mul(row3,tmp1));
-minor3=SIMD.Float32x4.sub(SIMD.Float32x4.mul(row0,tmp1),minor3);
-minor3=SIMD.Float32x4.swizzle(minor3,2,3,0,1);
-
-tmp1=SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(row1,2,3,0,1),row3);
-tmp1=SIMD.Float32x4.swizzle(tmp1,1,0,3,2);
-row2=SIMD.Float32x4.swizzle(row2,2,3,0,1);
-minor0=SIMD.Float32x4.add(SIMD.Float32x4.mul(row2,tmp1),minor0);
-minor2=SIMD.Float32x4.mul(row0,tmp1);
-tmp1=SIMD.Float32x4.swizzle(tmp1,2,3,0,1);
-minor0=SIMD.Float32x4.sub(minor0,SIMD.Float32x4.mul(row2,tmp1));
-minor2=SIMD.Float32x4.sub(SIMD.Float32x4.mul(row0,tmp1),minor2);
-minor2=SIMD.Float32x4.swizzle(minor2,2,3,0,1);
-
-tmp1=SIMD.Float32x4.mul(row0,row1);
-tmp1=SIMD.Float32x4.swizzle(tmp1,1,0,3,2);
-minor2=SIMD.Float32x4.add(SIMD.Float32x4.mul(row3,tmp1),minor2);
-minor3=SIMD.Float32x4.sub(SIMD.Float32x4.mul(row2,tmp1),minor3);
-tmp1=SIMD.Float32x4.swizzle(tmp1,2,3,0,1);
-minor2=SIMD.Float32x4.sub(SIMD.Float32x4.mul(row3,tmp1),minor2);
-minor3=SIMD.Float32x4.sub(minor3,SIMD.Float32x4.mul(row2,tmp1));
-
-tmp1=SIMD.Float32x4.mul(row0,row3);
-tmp1=SIMD.Float32x4.swizzle(tmp1,1,0,3,2);
-minor1=SIMD.Float32x4.sub(minor1,SIMD.Float32x4.mul(row2,tmp1));
-minor2=SIMD.Float32x4.add(SIMD.Float32x4.mul(row1,tmp1),minor2);
-tmp1=SIMD.Float32x4.swizzle(tmp1,2,3,0,1);
-minor1=SIMD.Float32x4.add(SIMD.Float32x4.mul(row2,tmp1),minor1);
-minor2=SIMD.Float32x4.sub(minor2,SIMD.Float32x4.mul(row1,tmp1));
-
-tmp1=SIMD.Float32x4.mul(row0,row2);
-tmp1=SIMD.Float32x4.swizzle(tmp1,1,0,3,2);
-minor1=SIMD.Float32x4.add(SIMD.Float32x4.mul(row3,tmp1),minor1);
-minor3=SIMD.Float32x4.sub(minor3,SIMD.Float32x4.mul(row1,tmp1));
-tmp1=SIMD.Float32x4.swizzle(tmp1,2,3,0,1);
-minor1=SIMD.Float32x4.sub(minor1,SIMD.Float32x4.mul(row3,tmp1));
-minor3=SIMD.Float32x4.add(SIMD.Float32x4.mul(row1,tmp1),minor3);
-
-
-det=SIMD.Float32x4.mul(row0,minor0);
-det=SIMD.Float32x4.add(SIMD.Float32x4.swizzle(det,2,3,0,1),det);
-det=SIMD.Float32x4.add(SIMD.Float32x4.swizzle(det,1,0,3,2),det);
-tmp1=SIMD.Float32x4.reciprocalApproximation(det);
-det=SIMD.Float32x4.sub(
-SIMD.Float32x4.add(tmp1,tmp1),
-SIMD.Float32x4.mul(det,SIMD.Float32x4.mul(tmp1,tmp1)));
-det=SIMD.Float32x4.swizzle(det,0,0,0,0);
-if(!det){
-return null;
-}
-
-
-SIMD.Float32x4.store(out,0,SIMD.Float32x4.mul(det,minor0));
-SIMD.Float32x4.store(out,4,SIMD.Float32x4.mul(det,minor1));
-SIMD.Float32x4.store(out,8,SIMD.Float32x4.mul(det,minor2));
-SIMD.Float32x4.store(out,12,SIMD.Float32x4.mul(det,minor3));
-return out;
-};
-
-
-
-
-
-
-
-
-mat4.invert=glMatrix.USE_SIMD?mat4.SIMD.invert:mat4.scalar.invert;
-
-
-
-
-
-
-
-
-mat4.scalar.adjoint=function(out,a){
-var a00=a[0],a01=a[1],a02=a[2],a03=a[3],
-a10=a[4],a11=a[5],a12=a[6],a13=a[7],
-a20=a[8],a21=a[9],a22=a[10],a23=a[11],
-a30=a[12],a31=a[13],a32=a[14],a33=a[15];
-
-out[0]=a11*(a22*a33-a23*a32)-a21*(a12*a33-a13*a32)+a31*(a12*a23-a13*a22);
-out[1]=-(a01*(a22*a33-a23*a32)-a21*(a02*a33-a03*a32)+a31*(a02*a23-a03*a22));
-out[2]=a01*(a12*a33-a13*a32)-a11*(a02*a33-a03*a32)+a31*(a02*a13-a03*a12);
-out[3]=-(a01*(a12*a23-a13*a22)-a11*(a02*a23-a03*a22)+a21*(a02*a13-a03*a12));
-out[4]=-(a10*(a22*a33-a23*a32)-a20*(a12*a33-a13*a32)+a30*(a12*a23-a13*a22));
-out[5]=a00*(a22*a33-a23*a32)-a20*(a02*a33-a03*a32)+a30*(a02*a23-a03*a22);
-out[6]=-(a00*(a12*a33-a13*a32)-a10*(a02*a33-a03*a32)+a30*(a02*a13-a03*a12));
-out[7]=a00*(a12*a23-a13*a22)-a10*(a02*a23-a03*a22)+a20*(a02*a13-a03*a12);
-out[8]=a10*(a21*a33-a23*a31)-a20*(a11*a33-a13*a31)+a30*(a11*a23-a13*a21);
-out[9]=-(a00*(a21*a33-a23*a31)-a20*(a01*a33-a03*a31)+a30*(a01*a23-a03*a21));
-out[10]=a00*(a11*a33-a13*a31)-a10*(a01*a33-a03*a31)+a30*(a01*a13-a03*a11);
-out[11]=-(a00*(a11*a23-a13*a21)-a10*(a01*a23-a03*a21)+a20*(a01*a13-a03*a11));
-out[12]=-(a10*(a21*a32-a22*a31)-a20*(a11*a32-a12*a31)+a30*(a11*a22-a12*a21));
-out[13]=a00*(a21*a32-a22*a31)-a20*(a01*a32-a02*a31)+a30*(a01*a22-a02*a21);
-out[14]=-(a00*(a11*a32-a12*a31)-a10*(a01*a32-a02*a31)+a30*(a01*a12-a02*a11));
-out[15]=a00*(a11*a22-a12*a21)-a10*(a01*a22-a02*a21)+a20*(a01*a12-a02*a11);
-return out;
-};
-
-
-
-
-
-
-
-
-mat4.SIMD.adjoint=function(out,a){
-var a0,a1,a2,a3;
-var row0,row1,row2,row3;
-var tmp1;
-var minor0,minor1,minor2,minor3;
-
-var a0=SIMD.Float32x4.load(a,0);
-var a1=SIMD.Float32x4.load(a,4);
-var a2=SIMD.Float32x4.load(a,8);
-var a3=SIMD.Float32x4.load(a,12);
-
-
-tmp1=SIMD.Float32x4.shuffle(a0,a1,0,1,4,5);
-row1=SIMD.Float32x4.shuffle(a2,a3,0,1,4,5);
-row0=SIMD.Float32x4.shuffle(tmp1,row1,0,2,4,6);
-row1=SIMD.Float32x4.shuffle(row1,tmp1,1,3,5,7);
-
-tmp1=SIMD.Float32x4.shuffle(a0,a1,2,3,6,7);
-row3=SIMD.Float32x4.shuffle(a2,a3,2,3,6,7);
-row2=SIMD.Float32x4.shuffle(tmp1,row3,0,2,4,6);
-row3=SIMD.Float32x4.shuffle(row3,tmp1,1,3,5,7);
-
-tmp1=SIMD.Float32x4.mul(row2,row3);
-tmp1=SIMD.Float32x4.swizzle(tmp1,1,0,3,2);
-minor0=SIMD.Float32x4.mul(row1,tmp1);
-minor1=SIMD.Float32x4.mul(row0,tmp1);
-tmp1=SIMD.Float32x4.swizzle(tmp1,2,3,0,1);
-minor0=SIMD.Float32x4.sub(SIMD.Float32x4.mul(row1,tmp1),minor0);
-minor1=SIMD.Float32x4.sub(SIMD.Float32x4.mul(row0,tmp1),minor1);
-minor1=SIMD.Float32x4.swizzle(minor1,2,3,0,1);
-
-tmp1=SIMD.Float32x4.mul(row1,row2);
-tmp1=SIMD.Float32x4.swizzle(tmp1,1,0,3,2);
-minor0=SIMD.Float32x4.add(SIMD.Float32x4.mul(row3,tmp1),minor0);
-minor3=SIMD.Float32x4.mul(row0,tmp1);
-tmp1=SIMD.Float32x4.swizzle(tmp1,2,3,0,1);
-minor0=SIMD.Float32x4.sub(minor0,SIMD.Float32x4.mul(row3,tmp1));
-minor3=SIMD.Float32x4.sub(SIMD.Float32x4.mul(row0,tmp1),minor3);
-minor3=SIMD.Float32x4.swizzle(minor3,2,3,0,1);
-
-tmp1=SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(row1,2,3,0,1),row3);
-tmp1=SIMD.Float32x4.swizzle(tmp1,1,0,3,2);
-row2=SIMD.Float32x4.swizzle(row2,2,3,0,1);
-minor0=SIMD.Float32x4.add(SIMD.Float32x4.mul(row2,tmp1),minor0);
-minor2=SIMD.Float32x4.mul(row0,tmp1);
-tmp1=SIMD.Float32x4.swizzle(tmp1,2,3,0,1);
-minor0=SIMD.Float32x4.sub(minor0,SIMD.Float32x4.mul(row2,tmp1));
-minor2=SIMD.Float32x4.sub(SIMD.Float32x4.mul(row0,tmp1),minor2);
-minor2=SIMD.Float32x4.swizzle(minor2,2,3,0,1);
-
-tmp1=SIMD.Float32x4.mul(row0,row1);
-tmp1=SIMD.Float32x4.swizzle(tmp1,1,0,3,2);
-minor2=SIMD.Float32x4.add(SIMD.Float32x4.mul(row3,tmp1),minor2);
-minor3=SIMD.Float32x4.sub(SIMD.Float32x4.mul(row2,tmp1),minor3);
-tmp1=SIMD.Float32x4.swizzle(tmp1,2,3,0,1);
-minor2=SIMD.Float32x4.sub(SIMD.Float32x4.mul(row3,tmp1),minor2);
-minor3=SIMD.Float32x4.sub(minor3,SIMD.Float32x4.mul(row2,tmp1));
-
-tmp1=SIMD.Float32x4.mul(row0,row3);
-tmp1=SIMD.Float32x4.swizzle(tmp1,1,0,3,2);
-minor1=SIMD.Float32x4.sub(minor1,SIMD.Float32x4.mul(row2,tmp1));
-minor2=SIMD.Float32x4.add(SIMD.Float32x4.mul(row1,tmp1),minor2);
-tmp1=SIMD.Float32x4.swizzle(tmp1,2,3,0,1);
-minor1=SIMD.Float32x4.add(SIMD.Float32x4.mul(row2,tmp1),minor1);
-minor2=SIMD.Float32x4.sub(minor2,SIMD.Float32x4.mul(row1,tmp1));
-
-tmp1=SIMD.Float32x4.mul(row0,row2);
-tmp1=SIMD.Float32x4.swizzle(tmp1,1,0,3,2);
-minor1=SIMD.Float32x4.add(SIMD.Float32x4.mul(row3,tmp1),minor1);
-minor3=SIMD.Float32x4.sub(minor3,SIMD.Float32x4.mul(row1,tmp1));
-tmp1=SIMD.Float32x4.swizzle(tmp1,2,3,0,1);
-minor1=SIMD.Float32x4.sub(minor1,SIMD.Float32x4.mul(row3,tmp1));
-minor3=SIMD.Float32x4.add(SIMD.Float32x4.mul(row1,tmp1),minor3);
-
-SIMD.Float32x4.store(out,0,minor0);
-SIMD.Float32x4.store(out,4,minor1);
-SIMD.Float32x4.store(out,8,minor2);
-SIMD.Float32x4.store(out,12,minor3);
-return out;
-};
-
-
-
-
-
-
-
-
-mat4.adjoint=glMatrix.USE_SIMD?mat4.SIMD.adjoint:mat4.scalar.adjoint;
-
-
-
-
-
-
-
-mat4.determinant=function(a){
-var a00=a[0],a01=a[1],a02=a[2],a03=a[3],
-a10=a[4],a11=a[5],a12=a[6],a13=a[7],
-a20=a[8],a21=a[9],a22=a[10],a23=a[11],
-a30=a[12],a31=a[13],a32=a[14],a33=a[15],
-
-b00=a00*a11-a01*a10,
-b01=a00*a12-a02*a10,
-b02=a00*a13-a03*a10,
-b03=a01*a12-a02*a11,
-b04=a01*a13-a03*a11,
-b05=a02*a13-a03*a12,
-b06=a20*a31-a21*a30,
-b07=a20*a32-a22*a30,
-b08=a20*a33-a23*a30,
-b09=a21*a32-a22*a31,
-b10=a21*a33-a23*a31,
-b11=a22*a33-a23*a32;
-
-
-return b00*b11-b01*b10+b02*b09+b03*b08-b04*b07+b05*b06;
-};
-
-
-
-
-
-
-
-
-
-mat4.SIMD.multiply=function(out,a,b){
-var a0=SIMD.Float32x4.load(a,0);
-var a1=SIMD.Float32x4.load(a,4);
-var a2=SIMD.Float32x4.load(a,8);
-var a3=SIMD.Float32x4.load(a,12);
-
-var b0=SIMD.Float32x4.load(b,0);
-var out0=SIMD.Float32x4.add(
-SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b0,0,0,0,0),a0),
-SIMD.Float32x4.add(
-SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b0,1,1,1,1),a1),
-SIMD.Float32x4.add(
-SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b0,2,2,2,2),a2),
-SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b0,3,3,3,3),a3))));
-SIMD.Float32x4.store(out,0,out0);
-
-var b1=SIMD.Float32x4.load(b,4);
-var out1=SIMD.Float32x4.add(
-SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b1,0,0,0,0),a0),
-SIMD.Float32x4.add(
-SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b1,1,1,1,1),a1),
-SIMD.Float32x4.add(
-SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b1,2,2,2,2),a2),
-SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b1,3,3,3,3),a3))));
-SIMD.Float32x4.store(out,4,out1);
-
-var b2=SIMD.Float32x4.load(b,8);
-var out2=SIMD.Float32x4.add(
-SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b2,0,0,0,0),a0),
-SIMD.Float32x4.add(
-SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b2,1,1,1,1),a1),
-SIMD.Float32x4.add(
-SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b2,2,2,2,2),a2),
-SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b2,3,3,3,3),a3))));
-SIMD.Float32x4.store(out,8,out2);
-
-var b3=SIMD.Float32x4.load(b,12);
-var out3=SIMD.Float32x4.add(
-SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b3,0,0,0,0),a0),
-SIMD.Float32x4.add(
-SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b3,1,1,1,1),a1),
-SIMD.Float32x4.add(
-SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b3,2,2,2,2),a2),
-SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b3,3,3,3,3),a3))));
-SIMD.Float32x4.store(out,12,out3);
-
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat4.scalar.multiply=function(out,a,b){
-var a00=a[0],a01=a[1],a02=a[2],a03=a[3],
-a10=a[4],a11=a[5],a12=a[6],a13=a[7],
-a20=a[8],a21=a[9],a22=a[10],a23=a[11],
-a30=a[12],a31=a[13],a32=a[14],a33=a[15];
-
-
-var b0=b[0],b1=b[1],b2=b[2],b3=b[3];
-out[0]=b0*a00+b1*a10+b2*a20+b3*a30;
-out[1]=b0*a01+b1*a11+b2*a21+b3*a31;
-out[2]=b0*a02+b1*a12+b2*a22+b3*a32;
-out[3]=b0*a03+b1*a13+b2*a23+b3*a33;
-
-b0=b[4];b1=b[5];b2=b[6];b3=b[7];
-out[4]=b0*a00+b1*a10+b2*a20+b3*a30;
-out[5]=b0*a01+b1*a11+b2*a21+b3*a31;
-out[6]=b0*a02+b1*a12+b2*a22+b3*a32;
-out[7]=b0*a03+b1*a13+b2*a23+b3*a33;
-
-b0=b[8];b1=b[9];b2=b[10];b3=b[11];
-out[8]=b0*a00+b1*a10+b2*a20+b3*a30;
-out[9]=b0*a01+b1*a11+b2*a21+b3*a31;
-out[10]=b0*a02+b1*a12+b2*a22+b3*a32;
-out[11]=b0*a03+b1*a13+b2*a23+b3*a33;
-
-b0=b[12];b1=b[13];b2=b[14];b3=b[15];
-out[12]=b0*a00+b1*a10+b2*a20+b3*a30;
-out[13]=b0*a01+b1*a11+b2*a21+b3*a31;
-out[14]=b0*a02+b1*a12+b2*a22+b3*a32;
-out[15]=b0*a03+b1*a13+b2*a23+b3*a33;
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat4.multiply=glMatrix.USE_SIMD?mat4.SIMD.multiply:mat4.scalar.multiply;
-
-
-
-
-
-mat4.mul=mat4.multiply;
-
-
-
-
-
-
-
-
-
-mat4.scalar.translate=function(out,a,v){
-var x=v[0],y=v[1],z=v[2],
-a00,a01,a02,a03,
-a10,a11,a12,a13,
-a20,a21,a22,a23;
-
-if(a===out){
-out[12]=a[0]*x+a[4]*y+a[8]*z+a[12];
-out[13]=a[1]*x+a[5]*y+a[9]*z+a[13];
-out[14]=a[2]*x+a[6]*y+a[10]*z+a[14];
-out[15]=a[3]*x+a[7]*y+a[11]*z+a[15];
-}else{
-a00=a[0];a01=a[1];a02=a[2];a03=a[3];
-a10=a[4];a11=a[5];a12=a[6];a13=a[7];
-a20=a[8];a21=a[9];a22=a[10];a23=a[11];
-
-out[0]=a00;out[1]=a01;out[2]=a02;out[3]=a03;
-out[4]=a10;out[5]=a11;out[6]=a12;out[7]=a13;
-out[8]=a20;out[9]=a21;out[10]=a22;out[11]=a23;
-
-out[12]=a00*x+a10*y+a20*z+a[12];
-out[13]=a01*x+a11*y+a21*z+a[13];
-out[14]=a02*x+a12*y+a22*z+a[14];
-out[15]=a03*x+a13*y+a23*z+a[15];
-}
-
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat4.SIMD.translate=function(out,a,v){
-var a0=SIMD.Float32x4.load(a,0),
-a1=SIMD.Float32x4.load(a,4),
-a2=SIMD.Float32x4.load(a,8),
-a3=SIMD.Float32x4.load(a,12),
-vec=SIMD.Float32x4(v[0],v[1],v[2],0);
-
-if(a!==out){
-out[0]=a[0];out[1]=a[1];out[2]=a[2];out[3]=a[3];
-out[4]=a[4];out[5]=a[5];out[6]=a[6];out[7]=a[7];
-out[8]=a[8];out[9]=a[9];out[10]=a[10];out[11]=a[11];
-}
-
-a0=SIMD.Float32x4.mul(a0,SIMD.Float32x4.swizzle(vec,0,0,0,0));
-a1=SIMD.Float32x4.mul(a1,SIMD.Float32x4.swizzle(vec,1,1,1,1));
-a2=SIMD.Float32x4.mul(a2,SIMD.Float32x4.swizzle(vec,2,2,2,2));
-
-var t0=SIMD.Float32x4.add(a0,SIMD.Float32x4.add(a1,SIMD.Float32x4.add(a2,a3)));
-SIMD.Float32x4.store(out,12,t0);
-
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat4.translate=glMatrix.USE_SIMD?mat4.SIMD.translate:mat4.scalar.translate;
-
-
-
-
-
-
-
-
-
-mat4.scalar.scale=function(out,a,v){
-var x=v[0],y=v[1],z=v[2];
-
-out[0]=a[0]*x;
-out[1]=a[1]*x;
-out[2]=a[2]*x;
-out[3]=a[3]*x;
-out[4]=a[4]*y;
-out[5]=a[5]*y;
-out[6]=a[6]*y;
-out[7]=a[7]*y;
-out[8]=a[8]*z;
-out[9]=a[9]*z;
-out[10]=a[10]*z;
-out[11]=a[11]*z;
-out[12]=a[12];
-out[13]=a[13];
-out[14]=a[14];
-out[15]=a[15];
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat4.SIMD.scale=function(out,a,v){
-var a0,a1,a2;
-var vec=SIMD.Float32x4(v[0],v[1],v[2],0);
-
-a0=SIMD.Float32x4.load(a,0);
-SIMD.Float32x4.store(
-out,0,SIMD.Float32x4.mul(a0,SIMD.Float32x4.swizzle(vec,0,0,0,0)));
-
-a1=SIMD.Float32x4.load(a,4);
-SIMD.Float32x4.store(
-out,4,SIMD.Float32x4.mul(a1,SIMD.Float32x4.swizzle(vec,1,1,1,1)));
-
-a2=SIMD.Float32x4.load(a,8);
-SIMD.Float32x4.store(
-out,8,SIMD.Float32x4.mul(a2,SIMD.Float32x4.swizzle(vec,2,2,2,2)));
-
-out[12]=a[12];
-out[13]=a[13];
-out[14]=a[14];
-out[15]=a[15];
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat4.scale=glMatrix.USE_SIMD?mat4.SIMD.scale:mat4.scalar.scale;
-
-
-
-
-
-
-
-
-
-
-mat4.rotate=function(out,a,rad,axis){
-var x=axis[0],y=axis[1],z=axis[2],
-len=Math.sqrt(x*x+y*y+z*z),
-s,c,t,
-a00,a01,a02,a03,
-a10,a11,a12,a13,
-a20,a21,a22,a23,
-b00,b01,b02,
-b10,b11,b12,
-b20,b21,b22;
-
-if(Math.abs(len)<glMatrix.EPSILON){return null;}
-
-len=1/len;
-x*=len;
-y*=len;
-z*=len;
-
-s=Math.sin(rad);
-c=Math.cos(rad);
-t=1-c;
-
-a00=a[0];a01=a[1];a02=a[2];a03=a[3];
-a10=a[4];a11=a[5];a12=a[6];a13=a[7];
-a20=a[8];a21=a[9];a22=a[10];a23=a[11];
-
-
-b00=x*x*t+c;b01=y*x*t+z*s;b02=z*x*t-y*s;
-b10=x*y*t-z*s;b11=y*y*t+c;b12=z*y*t+x*s;
-b20=x*z*t+y*s;b21=y*z*t-x*s;b22=z*z*t+c;
-
-
-out[0]=a00*b00+a10*b01+a20*b02;
-out[1]=a01*b00+a11*b01+a21*b02;
-out[2]=a02*b00+a12*b01+a22*b02;
-out[3]=a03*b00+a13*b01+a23*b02;
-out[4]=a00*b10+a10*b11+a20*b12;
-out[5]=a01*b10+a11*b11+a21*b12;
-out[6]=a02*b10+a12*b11+a22*b12;
-out[7]=a03*b10+a13*b11+a23*b12;
-out[8]=a00*b20+a10*b21+a20*b22;
-out[9]=a01*b20+a11*b21+a21*b22;
-out[10]=a02*b20+a12*b21+a22*b22;
-out[11]=a03*b20+a13*b21+a23*b22;
-
-if(a!==out){
-out[12]=a[12];
-out[13]=a[13];
-out[14]=a[14];
-out[15]=a[15];
-}
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat4.scalar.rotateX=function(out,a,rad){
-var s=Math.sin(rad),
-c=Math.cos(rad),
-a10=a[4],
-a11=a[5],
-a12=a[6],
-a13=a[7],
-a20=a[8],
-a21=a[9],
-a22=a[10],
-a23=a[11];
-
-if(a!==out){
-out[0]=a[0];
-out[1]=a[1];
-out[2]=a[2];
-out[3]=a[3];
-out[12]=a[12];
-out[13]=a[13];
-out[14]=a[14];
-out[15]=a[15];
-}
-
-
-out[4]=a10*c+a20*s;
-out[5]=a11*c+a21*s;
-out[6]=a12*c+a22*s;
-out[7]=a13*c+a23*s;
-out[8]=a20*c-a10*s;
-out[9]=a21*c-a11*s;
-out[10]=a22*c-a12*s;
-out[11]=a23*c-a13*s;
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat4.SIMD.rotateX=function(out,a,rad){
-var s=SIMD.Float32x4.splat(Math.sin(rad)),
-c=SIMD.Float32x4.splat(Math.cos(rad));
-
-if(a!==out){
-out[0]=a[0];
-out[1]=a[1];
-out[2]=a[2];
-out[3]=a[3];
-out[12]=a[12];
-out[13]=a[13];
-out[14]=a[14];
-out[15]=a[15];
-}
-
-
-var a_1=SIMD.Float32x4.load(a,4);
-var a_2=SIMD.Float32x4.load(a,8);
-SIMD.Float32x4.store(out,4,
-SIMD.Float32x4.add(SIMD.Float32x4.mul(a_1,c),SIMD.Float32x4.mul(a_2,s)));
-SIMD.Float32x4.store(out,8,
-SIMD.Float32x4.sub(SIMD.Float32x4.mul(a_2,c),SIMD.Float32x4.mul(a_1,s)));
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat4.rotateX=glMatrix.USE_SIMD?mat4.SIMD.rotateX:mat4.scalar.rotateX;
-
-
-
-
-
-
-
-
-
-mat4.scalar.rotateY=function(out,a,rad){
-var s=Math.sin(rad),
-c=Math.cos(rad),
-a00=a[0],
-a01=a[1],
-a02=a[2],
-a03=a[3],
-a20=a[8],
-a21=a[9],
-a22=a[10],
-a23=a[11];
-
-if(a!==out){
-out[4]=a[4];
-out[5]=a[5];
-out[6]=a[6];
-out[7]=a[7];
-out[12]=a[12];
-out[13]=a[13];
-out[14]=a[14];
-out[15]=a[15];
-}
-
-
-out[0]=a00*c-a20*s;
-out[1]=a01*c-a21*s;
-out[2]=a02*c-a22*s;
-out[3]=a03*c-a23*s;
-out[8]=a00*s+a20*c;
-out[9]=a01*s+a21*c;
-out[10]=a02*s+a22*c;
-out[11]=a03*s+a23*c;
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat4.SIMD.rotateY=function(out,a,rad){
-var s=SIMD.Float32x4.splat(Math.sin(rad)),
-c=SIMD.Float32x4.splat(Math.cos(rad));
-
-if(a!==out){
-out[4]=a[4];
-out[5]=a[5];
-out[6]=a[6];
-out[7]=a[7];
-out[12]=a[12];
-out[13]=a[13];
-out[14]=a[14];
-out[15]=a[15];
-}
-
-
-var a_0=SIMD.Float32x4.load(a,0);
-var a_2=SIMD.Float32x4.load(a,8);
-SIMD.Float32x4.store(out,0,
-SIMD.Float32x4.sub(SIMD.Float32x4.mul(a_0,c),SIMD.Float32x4.mul(a_2,s)));
-SIMD.Float32x4.store(out,8,
-SIMD.Float32x4.add(SIMD.Float32x4.mul(a_0,s),SIMD.Float32x4.mul(a_2,c)));
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat4.rotateY=glMatrix.USE_SIMD?mat4.SIMD.rotateY:mat4.scalar.rotateY;
-
-
-
-
-
-
-
-
-
-mat4.scalar.rotateZ=function(out,a,rad){
-var s=Math.sin(rad),
-c=Math.cos(rad),
-a00=a[0],
-a01=a[1],
-a02=a[2],
-a03=a[3],
-a10=a[4],
-a11=a[5],
-a12=a[6],
-a13=a[7];
-
-if(a!==out){
-out[8]=a[8];
-out[9]=a[9];
-out[10]=a[10];
-out[11]=a[11];
-out[12]=a[12];
-out[13]=a[13];
-out[14]=a[14];
-out[15]=a[15];
-}
-
-
-out[0]=a00*c+a10*s;
-out[1]=a01*c+a11*s;
-out[2]=a02*c+a12*s;
-out[3]=a03*c+a13*s;
-out[4]=a10*c-a00*s;
-out[5]=a11*c-a01*s;
-out[6]=a12*c-a02*s;
-out[7]=a13*c-a03*s;
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat4.SIMD.rotateZ=function(out,a,rad){
-var s=SIMD.Float32x4.splat(Math.sin(rad)),
-c=SIMD.Float32x4.splat(Math.cos(rad));
-
-if(a!==out){
-out[8]=a[8];
-out[9]=a[9];
-out[10]=a[10];
-out[11]=a[11];
-out[12]=a[12];
-out[13]=a[13];
-out[14]=a[14];
-out[15]=a[15];
-}
-
-
-var a_0=SIMD.Float32x4.load(a,0);
-var a_1=SIMD.Float32x4.load(a,4);
-SIMD.Float32x4.store(out,0,
-SIMD.Float32x4.add(SIMD.Float32x4.mul(a_0,c),SIMD.Float32x4.mul(a_1,s)));
-SIMD.Float32x4.store(out,4,
-SIMD.Float32x4.sub(SIMD.Float32x4.mul(a_1,c),SIMD.Float32x4.mul(a_0,s)));
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat4.rotateZ=glMatrix.USE_SIMD?mat4.SIMD.rotateZ:mat4.scalar.rotateZ;
-
-
-
-
-
-
-
-
-
-
-
-
-mat4.fromTranslation=function(out,v){
-out[0]=1;
-out[1]=0;
-out[2]=0;
-out[3]=0;
-out[4]=0;
-out[5]=1;
-out[6]=0;
-out[7]=0;
-out[8]=0;
-out[9]=0;
-out[10]=1;
-out[11]=0;
-out[12]=v[0];
-out[13]=v[1];
-out[14]=v[2];
-out[15]=1;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-mat4.fromScaling=function(out,v){
-out[0]=v[0];
-out[1]=0;
-out[2]=0;
-out[3]=0;
-out[4]=0;
-out[5]=v[1];
-out[6]=0;
-out[7]=0;
-out[8]=0;
-out[9]=0;
-out[10]=v[2];
-out[11]=0;
-out[12]=0;
-out[13]=0;
-out[14]=0;
-out[15]=1;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-
-mat4.fromRotation=function(out,rad,axis){
-var x=axis[0],y=axis[1],z=axis[2],
-len=Math.sqrt(x*x+y*y+z*z),
-s,c,t;
-
-if(Math.abs(len)<glMatrix.EPSILON){return null;}
-
-len=1/len;
-x*=len;
-y*=len;
-z*=len;
-
-s=Math.sin(rad);
-c=Math.cos(rad);
-t=1-c;
-
-
-out[0]=x*x*t+c;
-out[1]=y*x*t+z*s;
-out[2]=z*x*t-y*s;
-out[3]=0;
-out[4]=x*y*t-z*s;
-out[5]=y*y*t+c;
-out[6]=z*y*t+x*s;
-out[7]=0;
-out[8]=x*z*t+y*s;
-out[9]=y*z*t-x*s;
-out[10]=z*z*t+c;
-out[11]=0;
-out[12]=0;
-out[13]=0;
-out[14]=0;
-out[15]=1;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-mat4.fromXRotation=function(out,rad){
-var s=Math.sin(rad),
-c=Math.cos(rad);
-
-
-out[0]=1;
-out[1]=0;
-out[2]=0;
-out[3]=0;
-out[4]=0;
-out[5]=c;
-out[6]=s;
-out[7]=0;
-out[8]=0;
-out[9]=-s;
-out[10]=c;
-out[11]=0;
-out[12]=0;
-out[13]=0;
-out[14]=0;
-out[15]=1;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-mat4.fromYRotation=function(out,rad){
-var s=Math.sin(rad),
-c=Math.cos(rad);
-
-
-out[0]=c;
-out[1]=0;
-out[2]=-s;
-out[3]=0;
-out[4]=0;
-out[5]=1;
-out[6]=0;
-out[7]=0;
-out[8]=s;
-out[9]=0;
-out[10]=c;
-out[11]=0;
-out[12]=0;
-out[13]=0;
-out[14]=0;
-out[15]=1;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-mat4.fromZRotation=function(out,rad){
-var s=Math.sin(rad),
-c=Math.cos(rad);
-
-
-out[0]=c;
-out[1]=s;
-out[2]=0;
-out[3]=0;
-out[4]=-s;
-out[5]=c;
-out[6]=0;
-out[7]=0;
-out[8]=0;
-out[9]=0;
-out[10]=1;
-out[11]=0;
-out[12]=0;
-out[13]=0;
-out[14]=0;
-out[15]=1;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-mat4.fromRotationTranslation=function(out,q,v){
-
-var x=q[0],y=q[1],z=q[2],w=q[3],
-x2=x+x,
-y2=y+y,
-z2=z+z,
-
-xx=x*x2,
-xy=x*y2,
-xz=x*z2,
-yy=y*y2,
-yz=y*z2,
-zz=z*z2,
-wx=w*x2,
-wy=w*y2,
-wz=w*z2;
-
-out[0]=1-(yy+zz);
-out[1]=xy+wz;
-out[2]=xz-wy;
-out[3]=0;
-out[4]=xy-wz;
-out[5]=1-(xx+zz);
-out[6]=yz+wx;
-out[7]=0;
-out[8]=xz+wy;
-out[9]=yz-wx;
-out[10]=1-(xx+yy);
-out[11]=0;
-out[12]=v[0];
-out[13]=v[1];
-out[14]=v[2];
-out[15]=1;
-
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-mat4.getTranslation=function(out,mat){
-out[0]=mat[12];
-out[1]=mat[13];
-out[2]=mat[14];
-
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-mat4.getRotation=function(out,mat){
-
-var trace=mat[0]+mat[5]+mat[10];
-var S=0;
-
-if(trace>0){
-S=Math.sqrt(trace+1.0)*2;
-out[3]=0.25*S;
-out[0]=(mat[6]-mat[9])/S;
-out[1]=(mat[8]-mat[2])/S;
-out[2]=(mat[1]-mat[4])/S;
-}else if(mat[0]>mat[5]&mat[0]>mat[10]){
-S=Math.sqrt(1.0+mat[0]-mat[5]-mat[10])*2;
-out[3]=(mat[6]-mat[9])/S;
-out[0]=0.25*S;
-out[1]=(mat[1]+mat[4])/S;
-out[2]=(mat[8]+mat[2])/S;
-}else if(mat[5]>mat[10]){
-S=Math.sqrt(1.0+mat[5]-mat[0]-mat[10])*2;
-out[3]=(mat[8]-mat[2])/S;
-out[0]=(mat[1]+mat[4])/S;
-out[1]=0.25*S;
-out[2]=(mat[6]+mat[9])/S;
-}else{
-S=Math.sqrt(1.0+mat[10]-mat[0]-mat[5])*2;
-out[3]=(mat[1]-mat[4])/S;
-out[0]=(mat[8]+mat[2])/S;
-out[1]=(mat[6]+mat[9])/S;
-out[2]=0.25*S;
-}
-
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-mat4.fromRotationTranslationScale=function(out,q,v,s){
-
-var x=q[0],y=q[1],z=q[2],w=q[3],
-x2=x+x,
-y2=y+y,
-z2=z+z,
-
-xx=x*x2,
-xy=x*y2,
-xz=x*z2,
-yy=y*y2,
-yz=y*z2,
-zz=z*z2,
-wx=w*x2,
-wy=w*y2,
-wz=w*z2,
-sx=s[0],
-sy=s[1],
-sz=s[2];
-
-out[0]=(1-(yy+zz))*sx;
-out[1]=(xy+wz)*sx;
-out[2]=(xz-wy)*sx;
-out[3]=0;
-out[4]=(xy-wz)*sy;
-out[5]=(1-(xx+zz))*sy;
-out[6]=(yz+wx)*sy;
-out[7]=0;
-out[8]=(xz+wy)*sz;
-out[9]=(yz-wx)*sz;
-out[10]=(1-(xx+yy))*sz;
-out[11]=0;
-out[12]=v[0];
-out[13]=v[1];
-out[14]=v[2];
-out[15]=1;
-
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-mat4.fromRotationTranslationScaleOrigin=function(out,q,v,s,o){
-
-var x=q[0],y=q[1],z=q[2],w=q[3],
-x2=x+x,
-y2=y+y,
-z2=z+z,
-
-xx=x*x2,
-xy=x*y2,
-xz=x*z2,
-yy=y*y2,
-yz=y*z2,
-zz=z*z2,
-wx=w*x2,
-wy=w*y2,
-wz=w*z2,
-
-sx=s[0],
-sy=s[1],
-sz=s[2],
-
-ox=o[0],
-oy=o[1],
-oz=o[2];
-
-out[0]=(1-(yy+zz))*sx;
-out[1]=(xy+wz)*sx;
-out[2]=(xz-wy)*sx;
-out[3]=0;
-out[4]=(xy-wz)*sy;
-out[5]=(1-(xx+zz))*sy;
-out[6]=(yz+wx)*sy;
-out[7]=0;
-out[8]=(xz+wy)*sz;
-out[9]=(yz-wx)*sz;
-out[10]=(1-(xx+yy))*sz;
-out[11]=0;
-out[12]=v[0]+ox-(out[0]*ox+out[4]*oy+out[8]*oz);
-out[13]=v[1]+oy-(out[1]*ox+out[5]*oy+out[9]*oz);
-out[14]=v[2]+oz-(out[2]*ox+out[6]*oy+out[10]*oz);
-out[15]=1;
-
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat4.fromQuat=function(out,q){
-var x=q[0],y=q[1],z=q[2],w=q[3],
-x2=x+x,
-y2=y+y,
-z2=z+z,
-
-xx=x*x2,
-yx=y*x2,
-yy=y*y2,
-zx=z*x2,
-zy=z*y2,
-zz=z*z2,
-wx=w*x2,
-wy=w*y2,
-wz=w*z2;
-
-out[0]=1-yy-zz;
-out[1]=yx+wz;
-out[2]=zx-wy;
-out[3]=0;
-
-out[4]=yx-wz;
-out[5]=1-xx-zz;
-out[6]=zy+wx;
-out[7]=0;
-
-out[8]=zx+wy;
-out[9]=zy-wx;
-out[10]=1-xx-yy;
-out[11]=0;
-
-out[12]=0;
-out[13]=0;
-out[14]=0;
-out[15]=1;
-
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-
-mat4.frustum=function(out,left,right,bottom,top,near,far){
-var rl=1/(right-left),
-tb=1/(top-bottom),
-nf=1/(near-far);
-out[0]=near*2*rl;
-out[1]=0;
-out[2]=0;
-out[3]=0;
-out[4]=0;
-out[5]=near*2*tb;
-out[6]=0;
-out[7]=0;
-out[8]=(right+left)*rl;
-out[9]=(top+bottom)*tb;
-out[10]=(far+near)*nf;
-out[11]=-1;
-out[12]=0;
-out[13]=0;
-out[14]=far*near*2*nf;
-out[15]=0;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-mat4.perspective=function(out,fovy,aspect,near,far){
-var f=1.0/Math.tan(fovy/2),
-nf=1/(near-far);
-out[0]=f/aspect;
-out[1]=0;
-out[2]=0;
-out[3]=0;
-out[4]=0;
-out[5]=f;
-out[6]=0;
-out[7]=0;
-out[8]=0;
-out[9]=0;
-out[10]=(far+near)*nf;
-out[11]=-1;
-out[12]=0;
-out[13]=0;
-out[14]=2*far*near*nf;
-out[15]=0;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-mat4.perspectiveFromFieldOfView=function(out,fov,near,far){
-var upTan=Math.tan(fov.upDegrees*Math.PI/180.0),
-downTan=Math.tan(fov.downDegrees*Math.PI/180.0),
-leftTan=Math.tan(fov.leftDegrees*Math.PI/180.0),
-rightTan=Math.tan(fov.rightDegrees*Math.PI/180.0),
-xScale=2.0/(leftTan+rightTan),
-yScale=2.0/(upTan+downTan);
-
-out[0]=xScale;
-out[1]=0.0;
-out[2]=0.0;
-out[3]=0.0;
-out[4]=0.0;
-out[5]=yScale;
-out[6]=0.0;
-out[7]=0.0;
-out[8]=-((leftTan-rightTan)*xScale*0.5);
-out[9]=(upTan-downTan)*yScale*0.5;
-out[10]=far/(near-far);
-out[11]=-1.0;
-out[12]=0.0;
-out[13]=0.0;
-out[14]=far*near/(near-far);
-out[15]=0.0;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-
-mat4.ortho=function(out,left,right,bottom,top,near,far){
-var lr=1/(left-right),
-bt=1/(bottom-top),
-nf=1/(near-far);
-out[0]=-2*lr;
-out[1]=0;
-out[2]=0;
-out[3]=0;
-out[4]=0;
-out[5]=-2*bt;
-out[6]=0;
-out[7]=0;
-out[8]=0;
-out[9]=0;
-out[10]=2*nf;
-out[11]=0;
-out[12]=(left+right)*lr;
-out[13]=(top+bottom)*bt;
-out[14]=(far+near)*nf;
-out[15]=1;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-mat4.lookAt=function(out,eye,center,up){
-var x0,x1,x2,y0,y1,y2,z0,z1,z2,len,
-eyex=eye[0],
-eyey=eye[1],
-eyez=eye[2],
-upx=up[0],
-upy=up[1],
-upz=up[2],
-centerx=center[0],
-centery=center[1],
-centerz=center[2];
-
-if(Math.abs(eyex-centerx)<glMatrix.EPSILON&&
-Math.abs(eyey-centery)<glMatrix.EPSILON&&
-Math.abs(eyez-centerz)<glMatrix.EPSILON){
-return mat4.identity(out);
-}
-
-z0=eyex-centerx;
-z1=eyey-centery;
-z2=eyez-centerz;
-
-len=1/Math.sqrt(z0*z0+z1*z1+z2*z2);
-z0*=len;
-z1*=len;
-z2*=len;
-
-x0=upy*z2-upz*z1;
-x1=upz*z0-upx*z2;
-x2=upx*z1-upy*z0;
-len=Math.sqrt(x0*x0+x1*x1+x2*x2);
-if(!len){
-x0=0;
-x1=0;
-x2=0;
-}else{
-len=1/len;
-x0*=len;
-x1*=len;
-x2*=len;
-}
-
-y0=z1*x2-z2*x1;
-y1=z2*x0-z0*x2;
-y2=z0*x1-z1*x0;
-
-len=Math.sqrt(y0*y0+y1*y1+y2*y2);
-if(!len){
-y0=0;
-y1=0;
-y2=0;
-}else{
-len=1/len;
-y0*=len;
-y1*=len;
-y2*=len;
-}
-
-out[0]=x0;
-out[1]=y0;
-out[2]=z0;
-out[3]=0;
-out[4]=x1;
-out[5]=y1;
-out[6]=z1;
-out[7]=0;
-out[8]=x2;
-out[9]=y2;
-out[10]=z2;
-out[11]=0;
-out[12]=-(x0*eyex+x1*eyey+x2*eyez);
-out[13]=-(y0*eyex+y1*eyey+y2*eyez);
-out[14]=-(z0*eyex+z1*eyey+z2*eyez);
-out[15]=1;
-
-return out;
-};
-
-
-
-
-
-
-
-mat4.str=function(a){
-return'mat4('+a[0]+', '+a[1]+', '+a[2]+', '+a[3]+', '+
-a[4]+', '+a[5]+', '+a[6]+', '+a[7]+', '+
-a[8]+', '+a[9]+', '+a[10]+', '+a[11]+', '+
-a[12]+', '+a[13]+', '+a[14]+', '+a[15]+')';
-};
-
-
-
-
-
-
-
-mat4.frob=function(a){
-return Math.sqrt(Math.pow(a[0],2)+Math.pow(a[1],2)+Math.pow(a[2],2)+Math.pow(a[3],2)+Math.pow(a[4],2)+Math.pow(a[5],2)+Math.pow(a[6],2)+Math.pow(a[7],2)+Math.pow(a[8],2)+Math.pow(a[9],2)+Math.pow(a[10],2)+Math.pow(a[11],2)+Math.pow(a[12],2)+Math.pow(a[13],2)+Math.pow(a[14],2)+Math.pow(a[15],2));
-};
-
-
-
-
-
-
-
-
-
-mat4.add=function(out,a,b){
-out[0]=a[0]+b[0];
-out[1]=a[1]+b[1];
-out[2]=a[2]+b[2];
-out[3]=a[3]+b[3];
-out[4]=a[4]+b[4];
-out[5]=a[5]+b[5];
-out[6]=a[6]+b[6];
-out[7]=a[7]+b[7];
-out[8]=a[8]+b[8];
-out[9]=a[9]+b[9];
-out[10]=a[10]+b[10];
-out[11]=a[11]+b[11];
-out[12]=a[12]+b[12];
-out[13]=a[13]+b[13];
-out[14]=a[14]+b[14];
-out[15]=a[15]+b[15];
-return out;
-};
-
-
-
-
-
-
-
-
-
-mat4.subtract=function(out,a,b){
-out[0]=a[0]-b[0];
-out[1]=a[1]-b[1];
-out[2]=a[2]-b[2];
-out[3]=a[3]-b[3];
-out[4]=a[4]-b[4];
-out[5]=a[5]-b[5];
-out[6]=a[6]-b[6];
-out[7]=a[7]-b[7];
-out[8]=a[8]-b[8];
-out[9]=a[9]-b[9];
-out[10]=a[10]-b[10];
-out[11]=a[11]-b[11];
-out[12]=a[12]-b[12];
-out[13]=a[13]-b[13];
-out[14]=a[14]-b[14];
-out[15]=a[15]-b[15];
-return out;
-};
-
-
-
-
-
-mat4.sub=mat4.subtract;
-
-
-
-
-
-
-
-
-
-mat4.multiplyScalar=function(out,a,b){
-out[0]=a[0]*b;
-out[1]=a[1]*b;
-out[2]=a[2]*b;
-out[3]=a[3]*b;
-out[4]=a[4]*b;
-out[5]=a[5]*b;
-out[6]=a[6]*b;
-out[7]=a[7]*b;
-out[8]=a[8]*b;
-out[9]=a[9]*b;
-out[10]=a[10]*b;
-out[11]=a[11]*b;
-out[12]=a[12]*b;
-out[13]=a[13]*b;
-out[14]=a[14]*b;
-out[15]=a[15]*b;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-mat4.multiplyScalarAndAdd=function(out,a,b,scale){
-out[0]=a[0]+b[0]*scale;
-out[1]=a[1]+b[1]*scale;
-out[2]=a[2]+b[2]*scale;
-out[3]=a[3]+b[3]*scale;
-out[4]=a[4]+b[4]*scale;
-out[5]=a[5]+b[5]*scale;
-out[6]=a[6]+b[6]*scale;
-out[7]=a[7]+b[7]*scale;
-out[8]=a[8]+b[8]*scale;
-out[9]=a[9]+b[9]*scale;
-out[10]=a[10]+b[10]*scale;
-out[11]=a[11]+b[11]*scale;
-out[12]=a[12]+b[12]*scale;
-out[13]=a[13]+b[13]*scale;
-out[14]=a[14]+b[14]*scale;
-out[15]=a[15]+b[15]*scale;
-return out;
-};
-
-
-
-
-
-
-
-
-mat4.exactEquals=function(a,b){
-return a[0]===b[0]&&a[1]===b[1]&&a[2]===b[2]&&a[3]===b[3]&&
-a[4]===b[4]&&a[5]===b[5]&&a[6]===b[6]&&a[7]===b[7]&&
-a[8]===b[8]&&a[9]===b[9]&&a[10]===b[10]&&a[11]===b[11]&&
-a[12]===b[12]&&a[13]===b[13]&&a[14]===b[14]&&a[15]===b[15];
-};
-
-
-
-
-
-
-
-
-mat4.equals=function(a,b){
-var a0=a[0],a1=a[1],a2=a[2],a3=a[3],
-a4=a[4],a5=a[5],a6=a[6],a7=a[7],
-a8=a[8],a9=a[9],a10=a[10],a11=a[11],
-a12=a[12],a13=a[13],a14=a[14],a15=a[15];
-
-var b0=b[0],b1=b[1],b2=b[2],b3=b[3],
-b4=b[4],b5=b[5],b6=b[6],b7=b[7],
-b8=b[8],b9=b[9],b10=b[10],b11=b[11],
-b12=b[12],b13=b[13],b14=b[14],b15=b[15];
-
-return Math.abs(a0-b0)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a0),Math.abs(b0))&&
-Math.abs(a1-b1)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a1),Math.abs(b1))&&
-Math.abs(a2-b2)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a2),Math.abs(b2))&&
-Math.abs(a3-b3)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a3),Math.abs(b3))&&
-Math.abs(a4-b4)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a4),Math.abs(b4))&&
-Math.abs(a5-b5)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a5),Math.abs(b5))&&
-Math.abs(a6-b6)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a6),Math.abs(b6))&&
-Math.abs(a7-b7)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a7),Math.abs(b7))&&
-Math.abs(a8-b8)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a8),Math.abs(b8))&&
-Math.abs(a9-b9)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a9),Math.abs(b9))&&
-Math.abs(a10-b10)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a10),Math.abs(b10))&&
-Math.abs(a11-b11)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a11),Math.abs(b11))&&
-Math.abs(a12-b12)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a12),Math.abs(b12))&&
-Math.abs(a13-b13)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a13),Math.abs(b13))&&
-Math.abs(a14-b14)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a14),Math.abs(b14))&&
-Math.abs(a15-b15)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a15),Math.abs(b15));
-};
-
-
-
-module.exports=mat4;
-
-},{"./common.js":237}],242:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-var glMatrix=require("./common.js");
-var mat3=require("./mat3.js");
-var vec3=require("./vec3.js");
-var vec4=require("./vec4.js");
-
-
-
-
-
-var quat={};
-
-
-
-
-
-
-quat.create=function(){
-var out=new glMatrix.ARRAY_TYPE(4);
-out[0]=0;
-out[1]=0;
-out[2]=0;
-out[3]=1;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-quat.rotationTo=function(){
-var tmpvec3=vec3.create();
-var xUnitVec3=vec3.fromValues(1,0,0);
-var yUnitVec3=vec3.fromValues(0,1,0);
-
-return function(out,a,b){
-var dot=vec3.dot(a,b);
-if(dot<-0.999999){
-vec3.cross(tmpvec3,xUnitVec3,a);
-if(vec3.length(tmpvec3)<0.000001)
-vec3.cross(tmpvec3,yUnitVec3,a);
-vec3.normalize(tmpvec3,tmpvec3);
-quat.setAxisAngle(out,tmpvec3,Math.PI);
-return out;
-}else if(dot>0.999999){
-out[0]=0;
-out[1]=0;
-out[2]=0;
-out[3]=1;
-return out;
-}else{
-vec3.cross(tmpvec3,a,b);
-out[0]=tmpvec3[0];
-out[1]=tmpvec3[1];
-out[2]=tmpvec3[2];
-out[3]=1+dot;
-return quat.normalize(out,out);
-}
-};
-}();
-
-
-
-
-
-
-
-
-
-
-
-quat.setAxes=function(){
-var matr=mat3.create();
-
-return function(out,view,right,up){
-matr[0]=right[0];
-matr[3]=right[1];
-matr[6]=right[2];
-
-matr[1]=up[0];
-matr[4]=up[1];
-matr[7]=up[2];
-
-matr[2]=-view[0];
-matr[5]=-view[1];
-matr[8]=-view[2];
-
-return quat.normalize(out,quat.fromMat3(out,matr));
-};
-}();
-
-
-
-
-
-
-
-
-quat.clone=vec4.clone;
-
-
-
-
-
-
-
-
-
-
-
-quat.fromValues=vec4.fromValues;
-
-
-
-
-
-
-
-
-
-quat.copy=vec4.copy;
-
-
-
-
-
-
-
-
-
-
-
-
-quat.set=vec4.set;
-
-
-
-
-
-
-
-quat.identity=function(out){
-out[0]=0;
-out[1]=0;
-out[2]=0;
-out[3]=1;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-quat.setAxisAngle=function(out,axis,rad){
-rad=rad*0.5;
-var s=Math.sin(rad);
-out[0]=s*axis[0];
-out[1]=s*axis[1];
-out[2]=s*axis[2];
-out[3]=Math.cos(rad);
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-quat.getAxisAngle=function(out_axis,q){
-var rad=Math.acos(q[3])*2.0;
-var s=Math.sin(rad/2.0);
-if(s!=0.0){
-out_axis[0]=q[0]/s;
-out_axis[1]=q[1]/s;
-out_axis[2]=q[2]/s;
-}else{
-
-out_axis[0]=1;
-out_axis[1]=0;
-out_axis[2]=0;
-}
-return rad;
-};
-
-
-
-
-
-
-
-
-
-
-quat.add=vec4.add;
-
-
-
-
-
-
-
-
-
-quat.multiply=function(out,a,b){
-var ax=a[0],ay=a[1],az=a[2],aw=a[3],
-bx=b[0],by=b[1],bz=b[2],bw=b[3];
-
-out[0]=ax*bw+aw*bx+ay*bz-az*by;
-out[1]=ay*bw+aw*by+az*bx-ax*bz;
-out[2]=az*bw+aw*bz+ax*by-ay*bx;
-out[3]=aw*bw-ax*bx-ay*by-az*bz;
-return out;
-};
-
-
-
-
-
-quat.mul=quat.multiply;
-
-
-
-
-
-
-
-
-
-
-quat.scale=vec4.scale;
-
-
-
-
-
-
-
-
-
-quat.rotateX=function(out,a,rad){
-rad*=0.5;
-
-var ax=a[0],ay=a[1],az=a[2],aw=a[3],
-bx=Math.sin(rad),bw=Math.cos(rad);
-
-out[0]=ax*bw+aw*bx;
-out[1]=ay*bw+az*bx;
-out[2]=az*bw-ay*bx;
-out[3]=aw*bw-ax*bx;
-return out;
-};
-
-
-
-
-
-
-
-
-
-quat.rotateY=function(out,a,rad){
-rad*=0.5;
-
-var ax=a[0],ay=a[1],az=a[2],aw=a[3],
-by=Math.sin(rad),bw=Math.cos(rad);
-
-out[0]=ax*bw-az*by;
-out[1]=ay*bw+aw*by;
-out[2]=az*bw+ax*by;
-out[3]=aw*bw-ay*by;
-return out;
-};
-
-
-
-
-
-
-
-
-
-quat.rotateZ=function(out,a,rad){
-rad*=0.5;
-
-var ax=a[0],ay=a[1],az=a[2],aw=a[3],
-bz=Math.sin(rad),bw=Math.cos(rad);
-
-out[0]=ax*bw+ay*bz;
-out[1]=ay*bw-ax*bz;
-out[2]=az*bw+aw*bz;
-out[3]=aw*bw-az*bz;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-quat.calculateW=function(out,a){
-var x=a[0],y=a[1],z=a[2];
-
-out[0]=x;
-out[1]=y;
-out[2]=z;
-out[3]=Math.sqrt(Math.abs(1.0-x*x-y*y-z*z));
-return out;
-};
-
-
-
-
-
-
-
-
-
-quat.dot=vec4.dot;
-
-
-
-
-
-
-
-
-
-
-
-quat.lerp=vec4.lerp;
-
-
-
-
-
-
-
-
-
-
-quat.slerp=function(out,a,b,t){
-
-
-
-var ax=a[0],ay=a[1],az=a[2],aw=a[3],
-bx=b[0],by=b[1],bz=b[2],bw=b[3];
-
-var omega,cosom,sinom,scale0,scale1;
-
-
-cosom=ax*bx+ay*by+az*bz+aw*bw;
-
-if(cosom<0.0){
-cosom=-cosom;
-bx=-bx;
-by=-by;
-bz=-bz;
-bw=-bw;
-}
-
-if(1.0-cosom>0.000001){
-
-omega=Math.acos(cosom);
-sinom=Math.sin(omega);
-scale0=Math.sin((1.0-t)*omega)/sinom;
-scale1=Math.sin(t*omega)/sinom;
-}else{
-
-
-scale0=1.0-t;
-scale1=t;
-}
-
-out[0]=scale0*ax+scale1*bx;
-out[1]=scale0*ay+scale1*by;
-out[2]=scale0*az+scale1*bz;
-out[3]=scale0*aw+scale1*bw;
-
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-quat.sqlerp=function(){
-var temp1=quat.create();
-var temp2=quat.create();
-
-return function(out,a,b,c,d,t){
-quat.slerp(temp1,a,d,t);
-quat.slerp(temp2,b,c,t);
-quat.slerp(out,temp1,temp2,2*t*(1-t));
-
-return out;
-};
-}();
-
-
-
-
-
-
-
-
-quat.invert=function(out,a){
-var a0=a[0],a1=a[1],a2=a[2],a3=a[3],
-dot=a0*a0+a1*a1+a2*a2+a3*a3,
-invDot=dot?1.0/dot:0;
-
-
-
-out[0]=-a0*invDot;
-out[1]=-a1*invDot;
-out[2]=-a2*invDot;
-out[3]=a3*invDot;
-return out;
-};
-
-
-
-
-
-
-
-
-
-quat.conjugate=function(out,a){
-out[0]=-a[0];
-out[1]=-a[1];
-out[2]=-a[2];
-out[3]=a[3];
-return out;
-};
-
-
-
-
-
-
-
-
-quat.length=vec4.length;
-
-
-
-
-
-quat.len=quat.length;
-
-
-
-
-
-
-
-
-quat.squaredLength=vec4.squaredLength;
-
-
-
-
-
-quat.sqrLen=quat.squaredLength;
-
-
-
-
-
-
-
-
-
-quat.normalize=vec4.normalize;
-
-
-
-
-
-
-
-
-
-
-
-
-quat.fromMat3=function(out,m){
-
-
-var fTrace=m[0]+m[4]+m[8];
-var fRoot;
-
-if(fTrace>0.0){
-
-fRoot=Math.sqrt(fTrace+1.0);
-out[3]=0.5*fRoot;
-fRoot=0.5/fRoot;
-out[0]=(m[5]-m[7])*fRoot;
-out[1]=(m[6]-m[2])*fRoot;
-out[2]=(m[1]-m[3])*fRoot;
-}else{
-
-var i=0;
-if(m[4]>m[0])
-i=1;
-if(m[8]>m[i*3+i])
-i=2;
-var j=(i+1)%3;
-var k=(i+2)%3;
-
-fRoot=Math.sqrt(m[i*3+i]-m[j*3+j]-m[k*3+k]+1.0);
-out[i]=0.5*fRoot;
-fRoot=0.5/fRoot;
-out[3]=(m[j*3+k]-m[k*3+j])*fRoot;
-out[j]=(m[j*3+i]+m[i*3+j])*fRoot;
-out[k]=(m[k*3+i]+m[i*3+k])*fRoot;
-}
-
-return out;
-};
-
-
-
-
-
-
-
-quat.str=function(a){
-return'quat('+a[0]+', '+a[1]+', '+a[2]+', '+a[3]+')';
-};
-
-
-
-
-
-
-
-
-quat.exactEquals=vec4.exactEquals;
-
-
-
-
-
-
-
-
-quat.equals=vec4.equals;
-
-module.exports=quat;
-
-},{"./common.js":237,"./mat3.js":240,"./vec3.js":244,"./vec4.js":245}],243:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-var glMatrix=require("./common.js");
-
-
-
-
-
-var vec2={};
-
-
-
-
-
-
-vec2.create=function(){
-var out=new glMatrix.ARRAY_TYPE(2);
-out[0]=0;
-out[1]=0;
-return out;
-};
-
-
-
-
-
-
-
-vec2.clone=function(a){
-var out=new glMatrix.ARRAY_TYPE(2);
-out[0]=a[0];
-out[1]=a[1];
-return out;
-};
-
-
-
-
-
-
-
-
-vec2.fromValues=function(x,y){
-var out=new glMatrix.ARRAY_TYPE(2);
-out[0]=x;
-out[1]=y;
-return out;
-};
-
-
-
-
-
-
-
-
-vec2.copy=function(out,a){
-out[0]=a[0];
-out[1]=a[1];
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec2.set=function(out,x,y){
-out[0]=x;
-out[1]=y;
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec2.add=function(out,a,b){
-out[0]=a[0]+b[0];
-out[1]=a[1]+b[1];
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec2.subtract=function(out,a,b){
-out[0]=a[0]-b[0];
-out[1]=a[1]-b[1];
-return out;
-};
-
-
-
-
-
-vec2.sub=vec2.subtract;
-
-
-
-
-
-
-
-
-
-vec2.multiply=function(out,a,b){
-out[0]=a[0]*b[0];
-out[1]=a[1]*b[1];
-return out;
-};
-
-
-
-
-
-vec2.mul=vec2.multiply;
-
-
-
-
-
-
-
-
-
-vec2.divide=function(out,a,b){
-out[0]=a[0]/b[0];
-out[1]=a[1]/b[1];
-return out;
-};
-
-
-
-
-
-vec2.div=vec2.divide;
-
-
-
-
-
-
-
-
-vec2.ceil=function(out,a){
-out[0]=Math.ceil(a[0]);
-out[1]=Math.ceil(a[1]);
-return out;
-};
-
-
-
-
-
-
-
-
-vec2.floor=function(out,a){
-out[0]=Math.floor(a[0]);
-out[1]=Math.floor(a[1]);
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec2.min=function(out,a,b){
-out[0]=Math.min(a[0],b[0]);
-out[1]=Math.min(a[1],b[1]);
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec2.max=function(out,a,b){
-out[0]=Math.max(a[0],b[0]);
-out[1]=Math.max(a[1],b[1]);
-return out;
-};
-
-
-
-
-
-
-
-
-vec2.round=function(out,a){
-out[0]=Math.round(a[0]);
-out[1]=Math.round(a[1]);
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec2.scale=function(out,a,b){
-out[0]=a[0]*b;
-out[1]=a[1]*b;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-vec2.scaleAndAdd=function(out,a,b,scale){
-out[0]=a[0]+b[0]*scale;
-out[1]=a[1]+b[1]*scale;
-return out;
-};
-
-
-
-
-
-
-
-
-vec2.distance=function(a,b){
-var x=b[0]-a[0],
-y=b[1]-a[1];
-return Math.sqrt(x*x+y*y);
-};
-
-
-
-
-
-vec2.dist=vec2.distance;
-
-
-
-
-
-
-
-
-vec2.squaredDistance=function(a,b){
-var x=b[0]-a[0],
-y=b[1]-a[1];
-return x*x+y*y;
-};
-
-
-
-
-
-vec2.sqrDist=vec2.squaredDistance;
-
-
-
-
-
-
-
-vec2.length=function(a){
-var x=a[0],
-y=a[1];
-return Math.sqrt(x*x+y*y);
-};
-
-
-
-
-
-vec2.len=vec2.length;
-
-
-
-
-
-
-
-vec2.squaredLength=function(a){
-var x=a[0],
-y=a[1];
-return x*x+y*y;
-};
-
-
-
-
-
-vec2.sqrLen=vec2.squaredLength;
-
-
-
-
-
-
-
-
-vec2.negate=function(out,a){
-out[0]=-a[0];
-out[1]=-a[1];
-return out;
-};
-
-
-
-
-
-
-
-
-vec2.inverse=function(out,a){
-out[0]=1.0/a[0];
-out[1]=1.0/a[1];
-return out;
-};
-
-
-
-
-
-
-
-
-vec2.normalize=function(out,a){
-var x=a[0],
-y=a[1];
-var len=x*x+y*y;
-if(len>0){
-
-len=1/Math.sqrt(len);
-out[0]=a[0]*len;
-out[1]=a[1]*len;
-}
-return out;
-};
-
-
-
-
-
-
-
-
-vec2.dot=function(a,b){
-return a[0]*b[0]+a[1]*b[1];
-};
-
-
-
-
-
-
-
-
-
-
-vec2.cross=function(out,a,b){
-var z=a[0]*b[1]-a[1]*b[0];
-out[0]=out[1]=0;
-out[2]=z;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-vec2.lerp=function(out,a,b,t){
-var ax=a[0],
-ay=a[1];
-out[0]=ax+t*(b[0]-ax);
-out[1]=ay+t*(b[1]-ay);
-return out;
-};
-
-
-
-
-
-
-
-
-vec2.random=function(out,scale){
-scale=scale||1.0;
-var r=glMatrix.RANDOM()*2.0*Math.PI;
-out[0]=Math.cos(r)*scale;
-out[1]=Math.sin(r)*scale;
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec2.transformMat2=function(out,a,m){
-var x=a[0],
-y=a[1];
-out[0]=m[0]*x+m[2]*y;
-out[1]=m[1]*x+m[3]*y;
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec2.transformMat2d=function(out,a,m){
-var x=a[0],
-y=a[1];
-out[0]=m[0]*x+m[2]*y+m[4];
-out[1]=m[1]*x+m[3]*y+m[5];
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-vec2.transformMat3=function(out,a,m){
-var x=a[0],
-y=a[1];
-out[0]=m[0]*x+m[3]*y+m[6];
-out[1]=m[1]*x+m[4]*y+m[7];
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-vec2.transformMat4=function(out,a,m){
-var x=a[0],
-y=a[1];
-out[0]=m[0]*x+m[4]*y+m[12];
-out[1]=m[1]*x+m[5]*y+m[13];
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-
-vec2.forEach=function(){
-var vec=vec2.create();
-
-return function(a,stride,offset,count,fn,arg){
-var i,l;
-if(!stride){
-stride=2;
-}
-
-if(!offset){
-offset=0;
-}
-
-if(count){
-l=Math.min(count*stride+offset,a.length);
-}else{
-l=a.length;
-}
-
-for(i=offset;i<l;i+=stride){
-vec[0]=a[i];vec[1]=a[i+1];
-fn(vec,vec,arg);
-a[i]=vec[0];a[i+1]=vec[1];
-}
-
-return a;
-};
-}();
-
-
-
-
-
-
-
-vec2.str=function(a){
-return'vec2('+a[0]+', '+a[1]+')';
-};
-
-
-
-
-
-
-
-
-vec2.exactEquals=function(a,b){
-return a[0]===b[0]&&a[1]===b[1];
-};
-
-
-
-
-
-
-
-
-vec2.equals=function(a,b){
-var a0=a[0],a1=a[1];
-var b0=b[0],b1=b[1];
-return Math.abs(a0-b0)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a0),Math.abs(b0))&&
-Math.abs(a1-b1)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a1),Math.abs(b1));
-};
-
-module.exports=vec2;
-
-},{"./common.js":237}],244:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-var glMatrix=require("./common.js");
-
-
-
-
-
-var vec3={};
-
-
-
-
-
-
-vec3.create=function(){
-var out=new glMatrix.ARRAY_TYPE(3);
-out[0]=0;
-out[1]=0;
-out[2]=0;
-return out;
-};
-
-
-
-
-
-
-
-vec3.clone=function(a){
-var out=new glMatrix.ARRAY_TYPE(3);
-out[0]=a[0];
-out[1]=a[1];
-out[2]=a[2];
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec3.fromValues=function(x,y,z){
-var out=new glMatrix.ARRAY_TYPE(3);
-out[0]=x;
-out[1]=y;
-out[2]=z;
-return out;
-};
-
-
-
-
-
-
-
-
-vec3.copy=function(out,a){
-out[0]=a[0];
-out[1]=a[1];
-out[2]=a[2];
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-vec3.set=function(out,x,y,z){
-out[0]=x;
-out[1]=y;
-out[2]=z;
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec3.add=function(out,a,b){
-out[0]=a[0]+b[0];
-out[1]=a[1]+b[1];
-out[2]=a[2]+b[2];
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec3.subtract=function(out,a,b){
-out[0]=a[0]-b[0];
-out[1]=a[1]-b[1];
-out[2]=a[2]-b[2];
-return out;
-};
-
-
-
-
-
-vec3.sub=vec3.subtract;
-
-
-
-
-
-
-
-
-
-vec3.multiply=function(out,a,b){
-out[0]=a[0]*b[0];
-out[1]=a[1]*b[1];
-out[2]=a[2]*b[2];
-return out;
-};
-
-
-
-
-
-vec3.mul=vec3.multiply;
-
-
-
-
-
-
-
-
-
-vec3.divide=function(out,a,b){
-out[0]=a[0]/b[0];
-out[1]=a[1]/b[1];
-out[2]=a[2]/b[2];
-return out;
-};
-
-
-
-
-
-vec3.div=vec3.divide;
-
-
-
-
-
-
-
-
-vec3.ceil=function(out,a){
-out[0]=Math.ceil(a[0]);
-out[1]=Math.ceil(a[1]);
-out[2]=Math.ceil(a[2]);
-return out;
-};
-
-
-
-
-
-
-
-
-vec3.floor=function(out,a){
-out[0]=Math.floor(a[0]);
-out[1]=Math.floor(a[1]);
-out[2]=Math.floor(a[2]);
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec3.min=function(out,a,b){
-out[0]=Math.min(a[0],b[0]);
-out[1]=Math.min(a[1],b[1]);
-out[2]=Math.min(a[2],b[2]);
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec3.max=function(out,a,b){
-out[0]=Math.max(a[0],b[0]);
-out[1]=Math.max(a[1],b[1]);
-out[2]=Math.max(a[2],b[2]);
-return out;
-};
-
-
-
-
-
-
-
-
-vec3.round=function(out,a){
-out[0]=Math.round(a[0]);
-out[1]=Math.round(a[1]);
-out[2]=Math.round(a[2]);
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec3.scale=function(out,a,b){
-out[0]=a[0]*b;
-out[1]=a[1]*b;
-out[2]=a[2]*b;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-vec3.scaleAndAdd=function(out,a,b,scale){
-out[0]=a[0]+b[0]*scale;
-out[1]=a[1]+b[1]*scale;
-out[2]=a[2]+b[2]*scale;
-return out;
-};
-
-
-
-
-
-
-
-
-vec3.distance=function(a,b){
-var x=b[0]-a[0],
-y=b[1]-a[1],
-z=b[2]-a[2];
-return Math.sqrt(x*x+y*y+z*z);
-};
-
-
-
-
-
-vec3.dist=vec3.distance;
-
-
-
-
-
-
-
-
-vec3.squaredDistance=function(a,b){
-var x=b[0]-a[0],
-y=b[1]-a[1],
-z=b[2]-a[2];
-return x*x+y*y+z*z;
-};
-
-
-
-
-
-vec3.sqrDist=vec3.squaredDistance;
-
-
-
-
-
-
-
-vec3.length=function(a){
-var x=a[0],
-y=a[1],
-z=a[2];
-return Math.sqrt(x*x+y*y+z*z);
-};
-
-
-
-
-
-vec3.len=vec3.length;
-
-
-
-
-
-
-
-vec3.squaredLength=function(a){
-var x=a[0],
-y=a[1],
-z=a[2];
-return x*x+y*y+z*z;
-};
-
-
-
-
-
-vec3.sqrLen=vec3.squaredLength;
-
-
-
-
-
-
-
-
-vec3.negate=function(out,a){
-out[0]=-a[0];
-out[1]=-a[1];
-out[2]=-a[2];
-return out;
-};
-
-
-
-
-
-
-
-
-vec3.inverse=function(out,a){
-out[0]=1.0/a[0];
-out[1]=1.0/a[1];
-out[2]=1.0/a[2];
-return out;
-};
-
-
-
-
-
-
-
-
-vec3.normalize=function(out,a){
-var x=a[0],
-y=a[1],
-z=a[2];
-var len=x*x+y*y+z*z;
-if(len>0){
-
-len=1/Math.sqrt(len);
-out[0]=a[0]*len;
-out[1]=a[1]*len;
-out[2]=a[2]*len;
-}
-return out;
-};
-
-
-
-
-
-
-
-
-vec3.dot=function(a,b){
-return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];
-};
-
-
-
-
-
-
-
-
-
-vec3.cross=function(out,a,b){
-var ax=a[0],ay=a[1],az=a[2],
-bx=b[0],by=b[1],bz=b[2];
-
-out[0]=ay*bz-az*by;
-out[1]=az*bx-ax*bz;
-out[2]=ax*by-ay*bx;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-vec3.lerp=function(out,a,b,t){
-var ax=a[0],
-ay=a[1],
-az=a[2];
-out[0]=ax+t*(b[0]-ax);
-out[1]=ay+t*(b[1]-ay);
-out[2]=az+t*(b[2]-az);
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-vec3.hermite=function(out,a,b,c,d,t){
-var factorTimes2=t*t,
-factor1=factorTimes2*(2*t-3)+1,
-factor2=factorTimes2*(t-2)+t,
-factor3=factorTimes2*(t-1),
-factor4=factorTimes2*(3-2*t);
-
-out[0]=a[0]*factor1+b[0]*factor2+c[0]*factor3+d[0]*factor4;
-out[1]=a[1]*factor1+b[1]*factor2+c[1]*factor3+d[1]*factor4;
-out[2]=a[2]*factor1+b[2]*factor2+c[2]*factor3+d[2]*factor4;
-
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-vec3.bezier=function(out,a,b,c,d,t){
-var inverseFactor=1-t,
-inverseFactorTimesTwo=inverseFactor*inverseFactor,
-factorTimes2=t*t,
-factor1=inverseFactorTimesTwo*inverseFactor,
-factor2=3*t*inverseFactorTimesTwo,
-factor3=3*factorTimes2*inverseFactor,
-factor4=factorTimes2*t;
-
-out[0]=a[0]*factor1+b[0]*factor2+c[0]*factor3+d[0]*factor4;
-out[1]=a[1]*factor1+b[1]*factor2+c[1]*factor3+d[1]*factor4;
-out[2]=a[2]*factor1+b[2]*factor2+c[2]*factor3+d[2]*factor4;
-
-return out;
-};
-
-
-
-
-
-
-
-
-vec3.random=function(out,scale){
-scale=scale||1.0;
-
-var r=glMatrix.RANDOM()*2.0*Math.PI;
-var z=glMatrix.RANDOM()*2.0-1.0;
-var zScale=Math.sqrt(1.0-z*z)*scale;
-
-out[0]=Math.cos(r)*zScale;
-out[1]=Math.sin(r)*zScale;
-out[2]=z*scale;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-vec3.transformMat4=function(out,a,m){
-var x=a[0],y=a[1],z=a[2],
-w=m[3]*x+m[7]*y+m[11]*z+m[15];
-w=w||1.0;
-out[0]=(m[0]*x+m[4]*y+m[8]*z+m[12])/w;
-out[1]=(m[1]*x+m[5]*y+m[9]*z+m[13])/w;
-out[2]=(m[2]*x+m[6]*y+m[10]*z+m[14])/w;
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec3.transformMat3=function(out,a,m){
-var x=a[0],y=a[1],z=a[2];
-out[0]=x*m[0]+y*m[3]+z*m[6];
-out[1]=x*m[1]+y*m[4]+z*m[7];
-out[2]=x*m[2]+y*m[5]+z*m[8];
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec3.transformQuat=function(out,a,q){
-
-
-var x=a[0],y=a[1],z=a[2],
-qx=q[0],qy=q[1],qz=q[2],qw=q[3],
-
-
-ix=qw*x+qy*z-qz*y,
-iy=qw*y+qz*x-qx*z,
-iz=qw*z+qx*y-qy*x,
-iw=-qx*x-qy*y-qz*z;
-
-
-out[0]=ix*qw+iw*-qx+iy*-qz-iz*-qy;
-out[1]=iy*qw+iw*-qy+iz*-qx-ix*-qz;
-out[2]=iz*qw+iw*-qz+ix*-qy-iy*-qx;
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec3.rotateX=function(out,a,b,c){
-var p=[],r=[];
-
-p[0]=a[0]-b[0];
-p[1]=a[1]-b[1];
-p[2]=a[2]-b[2];
-
-
-r[0]=p[0];
-r[1]=p[1]*Math.cos(c)-p[2]*Math.sin(c);
-r[2]=p[1]*Math.sin(c)+p[2]*Math.cos(c);
-
-
-out[0]=r[0]+b[0];
-out[1]=r[1]+b[1];
-out[2]=r[2]+b[2];
-
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec3.rotateY=function(out,a,b,c){
-var p=[],r=[];
-
-p[0]=a[0]-b[0];
-p[1]=a[1]-b[1];
-p[2]=a[2]-b[2];
-
-
-r[0]=p[2]*Math.sin(c)+p[0]*Math.cos(c);
-r[1]=p[1];
-r[2]=p[2]*Math.cos(c)-p[0]*Math.sin(c);
-
-
-out[0]=r[0]+b[0];
-out[1]=r[1]+b[1];
-out[2]=r[2]+b[2];
-
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec3.rotateZ=function(out,a,b,c){
-var p=[],r=[];
-
-p[0]=a[0]-b[0];
-p[1]=a[1]-b[1];
-p[2]=a[2]-b[2];
-
-
-r[0]=p[0]*Math.cos(c)-p[1]*Math.sin(c);
-r[1]=p[0]*Math.sin(c)+p[1]*Math.cos(c);
-r[2]=p[2];
-
-
-out[0]=r[0]+b[0];
-out[1]=r[1]+b[1];
-out[2]=r[2]+b[2];
-
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-
-vec3.forEach=function(){
-var vec=vec3.create();
-
-return function(a,stride,offset,count,fn,arg){
-var i,l;
-if(!stride){
-stride=3;
-}
-
-if(!offset){
-offset=0;
-}
-
-if(count){
-l=Math.min(count*stride+offset,a.length);
-}else{
-l=a.length;
-}
-
-for(i=offset;i<l;i+=stride){
-vec[0]=a[i];vec[1]=a[i+1];vec[2]=a[i+2];
-fn(vec,vec,arg);
-a[i]=vec[0];a[i+1]=vec[1];a[i+2]=vec[2];
-}
-
-return a;
-};
-}();
-
-
-
-
-
-
-
-vec3.angle=function(a,b){
-
-var tempA=vec3.fromValues(a[0],a[1],a[2]);
-var tempB=vec3.fromValues(b[0],b[1],b[2]);
-
-vec3.normalize(tempA,tempA);
-vec3.normalize(tempB,tempB);
-
-var cosine=vec3.dot(tempA,tempB);
-
-if(cosine>1.0){
-return 0;
-}else{
-return Math.acos(cosine);
-}
-};
-
-
-
-
-
-
-
-vec3.str=function(a){
-return'vec3('+a[0]+', '+a[1]+', '+a[2]+')';
-};
-
-
-
-
-
-
-
-
-vec3.exactEquals=function(a,b){
-return a[0]===b[0]&&a[1]===b[1]&&a[2]===b[2];
-};
-
-
-
-
-
-
-
-
-vec3.equals=function(a,b){
-var a0=a[0],a1=a[1],a2=a[2];
-var b0=b[0],b1=b[1],b2=b[2];
-return Math.abs(a0-b0)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a0),Math.abs(b0))&&
-Math.abs(a1-b1)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a1),Math.abs(b1))&&
-Math.abs(a2-b2)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a2),Math.abs(b2));
-};
-
-module.exports=vec3;
-
-},{"./common.js":237}],245:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-var glMatrix=require("./common.js");
-
-
-
-
-
-var vec4={};
-
-
-
-
-
-
-vec4.create=function(){
-var out=new glMatrix.ARRAY_TYPE(4);
-out[0]=0;
-out[1]=0;
-out[2]=0;
-out[3]=0;
-return out;
-};
-
-
-
-
-
-
-
-vec4.clone=function(a){
-var out=new glMatrix.ARRAY_TYPE(4);
-out[0]=a[0];
-out[1]=a[1];
-out[2]=a[2];
-out[3]=a[3];
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-vec4.fromValues=function(x,y,z,w){
-var out=new glMatrix.ARRAY_TYPE(4);
-out[0]=x;
-out[1]=y;
-out[2]=z;
-out[3]=w;
-return out;
-};
-
-
-
-
-
-
-
-
-vec4.copy=function(out,a){
-out[0]=a[0];
-out[1]=a[1];
-out[2]=a[2];
-out[3]=a[3];
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-vec4.set=function(out,x,y,z,w){
-out[0]=x;
-out[1]=y;
-out[2]=z;
-out[3]=w;
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec4.add=function(out,a,b){
-out[0]=a[0]+b[0];
-out[1]=a[1]+b[1];
-out[2]=a[2]+b[2];
-out[3]=a[3]+b[3];
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec4.subtract=function(out,a,b){
-out[0]=a[0]-b[0];
-out[1]=a[1]-b[1];
-out[2]=a[2]-b[2];
-out[3]=a[3]-b[3];
-return out;
-};
-
-
-
-
-
-vec4.sub=vec4.subtract;
-
-
-
-
-
-
-
-
-
-vec4.multiply=function(out,a,b){
-out[0]=a[0]*b[0];
-out[1]=a[1]*b[1];
-out[2]=a[2]*b[2];
-out[3]=a[3]*b[3];
-return out;
-};
-
-
-
-
-
-vec4.mul=vec4.multiply;
-
-
-
-
-
-
-
-
-
-vec4.divide=function(out,a,b){
-out[0]=a[0]/b[0];
-out[1]=a[1]/b[1];
-out[2]=a[2]/b[2];
-out[3]=a[3]/b[3];
-return out;
-};
-
-
-
-
-
-vec4.div=vec4.divide;
-
-
-
-
-
-
-
-
-vec4.ceil=function(out,a){
-out[0]=Math.ceil(a[0]);
-out[1]=Math.ceil(a[1]);
-out[2]=Math.ceil(a[2]);
-out[3]=Math.ceil(a[3]);
-return out;
-};
-
-
-
-
-
-
-
-
-vec4.floor=function(out,a){
-out[0]=Math.floor(a[0]);
-out[1]=Math.floor(a[1]);
-out[2]=Math.floor(a[2]);
-out[3]=Math.floor(a[3]);
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec4.min=function(out,a,b){
-out[0]=Math.min(a[0],b[0]);
-out[1]=Math.min(a[1],b[1]);
-out[2]=Math.min(a[2],b[2]);
-out[3]=Math.min(a[3],b[3]);
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec4.max=function(out,a,b){
-out[0]=Math.max(a[0],b[0]);
-out[1]=Math.max(a[1],b[1]);
-out[2]=Math.max(a[2],b[2]);
-out[3]=Math.max(a[3],b[3]);
-return out;
-};
-
-
-
-
-
-
-
-
-vec4.round=function(out,a){
-out[0]=Math.round(a[0]);
-out[1]=Math.round(a[1]);
-out[2]=Math.round(a[2]);
-out[3]=Math.round(a[3]);
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec4.scale=function(out,a,b){
-out[0]=a[0]*b;
-out[1]=a[1]*b;
-out[2]=a[2]*b;
-out[3]=a[3]*b;
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-vec4.scaleAndAdd=function(out,a,b,scale){
-out[0]=a[0]+b[0]*scale;
-out[1]=a[1]+b[1]*scale;
-out[2]=a[2]+b[2]*scale;
-out[3]=a[3]+b[3]*scale;
-return out;
-};
-
-
-
-
-
-
-
-
-vec4.distance=function(a,b){
-var x=b[0]-a[0],
-y=b[1]-a[1],
-z=b[2]-a[2],
-w=b[3]-a[3];
-return Math.sqrt(x*x+y*y+z*z+w*w);
-};
-
-
-
-
-
-vec4.dist=vec4.distance;
-
-
-
-
-
-
-
-
-vec4.squaredDistance=function(a,b){
-var x=b[0]-a[0],
-y=b[1]-a[1],
-z=b[2]-a[2],
-w=b[3]-a[3];
-return x*x+y*y+z*z+w*w;
-};
-
-
-
-
-
-vec4.sqrDist=vec4.squaredDistance;
-
-
-
-
-
-
-
-vec4.length=function(a){
-var x=a[0],
-y=a[1],
-z=a[2],
-w=a[3];
-return Math.sqrt(x*x+y*y+z*z+w*w);
-};
-
-
-
-
-
-vec4.len=vec4.length;
-
-
-
-
-
-
-
-vec4.squaredLength=function(a){
-var x=a[0],
-y=a[1],
-z=a[2],
-w=a[3];
-return x*x+y*y+z*z+w*w;
-};
-
-
-
-
-
-vec4.sqrLen=vec4.squaredLength;
-
-
-
-
-
-
-
-
-vec4.negate=function(out,a){
-out[0]=-a[0];
-out[1]=-a[1];
-out[2]=-a[2];
-out[3]=-a[3];
-return out;
-};
-
-
-
-
-
-
-
-
-vec4.inverse=function(out,a){
-out[0]=1.0/a[0];
-out[1]=1.0/a[1];
-out[2]=1.0/a[2];
-out[3]=1.0/a[3];
-return out;
-};
-
-
-
-
-
-
-
-
-vec4.normalize=function(out,a){
-var x=a[0],
-y=a[1],
-z=a[2],
-w=a[3];
-var len=x*x+y*y+z*z+w*w;
-if(len>0){
-len=1/Math.sqrt(len);
-out[0]=x*len;
-out[1]=y*len;
-out[2]=z*len;
-out[3]=w*len;
-}
-return out;
-};
-
-
-
-
-
-
-
-
-vec4.dot=function(a,b){
-return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];
-};
-
-
-
-
-
-
-
-
-
-
-vec4.lerp=function(out,a,b,t){
-var ax=a[0],
-ay=a[1],
-az=a[2],
-aw=a[3];
-out[0]=ax+t*(b[0]-ax);
-out[1]=ay+t*(b[1]-ay);
-out[2]=az+t*(b[2]-az);
-out[3]=aw+t*(b[3]-aw);
-return out;
-};
-
-
-
-
-
-
-
-
-vec4.random=function(out,scale){
-scale=scale||1.0;
-
-
-out[0]=glMatrix.RANDOM();
-out[1]=glMatrix.RANDOM();
-out[2]=glMatrix.RANDOM();
-out[3]=glMatrix.RANDOM();
-vec4.normalize(out,out);
-vec4.scale(out,out,scale);
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec4.transformMat4=function(out,a,m){
-var x=a[0],y=a[1],z=a[2],w=a[3];
-out[0]=m[0]*x+m[4]*y+m[8]*z+m[12]*w;
-out[1]=m[1]*x+m[5]*y+m[9]*z+m[13]*w;
-out[2]=m[2]*x+m[6]*y+m[10]*z+m[14]*w;
-out[3]=m[3]*x+m[7]*y+m[11]*z+m[15]*w;
-return out;
-};
-
-
-
-
-
-
-
-
-
-vec4.transformQuat=function(out,a,q){
-var x=a[0],y=a[1],z=a[2],
-qx=q[0],qy=q[1],qz=q[2],qw=q[3],
-
-
-ix=qw*x+qy*z-qz*y,
-iy=qw*y+qz*x-qx*z,
-iz=qw*z+qx*y-qy*x,
-iw=-qx*x-qy*y-qz*z;
-
-
-out[0]=ix*qw+iw*-qx+iy*-qz-iz*-qy;
-out[1]=iy*qw+iw*-qy+iz*-qx-ix*-qz;
-out[2]=iz*qw+iw*-qz+ix*-qy-iy*-qx;
-out[3]=a[3];
-return out;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-
-vec4.forEach=function(){
-var vec=vec4.create();
-
-return function(a,stride,offset,count,fn,arg){
-var i,l;
-if(!stride){
-stride=4;
-}
-
-if(!offset){
-offset=0;
-}
-
-if(count){
-l=Math.min(count*stride+offset,a.length);
-}else{
-l=a.length;
-}
-
-for(i=offset;i<l;i+=stride){
-vec[0]=a[i];vec[1]=a[i+1];vec[2]=a[i+2];vec[3]=a[i+3];
-fn(vec,vec,arg);
-a[i]=vec[0];a[i+1]=vec[1];a[i+2]=vec[2];a[i+3]=vec[3];
-}
-
-return a;
-};
-}();
-
-
-
-
-
-
-
-vec4.str=function(a){
-return'vec4('+a[0]+', '+a[1]+', '+a[2]+', '+a[3]+')';
-};
-
-
-
-
-
-
-
-
-vec4.exactEquals=function(a,b){
-return a[0]===b[0]&&a[1]===b[1]&&a[2]===b[2]&&a[3]===b[3];
-};
-
-
-
-
-
-
-
-
-vec4.equals=function(a,b){
-var a0=a[0],a1=a[1],a2=a[2],a3=a[3];
-var b0=b[0],b1=b[1],b2=b[2],b3=b[3];
-return Math.abs(a0-b0)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a0),Math.abs(b0))&&
-Math.abs(a1-b1)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a1),Math.abs(b1))&&
-Math.abs(a2-b2)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a2),Math.abs(b2))&&
-Math.abs(a3-b3)<=glMatrix.EPSILON*Math.max(1.0,Math.abs(a3),Math.abs(b3));
-};
-
-module.exports=vec4;
-
-},{"./common.js":237}],246:[function(require,module,exports){
-'use strict';
-
-exports.__esModule=true;
-
-
-function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{'default':obj};}
-
-
-
-function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj['default']=obj;return newObj;}}
-
-var _handlebarsBase=require('./handlebars/base');
-
-var base=_interopRequireWildcard(_handlebarsBase);
-
-
-
-
-var _handlebarsSafeString=require('./handlebars/safe-string');
-
-var _handlebarsSafeString2=_interopRequireDefault(_handlebarsSafeString);
-
-var _handlebarsException=require('./handlebars/exception');
-
-var _handlebarsException2=_interopRequireDefault(_handlebarsException);
-
-var _handlebarsUtils=require('./handlebars/utils');
-
-var Utils=_interopRequireWildcard(_handlebarsUtils);
-
-var _handlebarsRuntime=require('./handlebars/runtime');
-
-var runtime=_interopRequireWildcard(_handlebarsRuntime);
-
-var _handlebarsNoConflict=require('./handlebars/no-conflict');
-
-var _handlebarsNoConflict2=_interopRequireDefault(_handlebarsNoConflict);
-
-
-function create(){
-var hb=new base.HandlebarsEnvironment();
-
-Utils.extend(hb,base);
-hb.SafeString=_handlebarsSafeString2['default'];
-hb.Exception=_handlebarsException2['default'];
-hb.Utils=Utils;
-hb.escapeExpression=Utils.escapeExpression;
-
-hb.VM=runtime;
-hb.template=function(spec){
-return runtime.template(spec,hb);
-};
-
-return hb;
-}
-
-var inst=create();
-inst.create=create;
-
-_handlebarsNoConflict2['default'](inst);
-
-inst['default']=inst;
-
-exports['default']=inst;
-module.exports=exports['default'];
-
-
-},{"./handlebars/base":247,"./handlebars/exception":250,"./handlebars/no-conflict":260,"./handlebars/runtime":261,"./handlebars/safe-string":262,"./handlebars/utils":263}],247:[function(require,module,exports){
-'use strict';
-
-exports.__esModule=true;
-exports.HandlebarsEnvironment=HandlebarsEnvironment;
-
-
-function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{'default':obj};}
-
-var _utils=require('./utils');
-
-var _exception=require('./exception');
-
-var _exception2=_interopRequireDefault(_exception);
-
-var _helpers=require('./helpers');
-
-var _decorators=require('./decorators');
-
-var _logger=require('./logger');
-
-var _logger2=_interopRequireDefault(_logger);
-
-var VERSION='4.0.5';
-exports.VERSION=VERSION;
-var COMPILER_REVISION=7;
-
-exports.COMPILER_REVISION=COMPILER_REVISION;
-var REVISION_CHANGES={
-1:'<= 1.0.rc.2',
-2:'== 1.0.0-rc.3',
-3:'== 1.0.0-rc.4',
-4:'== 1.x.x',
-5:'== 2.0.0-alpha.x',
-6:'>= 2.0.0-beta.1',
-7:'>= 4.0.0'};
-
-
-exports.REVISION_CHANGES=REVISION_CHANGES;
-var objectType='[object Object]';
-
-function HandlebarsEnvironment(helpers,partials,decorators){
-this.helpers=helpers||{};
-this.partials=partials||{};
-this.decorators=decorators||{};
-
-_helpers.registerDefaultHelpers(this);
-_decorators.registerDefaultDecorators(this);
-}
-
-HandlebarsEnvironment.prototype={
-constructor:HandlebarsEnvironment,
-
-logger:_logger2['default'],
-log:_logger2['default'].log,
-
-registerHelper:function registerHelper(name,fn){
-if(_utils.toString.call(name)===objectType){
-if(fn){
-throw new _exception2['default']('Arg not supported with multiple helpers');
-}
-_utils.extend(this.helpers,name);
-}else{
-this.helpers[name]=fn;
-}
-},
-unregisterHelper:function unregisterHelper(name){
-delete this.helpers[name];
-},
-
-registerPartial:function registerPartial(name,partial){
-if(_utils.toString.call(name)===objectType){
-_utils.extend(this.partials,name);
-}else{
-if(typeof partial==='undefined'){
-throw new _exception2['default']('Attempting to register a partial called "'+name+'" as undefined');
-}
-this.partials[name]=partial;
-}
-},
-unregisterPartial:function unregisterPartial(name){
-delete this.partials[name];
-},
-
-registerDecorator:function registerDecorator(name,fn){
-if(_utils.toString.call(name)===objectType){
-if(fn){
-throw new _exception2['default']('Arg not supported with multiple decorators');
-}
-_utils.extend(this.decorators,name);
-}else{
-this.decorators[name]=fn;
-}
-},
-unregisterDecorator:function unregisterDecorator(name){
-delete this.decorators[name];
-}};
-
-
-var log=_logger2['default'].log;
-
-exports.log=log;
-exports.createFrame=_utils.createFrame;
-exports.logger=_logger2['default'];
-
-
-},{"./decorators":248,"./exception":250,"./helpers":251,"./logger":259,"./utils":263}],248:[function(require,module,exports){
-'use strict';
-
-exports.__esModule=true;
-exports.registerDefaultDecorators=registerDefaultDecorators;
-
-
-function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{'default':obj};}
-
-var _decoratorsInline=require('./decorators/inline');
-
-var _decoratorsInline2=_interopRequireDefault(_decoratorsInline);
-
-function registerDefaultDecorators(instance){
-_decoratorsInline2['default'](instance);
-}
-
-
-},{"./decorators/inline":249}],249:[function(require,module,exports){
-'use strict';
-
-exports.__esModule=true;
-
-var _utils=require('../utils');
-
-exports['default']=function(instance){
-instance.registerDecorator('inline',function(fn,props,container,options){
-var ret=fn;
-if(!props.partials){
-props.partials={};
-ret=function(context,options){
-
-var original=container.partials;
-container.partials=_utils.extend({},original,props.partials);
-var ret=fn(context,options);
-container.partials=original;
-return ret;
-};
-}
-
-props.partials[options.args[0]]=options.fn;
-
-return ret;
-});
-};
-
-module.exports=exports['default'];
-
-
-},{"../utils":263}],250:[function(require,module,exports){
-'use strict';
-
-exports.__esModule=true;
-
-var errorProps=['description','fileName','lineNumber','message','name','number','stack'];
-
-function Exception(message,node){
-var loc=node&&node.loc,
-line=undefined,
-column=undefined;
-if(loc){
-line=loc.start.line;
-column=loc.start.column;
-
-message+=' - '+line+':'+column;
-}
-
-var tmp=Error.prototype.constructor.call(this,message);
-
-
-for(var idx=0;idx<errorProps.length;idx++){
-this[errorProps[idx]]=tmp[errorProps[idx]];
-}
-
-
-if(Error.captureStackTrace){
-Error.captureStackTrace(this,Exception);
-}
-
-if(loc){
-this.lineNumber=line;
-this.column=column;
-}
+    __proto__: WebInspector.SDKObject.prototype
 }
 
-Exception.prototype=new Error();
-
-exports['default']=Exception;
-module.exports=exports['default'];
-
-
 },{}],251:[function(require,module,exports){
-'use strict';
+/*
+ * Copyright 2014 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
 
-exports.__esModule=true;
-exports.registerDefaultHelpers=registerDefaultHelpers;
-
-
-function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{'default':obj};}
-
-var _helpersBlockHelperMissing=require('./helpers/block-helper-missing');
-
-var _helpersBlockHelperMissing2=_interopRequireDefault(_helpersBlockHelperMissing);
-
-var _helpersEach=require('./helpers/each');
-
-var _helpersEach2=_interopRequireDefault(_helpersEach);
-
-var _helpersHelperMissing=require('./helpers/helper-missing');
-
-var _helpersHelperMissing2=_interopRequireDefault(_helpersHelperMissing);
-
-var _helpersIf=require('./helpers/if');
-
-var _helpersIf2=_interopRequireDefault(_helpersIf);
-
-var _helpersLog=require('./helpers/log');
-
-var _helpersLog2=_interopRequireDefault(_helpersLog);
-
-var _helpersLookup=require('./helpers/lookup');
-
-var _helpersLookup2=_interopRequireDefault(_helpersLookup);
-
-var _helpersWith=require('./helpers/with');
-
-var _helpersWith2=_interopRequireDefault(_helpersWith);
-
-function registerDefaultHelpers(instance){
-_helpersBlockHelperMissing2['default'](instance);
-_helpersEach2['default'](instance);
-_helpersHelperMissing2['default'](instance);
-_helpersIf2['default'](instance);
-_helpersLog2['default'](instance);
-_helpersLookup2['default'](instance);
-_helpersWith2['default'](instance);
+/**
+ * @constructor
+ * @extends {WebInspector.Object}
+ */
+WebInspector.TargetManager = function()
+{
+    WebInspector.Object.call(this);
+    /** @type {!Array.<!WebInspector.Target>} */
+    this._targets = [];
+    /** @type {!Array.<!WebInspector.TargetManager.Observer>} */
+    this._observers = [];
+    this._observerCapabiliesMaskSymbol = Symbol("observerCapabilitiesMask");
+    /** @type {!Map<symbol, !Array<{modelClass: !Function, thisObject: (!Object|undefined), listener: function(!WebInspector.Event)}>>} */
+    this._modelListeners = new Map();
+    this._isSuspended = false;
 }
 
-
-},{"./helpers/block-helper-missing":252,"./helpers/each":253,"./helpers/helper-missing":254,"./helpers/if":255,"./helpers/log":256,"./helpers/lookup":257,"./helpers/with":258}],252:[function(require,module,exports){
-'use strict';
-
-exports.__esModule=true;
-
-var _utils=require('../utils');
-
-exports['default']=function(instance){
-instance.registerHelper('blockHelperMissing',function(context,options){
-var inverse=options.inverse,
-fn=options.fn;
-
-if(context===true){
-return fn(this);
-}else if(context===false||context==null){
-return inverse(this);
-}else if(_utils.isArray(context)){
-if(context.length>0){
-if(options.ids){
-options.ids=[options.name];
+/** @enum {symbol} */
+WebInspector.TargetManager.Events = {
+    InspectedURLChanged: Symbol("InspectedURLChanged"),
+    MainFrameNavigated: Symbol("MainFrameNavigated"),
+    Load: Symbol("Load"),
+    PageReloadRequested: Symbol("PageReloadRequested"),
+    WillReloadPage: Symbol("WillReloadPage"),
+    TargetDisposed: Symbol("TargetDisposed"),
+    SuspendStateChanged: Symbol("SuspendStateChanged")
 }
 
-return instance.helpers.each(context,options);
-}else{
-return inverse(this);
-}
-}else{
-if(options.data&&options.ids){
-var data=_utils.createFrame(options.data);
-data.contextPath=_utils.appendContextPath(options.data.contextPath,options.name);
-options={data:data};
+WebInspector.TargetManager._listenersSymbol = Symbol("WebInspector.TargetManager.Listeners");
+
+WebInspector.TargetManager.prototype = {
+    suspendAllTargets: function()
+    {
+        if (this._isSuspended)
+            return;
+        this._isSuspended = true;
+        this.dispatchEventToListeners(WebInspector.TargetManager.Events.SuspendStateChanged);
+
+        for (var i = 0; i < this._targets.length; ++i) {
+            for (var model of this._targets[i].models())
+                model.suspendModel();
+        }
+    },
+
+    /**
+     * @return {!Promise}
+     */
+    resumeAllTargets: function()
+    {
+        if (!this._isSuspended)
+            throw new Error("Not suspended");
+        this._isSuspended = false;
+        this.dispatchEventToListeners(WebInspector.TargetManager.Events.SuspendStateChanged);
+
+        var promises = [];
+        for (var i = 0; i < this._targets.length; ++i) {
+            for (var model of this._targets[i].models())
+                promises.push(model.resumeModel());
+        }
+        return Promise.all(promises);
+    },
+
+    suspendAndResumeAllTargets: function()
+    {
+        this.suspendAllTargets();
+        this.resumeAllTargets();
+    },
+
+    /**
+     * @return {boolean}
+     */
+    allTargetsSuspended: function()
+    {
+        return this._isSuspended;
+    },
+
+    /**
+     * @return {string}
+     */
+    inspectedURL: function()
+    {
+        return this._targets[0] ? this._targets[0].inspectedURL() : "";
+    },
+
+    /**
+     * @param {!WebInspector.TargetManager.Events} eventName
+     * @param {!WebInspector.Event} event
+     */
+    _redispatchEvent: function(eventName, event)
+    {
+        this.dispatchEventToListeners(eventName, event.data);
+    },
+
+    /**
+     * @param {boolean=} bypassCache
+     * @param {string=} injectedScript
+     */
+    reloadPage: function(bypassCache, injectedScript)
+    {
+        if (!this._targets.length)
+            return;
+
+        var resourceTreeModel = WebInspector.ResourceTreeModel.fromTarget(this._targets[0]);
+        if (!resourceTreeModel)
+            return;
+
+        resourceTreeModel.reloadPage(bypassCache, injectedScript);
+    },
+
+    /**
+     * @param {!Function} modelClass
+     * @param {symbol} eventType
+     * @param {function(!WebInspector.Event)} listener
+     * @param {!Object=} thisObject
+     */
+    addModelListener: function(modelClass, eventType, listener, thisObject)
+    {
+        for (var i = 0; i < this._targets.length; ++i) {
+            var model = this._targets[i].model(modelClass);
+            if (model)
+                model.addEventListener(eventType, listener, thisObject);
+        }
+        if (!this._modelListeners.has(eventType))
+            this._modelListeners.set(eventType, []);
+        this._modelListeners.get(eventType).push({ modelClass: modelClass, thisObject: thisObject, listener: listener });
+    },
+
+    /**
+     * @param {!Function} modelClass
+     * @param {symbol} eventType
+     * @param {function(!WebInspector.Event)} listener
+     * @param {!Object=} thisObject
+     */
+    removeModelListener: function(modelClass, eventType, listener, thisObject)
+    {
+        if (!this._modelListeners.has(eventType))
+            return;
+
+        for (var i = 0; i < this._targets.length; ++i) {
+            var model = this._targets[i].model(modelClass);
+            if (model)
+                model.removeEventListener(eventType, listener, thisObject);
+        }
+
+        var listeners = this._modelListeners.get(eventType);
+        for (var i = 0; i < listeners.length; ++i) {
+            if (listeners[i].modelClass === modelClass && listeners[i].listener === listener && listeners[i].thisObject === thisObject)
+                listeners.splice(i--, 1);
+        }
+        if (!listeners.length)
+            this._modelListeners.delete(eventType);
+    },
+
+    /**
+     * @param {!WebInspector.TargetManager.Observer} targetObserver
+     * @param {number=} capabilitiesMask
+     */
+    observeTargets: function(targetObserver, capabilitiesMask)
+    {
+        if (this._observerCapabiliesMaskSymbol in targetObserver)
+            throw new Error("Observer can only be registered once");
+        targetObserver[this._observerCapabiliesMaskSymbol] = capabilitiesMask || 0;
+        this.targets(capabilitiesMask).forEach(targetObserver.targetAdded.bind(targetObserver));
+        this._observers.push(targetObserver);
+    },
+
+    /**
+     * @param {!WebInspector.TargetManager.Observer} targetObserver
+     */
+    unobserveTargets: function(targetObserver)
+    {
+        delete targetObserver[this._observerCapabiliesMaskSymbol];
+        this._observers.remove(targetObserver);
+    },
+
+    /**
+     * @param {string} name
+     * @param {number} capabilitiesMask
+     * @param {!InspectorBackendClass.Connection} connection
+     * @param {?WebInspector.Target} parentTarget
+     * @return {!WebInspector.Target}
+     */
+    createTarget: function(name, capabilitiesMask, connection, parentTarget)
+    {
+        var target = new WebInspector.Target(this, name, capabilitiesMask, connection, parentTarget);
+
+        var logAgent = target.hasLogCapability() ? target.logAgent() : null;
+
+        /** @type {!WebInspector.ConsoleModel} */
+        target.consoleModel = new WebInspector.ConsoleModel(target, logAgent);
+        /** @type {!WebInspector.RuntimeModel} */
+        target.runtimeModel = new WebInspector.RuntimeModel(target);
+
+        var networkManager = null;
+        var resourceTreeModel = null;
+        if (target.hasNetworkCapability())
+            networkManager = new WebInspector.NetworkManager(target);
+        if (networkManager && target.hasDOMCapability()) {
+            resourceTreeModel = new WebInspector.ResourceTreeModel(target, networkManager, WebInspector.SecurityOriginManager.fromTarget(target));
+            new WebInspector.NetworkLog(target, resourceTreeModel, networkManager);
+        }
+
+        if (target.hasJSCapability())
+            new WebInspector.DebuggerModel(target);
+
+        if (resourceTreeModel) {
+            var domModel = new WebInspector.DOMModel(target);
+            // TODO(eostroukhov) CSSModel should not depend on RTM
+            new WebInspector.CSSModel(target, domModel);
+        }
+
+        /** @type {?WebInspector.WorkerManager} */
+        target.workerManager = target.hasWorkerCapability() ? new WebInspector.WorkerManager(target) : null;
+        /** @type {!WebInspector.CPUProfilerModel} */
+        target.cpuProfilerModel = new WebInspector.CPUProfilerModel(target);
+        /** @type {!WebInspector.HeapProfilerModel} */
+        target.heapProfilerModel = new WebInspector.HeapProfilerModel(target);
+
+        target.tracingManager = new WebInspector.TracingManager(target);
+
+        if (target.hasBrowserCapability()) {
+            target.subTargetsManager = new WebInspector.SubTargetsManager(target);
+            target.serviceWorkerManager = new WebInspector.ServiceWorkerManager(target, target.subTargetsManager);
+        }
+
+        this.addTarget(target);
+        return target;
+    },
+
+    /**
+     * @param {!WebInspector.Target} target
+     * @return {!Array<!WebInspector.TargetManager.Observer>}
+     */
+    _observersForTarget: function(target)
+    {
+        return this._observers.filter((observer) => target.hasAllCapabilities(observer[this._observerCapabiliesMaskSymbol] || 0));
+    },
+
+    /**
+     * @param {!WebInspector.Target} target
+     */
+    addTarget: function(target)
+    {
+        this._targets.push(target);
+        var resourceTreeModel = WebInspector.ResourceTreeModel.fromTarget(target);
+        if (this._targets.length === 1 && resourceTreeModel) {
+            resourceTreeModel[WebInspector.TargetManager._listenersSymbol] = [
+                setupRedispatch.call(this, WebInspector.ResourceTreeModel.Events.MainFrameNavigated, WebInspector.TargetManager.Events.MainFrameNavigated),
+                setupRedispatch.call(this, WebInspector.ResourceTreeModel.Events.Load, WebInspector.TargetManager.Events.Load),
+                setupRedispatch.call(this, WebInspector.ResourceTreeModel.Events.PageReloadRequested, WebInspector.TargetManager.Events.PageReloadRequested),
+                setupRedispatch.call(this, WebInspector.ResourceTreeModel.Events.WillReloadPage, WebInspector.TargetManager.Events.WillReloadPage)
+            ];
+        }
+        var copy = this._observersForTarget(target);
+        for (var i = 0; i < copy.length; ++i)
+            copy[i].targetAdded(target);
+
+        for (var pair of this._modelListeners) {
+            var listeners = pair[1];
+            for (var i = 0; i < listeners.length; ++i) {
+                var model = target.model(listeners[i].modelClass);
+                if (model)
+                    model.addEventListener(/** @type {symbol} */ (pair[0]), listeners[i].listener, listeners[i].thisObject);
+            }
+        }
+
+        /**
+         * @param {!WebInspector.ResourceTreeModel.Events} sourceEvent
+         * @param {!WebInspector.TargetManager.Events} targetEvent
+         * @return {!WebInspector.EventTarget.EventDescriptor}
+         * @this {WebInspector.TargetManager}
+         */
+        function setupRedispatch(sourceEvent, targetEvent)
+        {
+            return resourceTreeModel.addEventListener(sourceEvent, this._redispatchEvent.bind(this, targetEvent));
+        }
+    },
+
+    /**
+     * @param {!WebInspector.Target} target
+     */
+    removeTarget: function(target)
+    {
+        this._targets.remove(target);
+        var resourceTreeModel = WebInspector.ResourceTreeModel.fromTarget(target);
+        var treeModelListeners = resourceTreeModel && resourceTreeModel[WebInspector.TargetManager._listenersSymbol];
+        if (treeModelListeners)
+            WebInspector.EventTarget.removeEventListeners(treeModelListeners);
+
+        var copy = this._observersForTarget(target);
+        for (var i = 0; i < copy.length; ++i)
+            copy[i].targetRemoved(target);
+
+        for (var pair of this._modelListeners) {
+            var listeners = pair[1];
+            for (var i = 0; i < listeners.length; ++i) {
+                var model = target.model(listeners[i].modelClass);
+                if (model)
+                    model.removeEventListener(/** @type {symbol} */ (pair[0]), listeners[i].listener, listeners[i].thisObject);
+            }
+        }
+    },
+
+    /**
+     * @param {number=} capabilitiesMask
+     * @return {!Array.<!WebInspector.Target>}
+     */
+    targets: function(capabilitiesMask)
+    {
+        if (!capabilitiesMask)
+            return this._targets.slice();
+        else
+            return this._targets.filter((target) => target.hasAllCapabilities(capabilitiesMask || 0));
+    },
+
+    /**
+     *
+     * @param {number} id
+     * @return {?WebInspector.Target}
+     */
+    targetById: function(id)
+    {
+        for (var i = 0; i < this._targets.length; ++i) {
+            if (this._targets[i].id() === id)
+                return this._targets[i];
+        }
+        return null;
+    },
+
+    /**
+     * @return {?WebInspector.Target}
+     */
+    mainTarget: function()
+    {
+        return this._targets[0] || null;
+    },
+
+    /**
+     * @param {!WebInspector.Target} target
+     */
+    suspendReload: function(target)
+    {
+        var resourceTreeModel = WebInspector.ResourceTreeModel.fromTarget(target);
+        if (resourceTreeModel)
+            resourceTreeModel.suspendReload();
+    },
+
+    /**
+     * @param {!WebInspector.Target} target
+     */
+    resumeReload: function(target)
+    {
+        var resourceTreeModel = WebInspector.ResourceTreeModel.fromTarget(target);
+        if (resourceTreeModel)
+            setImmediate(resourceTreeModel.resumeReload.bind(resourceTreeModel));
+    },
+
+    __proto__: WebInspector.Object.prototype
 }
 
-return fn(context,options);
+/**
+ * @interface
+ */
+WebInspector.TargetManager.Observer = function()
+{
 }
-});
+
+WebInspector.TargetManager.Observer.prototype = {
+    /**
+     * @param {!WebInspector.Target} target
+     */
+    targetAdded: function(target) { },
+
+    /**
+     * @param {!WebInspector.Target} target
+     */
+    targetRemoved: function(target) { },
+}
+
+/**
+ * @type {!WebInspector.TargetManager}
+ */
+WebInspector.targetManager = new WebInspector.TargetManager();
+
+},{}],252:[function(require,module,exports){
+/*
+ * Copyright 2014 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**
+ * @constructor
+ * @param {!WebInspector.BackingStorage} backingStorage
+ */
+WebInspector.TracingModel = function(backingStorage)
+{
+    this._backingStorage = backingStorage;
+    // Avoid extra reset of the storage as it's expensive.
+    this._firstWritePending = true;
+    this.reset();
+}
+
+/**
+ * @enum {string}
+ */
+WebInspector.TracingModel.Phase = {
+    Begin: "B",
+    End: "E",
+    Complete: "X",
+    Instant: "I",
+    AsyncBegin: "S",
+    AsyncStepInto: "T",
+    AsyncStepPast: "p",
+    AsyncEnd: "F",
+    NestableAsyncBegin: "b",
+    NestableAsyncEnd: "e",
+    NestableAsyncInstant: "n",
+    FlowBegin: "s",
+    FlowStep: "t",
+    FlowEnd: "f",
+    Metadata: "M",
+    Counter: "C",
+    Sample: "P",
+    CreateObject: "N",
+    SnapshotObject: "O",
+    DeleteObject: "D"
 };
 
-module.exports=exports['default'];
-
-
-},{"../utils":263}],253:[function(require,module,exports){
-'use strict';
-
-exports.__esModule=true;
-
-
-function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{'default':obj};}
-
-var _utils=require('../utils');
-
-var _exception=require('../exception');
-
-var _exception2=_interopRequireDefault(_exception);
-
-exports['default']=function(instance){
-instance.registerHelper('each',function(context,options){
-if(!options){
-throw new _exception2['default']('Must pass iterator to #each');
+WebInspector.TracingModel.MetadataEvent = {
+    ProcessSortIndex: "process_sort_index",
+    ProcessName: "process_name",
+    ThreadSortIndex: "thread_sort_index",
+    ThreadName: "thread_name"
 }
 
-var fn=options.fn,
-inverse=options.inverse,
-i=0,
-ret='',
-data=undefined,
-contextPath=undefined;
+WebInspector.TracingModel.TopLevelEventCategory = "toplevel";
+WebInspector.TracingModel.DevToolsMetadataEventCategory = "disabled-by-default-devtools.timeline";
+WebInspector.TracingModel.DevToolsTimelineEventCategory = "disabled-by-default-devtools.timeline";
 
-if(options.data&&options.ids){
-contextPath=_utils.appendContextPath(options.data.contextPath,options.ids[0])+'.';
+WebInspector.TracingModel.FrameLifecycleEventCategory = "cc,devtools";
+
+/**
+ * @param {string} phase
+ * @return {boolean}
+ */
+WebInspector.TracingModel.isNestableAsyncPhase = function(phase)
+{
+    return phase === "b" || phase === "e" || phase === "n";
 }
 
-if(_utils.isFunction(context)){
-context=context.call(this);
+/**
+ * @param {string} phase
+ * @return {boolean}
+ */
+WebInspector.TracingModel.isAsyncBeginPhase = function(phase)
+{
+    return phase === "S" || phase === "b";
 }
 
-if(options.data){
-data=_utils.createFrame(options.data);
+/**
+ * @param {string} phase
+ * @return {boolean}
+ */
+WebInspector.TracingModel.isAsyncPhase = function(phase)
+{
+    return WebInspector.TracingModel.isNestableAsyncPhase(phase) || phase === "S" || phase === "T" || phase === "F" || phase === "p";
 }
 
-function execIteration(field,index,last){
-if(data){
-data.key=field;
-data.index=index;
-data.first=index===0;
-data.last=!!last;
-
-if(contextPath){
-data.contextPath=contextPath+field;
-}
+/**
+ * @param {string} phase
+ * @return {boolean}
+ */
+WebInspector.TracingModel.isFlowPhase = function(phase)
+{
+    return phase === "s" || phase === "t" || phase === "f";
 }
 
-ret=ret+fn(context[field],{
-data:data,
-blockParams:_utils.blockParams([context[field],field],[contextPath+field,null])});
-
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @return {boolean}
+ */
+WebInspector.TracingModel.isTopLevelEvent = function(event)
+{
+    return event.hasCategory(WebInspector.TracingModel.TopLevelEventCategory) ||
+        event.hasCategory(WebInspector.TracingModel.DevToolsMetadataEventCategory) && event.name === "Program"; // Older timelines may have this instead of toplevel.
 }
 
-if(context&&typeof context==='object'){
-if(_utils.isArray(context)){
-for(var j=context.length;i<j;i++){
-if(i in context){
-execIteration(i,i,i===context.length-1);
-}
-}
-}else{
-var priorKey=undefined;
-
-for(var key in context){
-if(context.hasOwnProperty(key)){
-
-
-
-if(priorKey!==undefined){
-execIteration(priorKey,i-1);
-}
-priorKey=key;
-i++;
-}
-}
-if(priorKey!==undefined){
-execIteration(priorKey,i-1,true);
-}
-}
+/**
+ * @param {!WebInspector.TracingManager.EventPayload} payload
+ * @return {string|undefined}
+ */
+WebInspector.TracingModel._extractId = function(payload)
+{
+    var scope = payload.scope || "";
+    if (typeof payload.id2 === "undefined")
+        return scope && payload.id ? `${scope}@${payload.id}` : payload.id;
+    var id2 = payload.id2;
+    if (typeof id2 === "object" && ("global" in id2) !== ("local" in id2))
+        return typeof id2["global"] !== "undefined" ? `:${scope}:${id2["global"]}` : `:${scope}:${payload.pid}:${id2["local"]}`;
+    console.error(`Unexpected id2 field at ${payload.ts / 1000}, one and only one of 'local' and 'global' should be present.`);
 }
 
-if(i===0){
-ret=inverse(this);
+/**
+ * @param {!WebInspector.TracingModel} tracingModel
+ * @return {?WebInspector.TracingModel.Thread}
+ *
+ * TODO: Move this to a better place. This is here just for convenience o
+ * re-use between modules. This really belongs to a higher level, since it
+ * is specific to chrome's usage of tracing.
+ */
+WebInspector.TracingModel.browserMainThread = function(tracingModel)
+{
+    var processes = tracingModel.sortedProcesses();
+    var browserProcesses = [];
+    var crRendererMainThreads = [];
+    for (var process of processes) {
+        if (process.name().toLowerCase().endsWith("browser"))
+            browserProcesses.push(process);
+        crRendererMainThreads.push(...process.sortedThreads().filter(t => t.name() === "CrBrowserMain"));
+    }
+    if (crRendererMainThreads.length === 1)
+        return crRendererMainThreads[0];
+    if (browserProcesses.length === 1)
+        return browserProcesses[0].threadByName("CrBrowserMain");
+    var tracingStartedInBrowser = tracingModel.devToolsMetadataEvents().filter(e => e.name === "TracingStartedInBrowser");
+    if (tracingStartedInBrowser.length === 1)
+        return tracingStartedInBrowser[0].thread;
+    WebInspector.console.error("Failed to find browser main thread in trace, some timeline features may be unavailable");
+    return null;
 }
 
-return ret;
-});
+/**
+ * @interface
+ */
+WebInspector.BackingStorage = function()
+{
+}
+
+WebInspector.BackingStorage.prototype = {
+    /**
+     * @param {string} string
+     */
+    appendString: function(string) { },
+
+    /**
+     * @param {string} string
+     * @return {function():!Promise.<?string>}
+     */
+    appendAccessibleString: function(string) { },
+
+    finishWriting: function() { },
+
+    reset: function() { },
+}
+
+
+WebInspector.TracingModel.prototype = {
+    /**
+     * @return {!Array.<!WebInspector.TracingModel.Event>}
+     */
+    devToolsMetadataEvents: function()
+    {
+        return this._devToolsMetadataEvents;
+    },
+
+    /**
+     * @param {!Array.<!WebInspector.TracingManager.EventPayload>} events
+     */
+    setEventsForTest: function(events)
+    {
+        this.reset();
+        this.addEvents(events);
+        this.tracingComplete();
+    },
+
+    /**
+     * @param {!Array.<!WebInspector.TracingManager.EventPayload>} events
+     */
+    addEvents: function(events)
+    {
+        for (var i = 0; i < events.length; ++i)
+            this._addEvent(events[i]);
+    },
+
+    tracingComplete: function()
+    {
+        this._processPendingAsyncEvents();
+        this._backingStorage.appendString(this._firstWritePending ? "[]" : "]");
+        this._backingStorage.finishWriting();
+        this._firstWritePending = false;
+        for (var process of this._processById.values()) {
+            for (var thread of process._threads.values())
+                thread.tracingComplete();
+        }
+    },
+
+    reset: function()
+    {
+        /** @type {!Map<(number|string), !WebInspector.TracingModel.Process>} */
+        this._processById = new Map();
+        this._processByName = new Map();
+        this._minimumRecordTime = 0;
+        this._maximumRecordTime = 0;
+        this._devToolsMetadataEvents = [];
+        if (!this._firstWritePending)
+            this._backingStorage.reset();
+
+        this._firstWritePending = true;
+        /** @type {!Array<!WebInspector.TracingModel.Event>} */
+        this._asyncEvents = [];
+        /** @type {!Map<string, !WebInspector.TracingModel.AsyncEvent>} */
+        this._openAsyncEvents = new Map();
+        /** @type {!Map<string, !Array<!WebInspector.TracingModel.AsyncEvent>>} */
+        this._openNestableAsyncEvents = new Map();
+        /** @type {!Map<string, !Set<string>>} */
+        this._parsedCategories = new Map();
+    },
+
+    /**
+      * @param {!WebInspector.TracingManager.EventPayload} payload
+      */
+    _addEvent: function(payload)
+    {
+        var process = this._processById.get(payload.pid);
+        if (!process) {
+            process = new WebInspector.TracingModel.Process(this, payload.pid);
+            this._processById.set(payload.pid, process);
+        }
+
+        var eventsDelimiter = ",\n";
+        this._backingStorage.appendString(this._firstWritePending ? "[" : eventsDelimiter);
+        this._firstWritePending = false;
+        var stringPayload = JSON.stringify(payload);
+        var isAccessible = payload.ph === WebInspector.TracingModel.Phase.SnapshotObject;
+        var backingStorage = null;
+        var keepStringsLessThan = 10000;
+        if (isAccessible && stringPayload.length > keepStringsLessThan)
+            backingStorage = this._backingStorage.appendAccessibleString(stringPayload);
+        else
+            this._backingStorage.appendString(stringPayload);
+
+        var timestamp = payload.ts / 1000;
+        // We do allow records for unrelated threads to arrive out-of-order,
+        // so there's a chance we're getting records from the past.
+        if (timestamp && (!this._minimumRecordTime || timestamp < this._minimumRecordTime))
+            this._minimumRecordTime = timestamp;
+        var endTimeStamp = (payload.ts + (payload.dur || 0)) / 1000;
+        this._maximumRecordTime = Math.max(this._maximumRecordTime, endTimeStamp);
+        var event = process._addEvent(payload);
+        if (!event)
+            return;
+        // Build async event when we've got events from all threads & processes, so we can sort them and process in the
+        // chronological order. However, also add individual async events to the thread flow (above), so we can easily
+        // display them on the same chart as other events, should we choose so.
+        if (WebInspector.TracingModel.isAsyncPhase(payload.ph))
+            this._asyncEvents.push(event);
+        event._setBackingStorage(backingStorage);
+        if (event.hasCategory(WebInspector.TracingModel.DevToolsMetadataEventCategory))
+            this._devToolsMetadataEvents.push(event);
+
+        if (payload.ph !== WebInspector.TracingModel.Phase.Metadata)
+            return;
+
+        switch (payload.name) {
+        case WebInspector.TracingModel.MetadataEvent.ProcessSortIndex:
+            process._setSortIndex(payload.args["sort_index"]);
+            break;
+        case WebInspector.TracingModel.MetadataEvent.ProcessName:
+            var processName = payload.args["name"];
+            process._setName(processName);
+            this._processByName.set(processName, process);
+            break;
+        case WebInspector.TracingModel.MetadataEvent.ThreadSortIndex:
+            process.threadById(payload.tid)._setSortIndex(payload.args["sort_index"]);
+            break;
+        case WebInspector.TracingModel.MetadataEvent.ThreadName:
+            process.threadById(payload.tid)._setName(payload.args["name"]);
+            break;
+        }
+    },
+
+    /**
+     * @return {number}
+     */
+    minimumRecordTime: function()
+    {
+        return this._minimumRecordTime;
+    },
+
+    /**
+     * @return {number}
+     */
+    maximumRecordTime: function()
+    {
+        return this._maximumRecordTime;
+    },
+
+    /**
+     * @return {!Array.<!WebInspector.TracingModel.Process>}
+     */
+    sortedProcesses: function()
+    {
+        return WebInspector.TracingModel.NamedObject._sort(this._processById.valuesArray());
+    },
+
+    /**
+     * @param {string} name
+     * @return {?WebInspector.TracingModel.Process}
+     */
+    processByName: function(name)
+    {
+        return this._processByName.get(name);
+    },
+
+    /**
+     * @param {string} processName
+     * @param {string} threadName
+     * @return {?WebInspector.TracingModel.Thread}
+     */
+    threadByName: function(processName, threadName)
+    {
+        var process = this.processByName(processName);
+        return process && process.threadByName(threadName);
+    },
+
+    _processPendingAsyncEvents: function()
+    {
+        this._asyncEvents.stableSort(WebInspector.TracingModel.Event.compareStartTime);
+        for (var i = 0; i < this._asyncEvents.length; ++i) {
+            var event = this._asyncEvents[i];
+            if (WebInspector.TracingModel.isNestableAsyncPhase(event.phase))
+                this._addNestableAsyncEvent(event);
+            else
+                this._addAsyncEvent(event);
+        }
+        this._asyncEvents = [];
+        this._closeOpenAsyncEvents();
+    },
+
+    _closeOpenAsyncEvents: function()
+    {
+        for (var event of this._openAsyncEvents.values()) {
+            event.setEndTime(this._maximumRecordTime);
+            // FIXME: remove this once we figure a better way to convert async console
+            // events to sync [waterfall] timeline records.
+            event.steps[0].setEndTime(this._maximumRecordTime);
+        }
+        this._openAsyncEvents.clear();
+
+        for (var eventStack of this._openNestableAsyncEvents.values()) {
+            while (eventStack.length)
+                eventStack.pop().setEndTime(this._maximumRecordTime);
+        }
+        this._openNestableAsyncEvents.clear();
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} event
+     */
+    _addNestableAsyncEvent: function(event)
+    {
+        var phase = WebInspector.TracingModel.Phase;
+        var key = event.categoriesString + "." + event.id;
+        var openEventsStack = this._openNestableAsyncEvents.get(key);
+
+        switch (event.phase) {
+        case phase.NestableAsyncBegin:
+            if (!openEventsStack) {
+                openEventsStack = [];
+                this._openNestableAsyncEvents.set(key, openEventsStack);
+            }
+            var asyncEvent = new WebInspector.TracingModel.AsyncEvent(event);
+            openEventsStack.push(asyncEvent);
+            event.thread._addAsyncEvent(asyncEvent);
+            break;
+
+        case phase.NestableAsyncInstant:
+            if (openEventsStack && openEventsStack.length)
+                openEventsStack.peekLast()._addStep(event);
+            break;
+
+        case phase.NestableAsyncEnd:
+            if (!openEventsStack || !openEventsStack.length)
+                break;
+            var top = openEventsStack.pop();
+            if (top.name !== event.name) {
+                console.error(`Begin/end event mismatch for nestable async event, ${top.name} vs. ${event.name}, key: ${key}`);
+                break;
+            }
+            top._addStep(event);
+        }
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} event
+     */
+    _addAsyncEvent: function(event)
+    {
+        var phase = WebInspector.TracingModel.Phase;
+        var key = event.categoriesString + "." + event.name + "." + event.id;
+        var asyncEvent = this._openAsyncEvents.get(key);
+
+        if (event.phase === phase.AsyncBegin) {
+            if (asyncEvent) {
+                console.error(`Event ${event.name} has already been started`);
+                return;
+            }
+            asyncEvent = new WebInspector.TracingModel.AsyncEvent(event);
+            this._openAsyncEvents.set(key, asyncEvent);
+            event.thread._addAsyncEvent(asyncEvent);
+            return;
+        }
+        if (!asyncEvent) {
+            // Quietly ignore stray async events, we're probably too late for the start.
+            return;
+        }
+        if (event.phase === phase.AsyncEnd) {
+            asyncEvent._addStep(event);
+            this._openAsyncEvents.delete(key);
+            return;
+        }
+        if (event.phase === phase.AsyncStepInto || event.phase === phase.AsyncStepPast) {
+            var lastStep = asyncEvent.steps.peekLast();
+            if (lastStep.phase !== phase.AsyncBegin && lastStep.phase !== event.phase) {
+                console.assert(false, "Async event step phase mismatch: " + lastStep.phase + " at " + lastStep.startTime + " vs. " + event.phase + " at " + event.startTime);
+                return;
+            }
+            asyncEvent._addStep(event);
+            return;
+        }
+        console.assert(false, "Invalid async event phase");
+    },
+
+    /**
+     * @param {string} str
+     * @return {!Set<string>}
+     */
+    _parsedCategoriesForString: function(str)
+    {
+        var parsedCategories = this._parsedCategories.get(str);
+        if (!parsedCategories) {
+            parsedCategories = new Set(str.split(","));
+            this._parsedCategories.set(str, parsedCategories);
+        }
+        return parsedCategories;
+    }
+}
+
+/**
+ * @constructor
+ * @param {string} categories
+ * @param {string} name
+ * @param {!WebInspector.TracingModel.Phase} phase
+ * @param {number} startTime
+ * @param {!WebInspector.TracingModel.Thread} thread
+ */
+WebInspector.TracingModel.Event = function(categories, name, phase, startTime, thread)
+{
+    /** @type {string} */
+    this.categoriesString = categories;
+    /** @type {!Set<string>} */
+    this._parsedCategories = thread._model._parsedCategoriesForString(categories);
+    /** @type {string} */
+    this.name = name;
+    /** @type {!WebInspector.TracingModel.Phase} */
+    this.phase = phase;
+    /** @type {number} */
+    this.startTime = startTime;
+    /** @type {!WebInspector.TracingModel.Thread} */
+    this.thread = thread;
+    /** @type {!Object} */
+    this.args = {};
+
+    /** @type {?string} */
+    this.warning = null;
+    /** @type {?WebInspector.TracingModel.Event} */
+    this.initiator = null;
+    /** @type {?Array<!RuntimeAgent.CallFrame>} */
+    this.stackTrace = null;
+    /** @type {?Element} */
+    this.previewElement = null;
+    /** @type {?string} */
+    this.url = null;
+    /** @type {number} */
+    this.backendNodeId = 0;
+
+    /** @type {number} */
+    this.selfTime = 0;
+}
+
+/**
+ * @param {!WebInspector.TracingManager.EventPayload} payload
+ * @param {!WebInspector.TracingModel.Thread} thread
+ * @return {!WebInspector.TracingModel.Event}
+ */
+WebInspector.TracingModel.Event.fromPayload = function(payload, thread)
+{
+    var event = new WebInspector.TracingModel.Event(payload.cat, payload.name, /** @type {!WebInspector.TracingModel.Phase} */ (payload.ph), payload.ts / 1000, thread);
+    if (payload.args)
+        event.addArgs(payload.args);
+    else
+        console.error("Missing mandatory event argument 'args' at " + payload.ts / 1000);
+    if (typeof payload.dur === "number")
+        event.setEndTime((payload.ts + payload.dur) / 1000);
+    var id = WebInspector.TracingModel._extractId(payload);
+    if (typeof id !== "undefined")
+        event.id = id;
+    if (payload.bind_id)
+        event.bind_id = payload.bind_id;
+
+    return event;
+}
+
+WebInspector.TracingModel.Event.prototype = {
+    /**
+     * @param {string} categoryName
+     * @return {boolean}
+     */
+    hasCategory: function(categoryName)
+    {
+        return this._parsedCategories.has(categoryName);
+    },
+
+    /**
+     * @param {number} endTime
+     */
+    setEndTime: function(endTime)
+    {
+        if (endTime < this.startTime) {
+            console.assert(false, "Event out of order: " + this.name);
+            return;
+        }
+        this.endTime = endTime;
+        this.duration = endTime - this.startTime;
+    },
+
+    /**
+     * @param {!Object} args
+     */
+    addArgs: function(args)
+    {
+        // Shallow copy args to avoid modifying original payload which may be saved to file.
+        for (var name in args) {
+            if (name in this.args)
+                console.error("Same argument name (" + name +  ") is used for begin and end phases of " + this.name);
+            this.args[name] = args[name];
+        }
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} endEvent
+     */
+    _complete: function(endEvent)
+    {
+        if (endEvent.args)
+            this.addArgs(endEvent.args);
+        else
+            console.error("Missing mandatory event argument 'args' at " + endEvent.startTime);
+        this.setEndTime(endEvent.startTime);
+    },
+
+    /**
+     * @param {?function():!Promise.<?string>} backingStorage
+     */
+    _setBackingStorage: function(backingStorage)
+    {
+    }
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} a
+ * @param {!WebInspector.TracingModel.Event} b
+ * @return {number}
+ */
+WebInspector.TracingModel.Event.compareStartTime = function(a, b)
+{
+    return a.startTime - b.startTime;
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} a
+ * @param {!WebInspector.TracingModel.Event} b
+ * @return {number}
+ */
+WebInspector.TracingModel.Event.compareStartAndEndTime = function(a, b)
+{
+    return a.startTime - b.startTime || (b.endTime !== undefined && a.endTime !== undefined && b.endTime - a.endTime) || 0;
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} a
+ * @param {!WebInspector.TracingModel.Event} b
+ * @return {number}
+ */
+WebInspector.TracingModel.Event.orderedCompareStartTime = function(a, b)
+{
+    // Array.mergeOrdered coalesces objects if comparator returns 0.
+    // To change this behavior this comparator return -1 in the case events
+    // startTime's are equal, so both events got placed into the result array.
+    return a.startTime - b.startTime || a.ordinal - b.ordinal || -1;
+}
+
+/**
+ * @constructor
+ * @extends {WebInspector.TracingModel.Event}
+ * @param {string} category
+ * @param {string} name
+ * @param {number} startTime
+ * @param {!WebInspector.TracingModel.Thread} thread
+ */
+WebInspector.TracingModel.ObjectSnapshot = function(category, name, startTime, thread)
+{
+    WebInspector.TracingModel.Event.call(this, category, name, WebInspector.TracingModel.Phase.SnapshotObject, startTime, thread);
+}
+
+/**
+ * @param {!WebInspector.TracingManager.EventPayload} payload
+ * @param {!WebInspector.TracingModel.Thread} thread
+ * @return {!WebInspector.TracingModel.ObjectSnapshot}
+ */
+WebInspector.TracingModel.ObjectSnapshot.fromPayload = function(payload, thread)
+{
+    var snapshot = new WebInspector.TracingModel.ObjectSnapshot(payload.cat, payload.name, payload.ts / 1000, thread);
+    var id = WebInspector.TracingModel._extractId(payload);
+    if (typeof id !== "undefined")
+        snapshot.id = id;
+    if (!payload.args || !payload.args["snapshot"]) {
+        console.error("Missing mandatory 'snapshot' argument at " + payload.ts / 1000);
+        return snapshot;
+    }
+    if (payload.args)
+        snapshot.addArgs(payload.args);
+    return snapshot;
+}
+
+WebInspector.TracingModel.ObjectSnapshot.prototype = {
+    /**
+     * @param {function(?)} callback
+     */
+    requestObject: function(callback)
+    {
+        var snapshot = this.args["snapshot"];
+        if (snapshot) {
+            callback(snapshot);
+            return;
+        }
+        this._backingStorage().then(onRead, callback.bind(null, null));
+        /**
+         * @param {?string} result
+         */
+        function onRead(result)
+        {
+            if (!result) {
+                callback(null);
+                return;
+            }
+            try {
+                var payload = JSON.parse(result);
+                callback(payload["args"]["snapshot"]);
+            } catch (e) {
+                WebInspector.console.error("Malformed event data in backing storage");
+                callback(null);
+            }
+        }
+    },
+
+    /**
+     * @return {!Promise<?>}
+     */
+    objectPromise: function()
+    {
+        if (!this._objectPromise)
+            this._objectPromise = new Promise(this.requestObject.bind(this));
+        return this._objectPromise;
+    },
+
+    /**
+     * @override
+     * @param {?function():!Promise.<?>} backingStorage
+     */
+    _setBackingStorage: function(backingStorage)
+    {
+        if (!backingStorage)
+            return;
+        this._backingStorage = backingStorage;
+        this.args = {};
+    },
+
+    __proto__: WebInspector.TracingModel.Event.prototype
+}
+
+/**
+ * @constructor
+ * @param {!WebInspector.TracingModel.Event} startEvent
+ * @extends {WebInspector.TracingModel.Event}
+ */
+WebInspector.TracingModel.AsyncEvent = function(startEvent)
+{
+    WebInspector.TracingModel.Event.call(this, startEvent.categoriesString, startEvent.name, startEvent.phase, startEvent.startTime, startEvent.thread)
+    this.addArgs(startEvent.args);
+    this.steps = [startEvent];
+}
+
+WebInspector.TracingModel.AsyncEvent.prototype = {
+    /**
+     * @param {!WebInspector.TracingModel.Event} event
+     */
+    _addStep: function(event)
+    {
+        this.steps.push(event);
+        if (event.phase === WebInspector.TracingModel.Phase.AsyncEnd || event.phase === WebInspector.TracingModel.Phase.NestableAsyncEnd) {
+            this.setEndTime(event.startTime);
+            // FIXME: ideally, we shouldn't do this, but this makes the logic of converting
+            // async console events to sync ones much simpler.
+            this.steps[0].setEndTime(event.startTime);
+        }
+    },
+
+    __proto__: WebInspector.TracingModel.Event.prototype
+}
+
+/**
+ * @constructor
+ */
+WebInspector.TracingModel.NamedObject = function()
+{
+}
+
+WebInspector.TracingModel.NamedObject.prototype =
+{
+    /**
+     * @param {string} name
+     */
+    _setName: function(name)
+    {
+        this._name = name;
+    },
+
+    /**
+     * @return {string}
+     */
+    name: function()
+    {
+        return this._name;
+    },
+
+    /**
+     * @param {number} sortIndex
+     */
+    _setSortIndex: function(sortIndex)
+    {
+        this._sortIndex = sortIndex;
+    },
+}
+
+/**
+ * @param {!Array.<!WebInspector.TracingModel.NamedObject>} array
+ */
+WebInspector.TracingModel.NamedObject._sort = function(array)
+{
+    /**
+     * @param {!WebInspector.TracingModel.NamedObject} a
+     * @param {!WebInspector.TracingModel.NamedObject} b
+     */
+    function comparator(a, b)
+    {
+        return a._sortIndex !== b._sortIndex ? a._sortIndex - b._sortIndex : a.name().localeCompare(b.name());
+    }
+    return array.sort(comparator);
+}
+
+/**
+ * @constructor
+ * @extends {WebInspector.TracingModel.NamedObject}
+ * @param {!WebInspector.TracingModel} model
+ * @param {number} id
+ */
+WebInspector.TracingModel.Process = function(model, id)
+{
+    WebInspector.TracingModel.NamedObject.call(this);
+    this._setName("Process " + id);
+    this._id = id;
+    /** @type {!Map<number, !WebInspector.TracingModel.Thread>} */
+    this._threads = new Map();
+    this._threadByName = new Map();
+    this._model = model;
+}
+
+WebInspector.TracingModel.Process.prototype = {
+    /**
+     * @return {number}
+     */
+    id: function()
+    {
+        return this._id;
+    },
+
+    /**
+     * @param {number} id
+     * @return {!WebInspector.TracingModel.Thread}
+     */
+    threadById: function(id)
+    {
+        var thread = this._threads.get(id);
+        if (!thread) {
+            thread = new WebInspector.TracingModel.Thread(this, id);
+            this._threads.set(id, thread);
+        }
+        return thread;
+    },
+
+    /**
+     * @param {string} name
+     * @return {?WebInspector.TracingModel.Thread}
+     */
+    threadByName: function(name)
+    {
+        return this._threadByName.get(name) || null;
+    },
+
+    /**
+     * @param {string} name
+     * @param {!WebInspector.TracingModel.Thread} thread
+     */
+    _setThreadByName: function(name, thread)
+    {
+        this._threadByName.set(name, thread);
+    },
+
+    /**
+     * @param {!WebInspector.TracingManager.EventPayload} payload
+     * @return {?WebInspector.TracingModel.Event} event
+     */
+    _addEvent: function(payload)
+    {
+        return this.threadById(payload.tid)._addEvent(payload);
+    },
+
+    /**
+     * @return {!Array.<!WebInspector.TracingModel.Thread>}
+     */
+    sortedThreads: function()
+    {
+        return WebInspector.TracingModel.NamedObject._sort(this._threads.valuesArray());
+    },
+
+    __proto__: WebInspector.TracingModel.NamedObject.prototype
+}
+
+/**
+ * @constructor
+ * @extends {WebInspector.TracingModel.NamedObject}
+ * @param {!WebInspector.TracingModel.Process} process
+ * @param {number} id
+ */
+WebInspector.TracingModel.Thread = function(process, id)
+{
+    WebInspector.TracingModel.NamedObject.call(this);
+    this._process = process;
+    this._setName("Thread " + id);
+    this._events = [];
+    this._asyncEvents = [];
+    this._id = id;
+    this._model = process._model;
+}
+
+WebInspector.TracingModel.Thread.prototype = {
+    tracingComplete: function()
+    {
+        this._asyncEvents.stableSort(WebInspector.TracingModel.Event.compareStartAndEndTime);
+        this._events.stableSort(WebInspector.TracingModel.Event.compareStartTime);
+        var phases = WebInspector.TracingModel.Phase;
+        var stack = [];
+        for (var i = 0; i < this._events.length; ++i) {
+            var e = this._events[i];
+            e.ordinal = i;
+            switch (e.phase) {
+            case phases.End:
+                this._events[i] = null;  // Mark for removal.
+                // Quietly ignore unbalanced close events, they're legit (we could have missed start one).
+                if (!stack.length)
+                    continue;
+                var top = stack.pop();
+                if (top.name !== e.name || top.categoriesString !== e.categoriesString)
+                    console.error("B/E events mismatch at " + top.startTime + " (" + top.name + ") vs. " + e.startTime + " (" + e.name + ")");
+                else
+                    top._complete(e);
+                break;
+            case phases.Begin:
+                stack.push(e);
+                break;
+            }
+        }
+        while (stack.length)
+            stack.pop().setEndTime(this._model.maximumRecordTime());
+        this._events.remove(null, false);
+    },
+
+    /**
+     * @param {!WebInspector.TracingManager.EventPayload} payload
+     * @return {?WebInspector.TracingModel.Event} event
+     */
+    _addEvent: function(payload)
+    {
+        var event = payload.ph === WebInspector.TracingModel.Phase.SnapshotObject
+            ? WebInspector.TracingModel.ObjectSnapshot.fromPayload(payload, this)
+            : WebInspector.TracingModel.Event.fromPayload(payload, this);
+        if (WebInspector.TracingModel.isTopLevelEvent(event)) {
+            // Discard nested "top-level" events.
+            if (this._lastTopLevelEvent && this._lastTopLevelEvent.endTime > event.startTime)
+                return null;
+            this._lastTopLevelEvent = event;
+        }
+        this._events.push(event);
+        return event;
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.AsyncEvent} asyncEvent
+     */
+    _addAsyncEvent: function(asyncEvent)
+    {
+        this._asyncEvents.push(asyncEvent);
+    },
+
+    /**
+     * @override
+     * @param {string} name
+     */
+    _setName: function(name)
+    {
+        WebInspector.TracingModel.NamedObject.prototype._setName.call(this, name);
+        this._process._setThreadByName(name, this);
+    },
+
+    /**
+     * @return {number}
+     */
+    id: function()
+    {
+        return this._id;
+    },
+
+    /**
+     * @return {!WebInspector.TracingModel.Process}
+     */
+    process: function()
+    {
+        return this._process;
+    },
+
+    /**
+     * @return {!Array.<!WebInspector.TracingModel.Event>}
+     */
+    events: function()
+    {
+        return this._events;
+    },
+
+    /**
+     * @return {!Array.<!WebInspector.TracingModel.AsyncEvent>}
+     */
+    asyncEvents: function()
+    {
+        return this._asyncEvents;
+    },
+
+    __proto__: WebInspector.TracingModel.NamedObject.prototype
+}
+
+},{}],253:[function(require,module,exports){
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @constructor
+ * @extends {WebInspector.VBox}
+ * @param {!WebInspector.TimelineModel} model
+ * @param {!Array<!WebInspector.TimelineModel.Filter>} filters
+ */
+WebInspector.TimelineTreeView = function(model, filters)
+{
+    WebInspector.VBox.call(this);
+    this.element.classList.add("timeline-tree-view");
+
+    this._model = model;
+    this._linkifier = new WebInspector.Linkifier();
+
+    this._filters = filters.slice();
+
+    var columns = [];
+    this._populateColumns(columns);
+
+    var mainView = new WebInspector.VBox();
+    this._populateToolbar(mainView.element);
+    this._dataGrid = new WebInspector.SortableDataGrid(columns);
+    this._dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this._sortingChanged, this);
+    this._dataGrid.element.addEventListener("mousemove", this._onMouseMove.bind(this), true)
+    this._dataGrid.setResizeMethod(WebInspector.DataGrid.ResizeMethod.Last);
+    this._dataGrid.asWidget().show(mainView.element);
+
+    this._splitWidget = new WebInspector.SplitWidget(true, true, "timelineTreeViewDetailsSplitWidget");
+    this._splitWidget.show(this.element);
+    this._splitWidget.setMainWidget(mainView);
+
+    this._detailsView = new WebInspector.VBox();
+    this._detailsView.element.classList.add("timeline-details-view", "timeline-details-view-body");
+    this._splitWidget.setSidebarWidget(this._detailsView);
+    this._dataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._updateDetailsForSelection, this);
+
+    /** @type {?WebInspector.TimelineProfileTree.Node|undefined} */
+    this._lastSelectedNode;
+}
+
+WebInspector.TimelineTreeView.prototype = {
+    /**
+     * @param {!WebInspector.TimelineSelection} selection
+     */
+    updateContents: function(selection)
+    {
+        this.setRange(selection.startTime(), selection.endTime());
+    },
+
+    /**
+     * @param {number} startTime
+     * @param {number} endTime
+     */
+    setRange: function(startTime, endTime)
+    {
+        this._startTime = startTime;
+        this._endTime = endTime;
+        this._refreshTree();
+    },
+
+    /**
+     * @return {boolean}
+     */
+    _exposePercentages: function()
+    {
+        return false;
+    },
+
+    /**
+     * @param {!Element} parent
+     */
+    _populateToolbar: function(parent) { },
+
+    /**
+     * @param {?WebInspector.TimelineProfileTree.Node} node
+     */
+    _onHover: function(node) { },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} event
+     * @return {?Element}
+     */
+    _linkifyLocation: function(event)
+    {
+        var target = this._model.targetByEvent(event);
+        if (!target)
+            return null;
+        var frame = WebInspector.TimelineProfileTree.eventStackFrame(event);
+        if (!frame)
+            return null;
+        return this._linkifier.maybeLinkifyConsoleCallFrame(target, frame);
+    },
+
+    /**
+     * @param {!WebInspector.TimelineProfileTree.Node} treeNode
+     * @param {boolean} suppressSelectedEvent
+     */
+    selectProfileNode: function(treeNode, suppressSelectedEvent)
+    {
+        var pathToRoot = [];
+        for (var node = treeNode; node; node = node.parent)
+            pathToRoot.push(node);
+        for (var i = pathToRoot.length - 1; i > 0; --i) {
+            var gridNode = this._dataGridNodeForTreeNode(pathToRoot[i]);
+            if (gridNode && gridNode.dataGrid)
+                gridNode.expand();
+        }
+        var gridNode = this._dataGridNodeForTreeNode(treeNode);
+        if (gridNode.dataGrid) {
+            gridNode.reveal();
+            gridNode.select(suppressSelectedEvent);
+        }
+    },
+
+    _refreshTree: function()
+    {
+        this._linkifier.reset();
+        this._dataGrid.rootNode().removeChildren();
+        var tree = this._buildTree();
+        if (!tree.children)
+            return;
+        var maxSelfTime = 0;
+        var maxTotalTime = 0;
+        for (var child of tree.children.values()) {
+            maxSelfTime = Math.max(maxSelfTime, child.selfTime);
+            maxTotalTime = Math.max(maxTotalTime, child.totalTime);
+        }
+        for (var child of tree.children.values()) {
+            // Exclude the idle time off the total calculation.
+            var gridNode = new WebInspector.TimelineTreeView.TreeGridNode(child, tree.totalTime, maxSelfTime, maxTotalTime, this);
+            this._dataGrid.insertChild(gridNode);
+        }
+        this._sortingChanged();
+        this._updateDetailsForSelection();
+    },
+
+    /**
+     * @return {!WebInspector.TimelineProfileTree.Node}
+     */
+    _buildTree: function()
+    {
+        throw new Error("Not Implemented");
+    },
+
+    /**
+     * @param {function(!WebInspector.TracingModel.Event):(string|symbol)=} eventIdCallback
+     * @return {!WebInspector.TimelineProfileTree.Node}
+     */
+    _buildTopDownTree: function(eventIdCallback)
+    {
+        return WebInspector.TimelineProfileTree.buildTopDown(this._model.mainThreadEvents(), this._filters, this._startTime, this._endTime, eventIdCallback)
+    },
+
+    /**
+     * @param {!Array<!WebInspector.DataGrid.ColumnDescriptor>} columns
+     */
+    _populateColumns: function(columns)
+    {
+        columns.push({id: "self", title: WebInspector.UIString("Self Time"), width: "110px", fixedWidth: true, sortable: true});
+        columns.push({id: "total", title: WebInspector.UIString("Total Time"), width: "110px", fixedWidth: true, sortable: true});
+        columns.push({id: "activity", title: WebInspector.UIString("Activity"), disclosure: true, sortable: true});
+    },
+
+    _sortingChanged: function()
+    {
+        var columnIdentifier = this._dataGrid.sortColumnIdentifier();
+        if (!columnIdentifier)
+            return;
+        var sortFunction;
+        switch (columnIdentifier) {
+        case "startTime":
+            sortFunction = compareStartTime;
+            break;
+        case "self":
+            sortFunction = compareNumericField.bind(null, "selfTime");
+            break;
+        case "total":
+            sortFunction = compareNumericField.bind(null, "totalTime");
+            break;
+        case "activity":
+            sortFunction = compareName;
+            break;
+        default:
+            console.assert(false, "Unknown sort field: " + columnIdentifier);
+            return;
+        }
+        this._dataGrid.sortNodes(sortFunction, !this._dataGrid.isSortOrderAscending());
+
+        /**
+         * @param {string} field
+         * @param {!WebInspector.DataGridNode} a
+         * @param {!WebInspector.DataGridNode} b
+         * @return {number}
+         */
+        function compareNumericField(field, a, b)
+        {
+            var nodeA = /** @type {!WebInspector.TimelineTreeView.TreeGridNode} */ (a);
+            var nodeB = /** @type {!WebInspector.TimelineTreeView.TreeGridNode} */ (b);
+            return nodeA._profileNode[field] - nodeB._profileNode[field];
+        }
+
+        /**
+         * @param {!WebInspector.DataGridNode} a
+         * @param {!WebInspector.DataGridNode} b
+         * @return {number}
+         */
+        function compareStartTime(a, b)
+        {
+            var nodeA = /** @type {!WebInspector.TimelineTreeView.TreeGridNode} */ (a);
+            var nodeB = /** @type {!WebInspector.TimelineTreeView.TreeGridNode} */ (b);
+            return nodeA._profileNode.event.startTime - nodeB._profileNode.event.startTime;
+        }
+
+        /**
+         * @param {!WebInspector.DataGridNode} a
+         * @param {!WebInspector.DataGridNode} b
+         * @return {number}
+         */
+        function compareName(a, b)
+        {
+            var nodeA = /** @type {!WebInspector.TimelineTreeView.TreeGridNode} */ (a);
+            var nodeB = /** @type {!WebInspector.TimelineTreeView.TreeGridNode} */ (b);
+            var nameA = WebInspector.TimelineTreeView.eventNameForSorting(nodeA._profileNode.event);
+            var nameB = WebInspector.TimelineTreeView.eventNameForSorting(nodeB._profileNode.event);
+            return nameA.localeCompare(nameB);
+        }
+    },
+
+    _updateDetailsForSelection: function()
+    {
+        var selectedNode = this._dataGrid.selectedNode ? /** @type {!WebInspector.TimelineTreeView.TreeGridNode} */ (this._dataGrid.selectedNode)._profileNode : null;
+        if (selectedNode === this._lastSelectedNode)
+            return;
+        this._lastSelectedNode = selectedNode;
+        this._detailsView.detachChildWidgets();
+        this._detailsView.element.removeChildren();
+        if (!selectedNode || !this._showDetailsForNode(selectedNode)) {
+            var banner = this._detailsView.element.createChild("div", "full-widget-dimmed-banner");
+            banner.createTextChild(WebInspector.UIString("Select item for details."));
+        }
+    },
+
+    /**
+     * @param {!WebInspector.TimelineProfileTree.Node} node
+     * @return {boolean}
+     */
+    _showDetailsForNode: function(node)
+    {
+        return false;
+    },
+
+    /**
+     * @param {!Event} event
+     */
+    _onMouseMove: function(event)
+    {
+        var gridNode = event.target && (event.target instanceof Node)
+            ? /** @type {?WebInspector.TimelineTreeView.TreeGridNode} */ (this._dataGrid.dataGridNodeFromNode(/** @type {!Node} */ (event.target)))
+            : null;
+        var profileNode = gridNode && gridNode._profileNode;
+        if (profileNode === this._lastHoveredProfileNode)
+            return;
+        this._lastHoveredProfileNode = profileNode;
+        this._onHover(profileNode);
+    },
+
+    /**
+     * @param {!WebInspector.TimelineProfileTree.Node} treeNode
+     * @return {?WebInspector.TimelineTreeView.GridNode}
+     */
+    _dataGridNodeForTreeNode: function(treeNode)
+    {
+        return treeNode[WebInspector.TimelineTreeView.TreeGridNode._gridNodeSymbol] || null;
+    },
+
+    __proto__: WebInspector.VBox.prototype
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @return {string}
+ */
+WebInspector.TimelineTreeView.eventNameForSorting = function(event)
+{
+    if (event.name === WebInspector.TimelineModel.RecordType.JSFrame) {
+        var data = event.args["data"];
+        return  data["functionName"] + "@" + (data["scriptId"] || data["url"] || "");
+    }
+    return event.name + ":@" + WebInspector.TimelineProfileTree.eventURL(event);
+}
+
+/**
+ * @constructor
+ * @extends {WebInspector.SortableDataGridNode}
+ * @param {!WebInspector.TimelineProfileTree.Node} profileNode
+ * @param {number} grandTotalTime
+ * @param {number} maxSelfTime
+ * @param {number} maxTotalTime
+ * @param {!WebInspector.TimelineTreeView} treeView
+ */
+WebInspector.TimelineTreeView.GridNode = function(profileNode, grandTotalTime, maxSelfTime, maxTotalTime, treeView)
+{
+    this._populated = false;
+    this._profileNode = profileNode;
+    this._treeView = treeView;
+    this._grandTotalTime = grandTotalTime;
+    this._maxSelfTime = maxSelfTime;
+    this._maxTotalTime = maxTotalTime;
+    WebInspector.SortableDataGridNode.call(this, null, false);
+}
+
+WebInspector.TimelineTreeView.GridNode.prototype = {
+    /**
+     * @override
+     * @param {string} columnIdentifier
+     * @return {!Element}
+     */
+    createCell: function(columnIdentifier)
+    {
+        if (columnIdentifier === "activity")
+            return this._createNameCell(columnIdentifier);
+        return this._createValueCell(columnIdentifier) || WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
+    },
+
+    /**
+     * @param {string} columnIdentifier
+     * @return {!Element}
+     */
+    _createNameCell: function(columnIdentifier)
+    {
+        var cell = this.createTD(columnIdentifier);
+        var container = cell.createChild("div", "name-container");
+        var icon = container.createChild("div", "activity-icon");
+        var name = container.createChild("div", "activity-name");
+        var event = this._profileNode.event;
+        if (this._profileNode.isGroupNode()) {
+            var treeView = /** @type {!WebInspector.AggregatedTimelineTreeView} */ (this._treeView);
+            var info = treeView._displayInfoForGroupNode(this._profileNode);
+            name.textContent = info.name;
+            icon.style.backgroundColor = info.color;
+        } else if (event) {
+            var data = event.args["data"];
+            var deoptReason = data && data["deoptReason"];
+            if (deoptReason)
+                container.createChild("div", "activity-warning").title = WebInspector.UIString("Not optimized: %s", deoptReason);
+            name.textContent = event.name === WebInspector.TimelineModel.RecordType.JSFrame
+                ? WebInspector.beautifyFunctionName(event.args["data"]["functionName"])
+                : WebInspector.TimelineUIUtils.eventTitle(event);
+            var link = this._treeView._linkifyLocation(event);
+            if (link)
+                container.createChild("div", "activity-link").appendChild(link);
+            icon.style.backgroundColor = WebInspector.TimelineUIUtils.eventColor(event);
+        }
+        return cell;
+    },
+
+    /**
+     * @param {string} columnIdentifier
+     * @return {?Element}
+     */
+    _createValueCell: function(columnIdentifier)
+    {
+        if (columnIdentifier !== "self" && columnIdentifier !== "total" && columnIdentifier !== "startTime")
+            return null;
+
+        var showPercents = false;
+        var value;
+        var maxTime;
+        switch (columnIdentifier) {
+        case "startTime":
+            value = this._profileNode.event.startTime - this._treeView._model.minimumRecordTime();
+            break;
+        case "self":
+            value = this._profileNode.selfTime;
+            maxTime = this._maxSelfTime;
+            showPercents = true;
+            break;
+        case "total":
+            value = this._profileNode.totalTime;
+            maxTime = this._maxTotalTime;
+            showPercents = true;
+            break;
+        default:
+            return null;
+        }
+        var cell = this.createTD(columnIdentifier);
+        cell.className = "numeric-column";
+        var textDiv = cell.createChild("div");
+        textDiv.createChild("span").textContent = WebInspector.UIString("%.1f\u2009ms", value);
+
+        if (showPercents && this._treeView._exposePercentages())
+            textDiv.createChild("span", "percent-column").textContent = WebInspector.UIString("%.1f\u2009%%", value / this._grandTotalTime * 100);
+        if (maxTime) {
+            textDiv.classList.add("background-percent-bar");
+            cell.createChild("div", "background-bar-container").createChild("div", "background-bar").style.width = (value * 100 / maxTime).toFixed(1) + "%";
+        }
+        return cell;
+    },
+
+    __proto__: WebInspector.SortableDataGridNode.prototype
+}
+
+/**
+ * @constructor
+ * @extends {WebInspector.TimelineTreeView.GridNode}
+ * @param {!WebInspector.TimelineProfileTree.Node} profileNode
+ * @param {number} grandTotalTime
+ * @param {number} maxSelfTime
+ * @param {number} maxTotalTime
+ * @param {!WebInspector.TimelineTreeView} treeView
+ */
+WebInspector.TimelineTreeView.TreeGridNode = function(profileNode, grandTotalTime, maxSelfTime, maxTotalTime, treeView)
+{
+    WebInspector.TimelineTreeView.GridNode.call(this, profileNode, grandTotalTime, maxSelfTime, maxTotalTime, treeView);
+    this.hasChildren = this._profileNode.children ? this._profileNode.children.size > 0 : false;
+    profileNode[WebInspector.TimelineTreeView.TreeGridNode._gridNodeSymbol] = this;
+}
+
+WebInspector.TimelineTreeView.TreeGridNode._gridNodeSymbol = Symbol("treeGridNode");
+
+WebInspector.TimelineTreeView.TreeGridNode.prototype = {
+    /**
+     * @override
+     */
+    populate: function()
+    {
+        if (this._populated)
+            return;
+        this._populated = true;
+        if (!this._profileNode.children)
+            return;
+        for (var node of this._profileNode.children.values()) {
+            var gridNode = new WebInspector.TimelineTreeView.TreeGridNode(node, this._grandTotalTime, this._maxSelfTime, this._maxTotalTime, this._treeView);
+            this.insertChildOrdered(gridNode);
+        }
+    },
+
+    __proto__: WebInspector.TimelineTreeView.GridNode.prototype
 };
 
-module.exports=exports['default'];
 
-
-},{"../exception":250,"../utils":263}],254:[function(require,module,exports){
-'use strict';
-
-exports.__esModule=true;
-
-
-function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{'default':obj};}
-
-var _exception=require('../exception');
-
-var _exception2=_interopRequireDefault(_exception);
-
-exports['default']=function(instance){
-instance.registerHelper('helperMissing',function(){
-if(arguments.length===1){
-
-return undefined;
-}else{
-
-throw new _exception2['default']('Missing helper: "'+arguments[arguments.length-1].name+'"');
+/**
+ * @constructor
+ * @extends {WebInspector.TimelineTreeView}
+ * @param {!WebInspector.TimelineModel} model
+ * @param {!Array<!WebInspector.TimelineModel.Filter>} filters
+ */
+WebInspector.AggregatedTimelineTreeView = function(model, filters)
+{
+    this._groupBySetting = WebInspector.settings.createSetting("timelineTreeGroupBy", WebInspector.TimelineAggregator.GroupBy.Category);
+    WebInspector.TimelineTreeView.call(this, model, filters);
+    var nonessentialEvents = [
+        WebInspector.TimelineModel.RecordType.EventDispatch,
+        WebInspector.TimelineModel.RecordType.FunctionCall,
+        WebInspector.TimelineModel.RecordType.TimerFire
+    ];
+    this._filters.push(new WebInspector.ExclusiveNameFilter(nonessentialEvents));
+    this._stackView = new WebInspector.TimelineStackView(this);
+    this._stackView.addEventListener(WebInspector.TimelineStackView.Events.SelectionChanged, this._onStackViewSelectionChanged, this);
 }
-});
+
+WebInspector.AggregatedTimelineTreeView.prototype = {
+    /**
+     * @override
+     * @param {!WebInspector.TimelineSelection} selection
+     */
+    updateContents: function(selection)
+    {
+        this._updateExtensionResolver();
+        WebInspector.TimelineTreeView.prototype.updateContents.call(this, selection);
+        var rootNode = this._dataGrid.rootNode();
+        if (rootNode.children.length)
+            rootNode.children[0].revealAndSelect();
+    },
+
+    _updateExtensionResolver: function()
+    {
+        this._executionContextNamesByOrigin = new Map();
+        for (var target of WebInspector.targetManager.targets()) {
+            for (var context of target.runtimeModel.executionContexts())
+                this._executionContextNamesByOrigin.set(context.origin, context.name);
+        }
+    },
+
+    /**
+     * @param {!WebInspector.TimelineProfileTree.Node} node
+     * @return {!{name: string, color: string}}
+     */
+    _displayInfoForGroupNode: function(node)
+    {
+        var categories = WebInspector.TimelineUIUtils.categories();
+        var color = node.id ? WebInspector.TimelineUIUtils.eventColor(node.event) : categories["other"].color;
+
+        switch (this._groupBySetting.get()) {
+        case WebInspector.TimelineAggregator.GroupBy.Category:
+            var category = categories[node.id] || categories["other"];
+            return {name: category.title, color: category.color};
+
+        case WebInspector.TimelineAggregator.GroupBy.Domain:
+        case WebInspector.TimelineAggregator.GroupBy.Subdomain:
+            var name = node.id;
+            if (WebInspector.TimelineAggregator.isExtensionInternalURL(name))
+                name = WebInspector.UIString("[Chrome extensions overhead]");
+            else if (name.startsWith("chrome-extension"))
+                name = this._executionContextNamesByOrigin.get(name) || name;
+            return {
+                name: name || WebInspector.UIString("unattributed"),
+                color: color
+            };
+
+        case WebInspector.TimelineAggregator.GroupBy.EventName:
+            var name = node.event.name === WebInspector.TimelineModel.RecordType.JSFrame ?
+                WebInspector.UIString("JavaScript") : WebInspector.TimelineUIUtils.eventTitle(node.event);
+            return {
+                name: name,
+                color: node.event.name === WebInspector.TimelineModel.RecordType.JSFrame ?
+                    WebInspector.TimelineUIUtils.eventStyle(node.event).category.color : color
+            };
+
+        case WebInspector.TimelineAggregator.GroupBy.URL:
+            break;
+
+        default:
+            console.assert(false, "Unexpected aggregation type");
+        }
+        return {
+            name: node.id || WebInspector.UIString("unattributed"),
+            color: color
+        };
+    },
+
+    /**
+     * @override
+     * @param {!Element} parent
+     */
+    _populateToolbar: function(parent)
+    {
+        var panelToolbar = new WebInspector.Toolbar("", parent);
+        this._groupByCombobox = new WebInspector.ToolbarComboBox(this._onGroupByChanged.bind(this));
+        /**
+         * @param {string} name
+         * @param {string} id
+         * @this {WebInspector.TimelineTreeView}
+         */
+        function addGroupingOption(name, id)
+        {
+            var option = this._groupByCombobox.createOption(name, "", id);
+            this._groupByCombobox.addOption(option);
+            if (id === this._groupBySetting.get())
+                this._groupByCombobox.select(option);
+        }
+        addGroupingOption.call(this, WebInspector.UIString("No Grouping"), WebInspector.TimelineAggregator.GroupBy.None);
+        addGroupingOption.call(this, WebInspector.UIString("Group by Activity"), WebInspector.TimelineAggregator.GroupBy.EventName);
+        addGroupingOption.call(this, WebInspector.UIString("Group by Category"), WebInspector.TimelineAggregator.GroupBy.Category);
+        addGroupingOption.call(this, WebInspector.UIString("Group by Domain"), WebInspector.TimelineAggregator.GroupBy.Domain);
+        addGroupingOption.call(this, WebInspector.UIString("Group by Subdomain"), WebInspector.TimelineAggregator.GroupBy.Subdomain);
+        addGroupingOption.call(this, WebInspector.UIString("Group by URL"), WebInspector.TimelineAggregator.GroupBy.URL);
+        panelToolbar.appendToolbarItem(this._groupByCombobox);
+    },
+
+    /**
+     * @param {!WebInspector.TimelineProfileTree.Node} treeNode
+     * @return {!Array<!WebInspector.TimelineProfileTree.Node>}
+     */
+    _buildHeaviestStack: function(treeNode)
+    {
+        console.assert(!!treeNode.parent, "Attempt to build stack for tree root");
+        var result = [];
+        // Do not add root to the stack, as it's the tree itself.
+        for (var node = treeNode; node && node.parent; node = node.parent)
+            result.push(node);
+        result = result.reverse();
+        for (node = treeNode; node && node.children && node.children.size;) {
+            var children = Array.from(node.children.values());
+            node = children.reduce((a, b) => a.totalTime > b.totalTime ? a : b);
+            result.push(node);
+        }
+        return result;
+    },
+
+    /**
+     * @override
+     * @return {boolean}
+     */
+    _exposePercentages: function()
+    {
+        return true;
+    },
+
+    _onGroupByChanged: function()
+    {
+        this._groupBySetting.set(this._groupByCombobox.selectedOption().value);
+        this._refreshTree();
+    },
+
+    _onStackViewSelectionChanged: function()
+    {
+        var treeNode = this._stackView.selectedTreeNode();
+        if (treeNode)
+            this.selectProfileNode(treeNode, true);
+    },
+
+    /**
+     * @override
+     * @param {!WebInspector.TimelineProfileTree.Node} node
+     * @return {boolean}
+     */
+    _showDetailsForNode: function(node)
+    {
+        var stack = this._buildHeaviestStack(node);
+        this._stackView.setStack(stack, node);
+        this._stackView.show(this._detailsView.element);
+        return true;
+    },
+
+    /**
+     * @return {!WebInspector.TimelineAggregator}
+     */
+    _createAggregator: function()
+    {
+        return new WebInspector.TimelineAggregator(
+            event => WebInspector.TimelineUIUtils.eventStyle(event).title,
+            event => WebInspector.TimelineUIUtils.eventStyle(event).category.name
+        );
+    },
+
+    __proto__: WebInspector.TimelineTreeView.prototype,
 };
 
-module.exports=exports['default'];
-
-
-},{"../exception":250}],255:[function(require,module,exports){
-'use strict';
-
-exports.__esModule=true;
-
-var _utils=require('../utils');
-
-exports['default']=function(instance){
-instance.registerHelper('if',function(conditional,options){
-if(_utils.isFunction(conditional)){
-conditional=conditional.call(this);
+/**
+ * @constructor
+ * @extends {WebInspector.AggregatedTimelineTreeView}
+ * @param {!WebInspector.TimelineModel} model
+ * @param {!Array<!WebInspector.TimelineModel.Filter>} filters
+ */
+WebInspector.CallTreeTimelineTreeView = function(model, filters)
+{
+    WebInspector.AggregatedTimelineTreeView.call(this, model, filters);
+    this._dataGrid.markColumnAsSortedBy("total", WebInspector.DataGrid.Order.Descending);
 }
 
+WebInspector.CallTreeTimelineTreeView.prototype = {
+    /**
+     * @override
+     * @return {!WebInspector.TimelineProfileTree.Node}
+     */
+    _buildTree: function()
+    {
+        var topDown = this._buildTopDownTree(WebInspector.TimelineAggregator.eventId);
+        return this._createAggregator().performGrouping(topDown, this._groupBySetting.get());
+    },
 
-
-
-if(!options.hash.includeZero&&!conditional||_utils.isEmpty(conditional)){
-return options.inverse(this);
-}else{
-return options.fn(this);
-}
-});
-
-instance.registerHelper('unless',function(conditional,options){
-return instance.helpers['if'].call(this,conditional,{fn:options.inverse,inverse:options.fn,hash:options.hash});
-});
+    __proto__: WebInspector.AggregatedTimelineTreeView.prototype,
 };
 
-module.exports=exports['default'];
-
-
-},{"../utils":263}],256:[function(require,module,exports){
-'use strict';
-
-exports.__esModule=true;
-
-exports['default']=function(instance){
-instance.registerHelper('log',function(){
-var args=[undefined],
-options=arguments[arguments.length-1];
-for(var i=0;i<arguments.length-1;i++){
-args.push(arguments[i]);
+/**
+ * @constructor
+ * @extends {WebInspector.AggregatedTimelineTreeView}
+ * @param {!WebInspector.TimelineModel} model
+ * @param {!Array<!WebInspector.TimelineModel.Filter>} filters
+ */
+WebInspector.BottomUpTimelineTreeView = function(model, filters)
+{
+    WebInspector.AggregatedTimelineTreeView.call(this, model, filters);
+    this._dataGrid.markColumnAsSortedBy("self", WebInspector.DataGrid.Order.Descending);
 }
 
-var level=1;
-if(options.hash.level!=null){
-level=options.hash.level;
-}else if(options.data&&options.data.level!=null){
-level=options.data.level;
-}
-args[0]=level;
+WebInspector.BottomUpTimelineTreeView.prototype = {
+    /**
+     * @override
+     * @return {!WebInspector.TimelineProfileTree.Node}
+     */
+    _buildTree: function()
+    {
+        var topDown = this._buildTopDownTree(WebInspector.TimelineAggregator.eventId);
+        return WebInspector.TimelineProfileTree.buildBottomUp(topDown, this._createAggregator().groupFunction(this._groupBySetting.get()));
+    },
 
-instance.log.apply(instance,args);
-});
+    __proto__: WebInspector.AggregatedTimelineTreeView.prototype
 };
 
-module.exports=exports['default'];
+/**
+ * @constructor
+ * @extends {WebInspector.TimelineTreeView}
+ * @param {!WebInspector.TimelineModel} model
+ * @param {!Array<!WebInspector.TimelineModel.Filter>} filters
+ * @param {!WebInspector.TimelineModeViewDelegate} delegate
+ */
+WebInspector.EventsTimelineTreeView = function(model, filters, delegate)
+{
+    this._filtersControl = new WebInspector.TimelineFilters();
+    this._filtersControl.addEventListener(WebInspector.TimelineFilters.Events.FilterChanged, this._onFilterChanged, this);
+    WebInspector.TimelineTreeView.call(this, model, filters);
+    this._delegate = delegate;
+    this._filters.push.apply(this._filters, this._filtersControl.filters());
+    this._dataGrid.markColumnAsSortedBy("startTime", WebInspector.DataGrid.Order.Ascending);
+}
 
+WebInspector.EventsTimelineTreeView.prototype = {
+    /**
+     * @override
+     * @param {!WebInspector.TimelineSelection} selection
+     */
+    updateContents: function(selection)
+    {
+        WebInspector.TimelineTreeView.prototype.updateContents.call(this, selection);
+        if (selection.type() === WebInspector.TimelineSelection.Type.TraceEvent) {
+            var event = /** @type {!WebInspector.TracingModel.Event} */ (selection.object());
+            this._selectEvent(event, true);
+        }
+    },
+
+    /**
+     * @override
+     * @return {!WebInspector.TimelineProfileTree.Node}
+     */
+    _buildTree: function()
+    {
+        this._currentTree = this._buildTopDownTree();
+        return this._currentTree;
+    },
+
+    _onFilterChanged: function()
+    {
+        var selectedEvent = this._lastSelectedNode && this._lastSelectedNode.event;
+        this._refreshTree();
+        if (selectedEvent)
+            this._selectEvent(selectedEvent, false);
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} event
+     * @return {?WebInspector.TimelineProfileTree.Node}
+     */
+    _findNodeWithEvent: function(event)
+    {
+        var iterators = [this._currentTree.children.values()];
+
+        while (iterators.length) {
+            var iterator = iterators.peekLast().next();
+            if (iterator.done) {
+                iterators.pop();
+                continue;
+            }
+            var child = /** @type {!WebInspector.TimelineProfileTree.Node} */ (iterator.value);
+            if (child.event === event)
+                return child;
+            if (child.children)
+                iterators.push(child.children.values());
+        }
+        return null;
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} event
+     * @param {boolean=} expand
+     */
+    _selectEvent: function(event, expand)
+    {
+        var node = this._findNodeWithEvent(event);
+        if (!node)
+            return;
+        this.selectProfileNode(node, false);
+        if (expand)
+            this._dataGridNodeForTreeNode(node).expand();
+    },
+
+    /**
+     * @override
+     * @param {!Array<!WebInspector.DataGrid.ColumnDescriptor>} columns
+     */
+    _populateColumns: function(columns)
+    {
+        columns.push({id: "startTime", title: WebInspector.UIString("Start Time"), width: "110px", fixedWidth: true, sortable: true});
+        WebInspector.TimelineTreeView.prototype._populateColumns.call(this, columns);
+    },
+
+    /**
+     * @override
+     * @param {!Element} parent
+     */
+    _populateToolbar: function(parent)
+    {
+        var filtersWidget = this._filtersControl.filtersWidget();
+        filtersWidget.forceShowFilterBar();
+        filtersWidget.show(parent);
+    },
+
+    /**
+     * @override
+     * @param {!WebInspector.TimelineProfileTree.Node} node
+     * @return {boolean}
+     */
+    _showDetailsForNode: function(node)
+    {
+        var traceEvent = node.event;
+        if (!traceEvent)
+            return false;
+        WebInspector.TimelineUIUtils.buildTraceEventDetails(traceEvent, this._model, this._linkifier, false, showDetails.bind(this));
+        return true;
+
+        /**
+         * @param {!DocumentFragment} fragment
+         * @this {WebInspector.EventsTimelineTreeView}
+         */
+        function showDetails(fragment)
+        {
+            this._detailsView.element.appendChild(fragment);
+        }
+    },
+
+    /**
+     * @override
+     * @param {?WebInspector.TimelineProfileTree.Node} node
+     */
+    _onHover: function(node)
+    {
+        this._delegate.highlightEvent(node && node.event);
+    },
+
+    __proto__: WebInspector.TimelineTreeView.prototype
+}
+
+/**
+ * @constructor
+ * @extends {WebInspector.VBox}
+ */
+WebInspector.TimelineStackView = function(treeView)
+{
+    WebInspector.VBox.call(this);
+    var header = this.element.createChild("div", "timeline-stack-view-header");
+    header.textContent = WebInspector.UIString("Heaviest stack");
+    this._treeView = treeView;
+    var columns = [
+        {id: "total", title: WebInspector.UIString("Total Time"), fixedWidth: true, width: "110px"},
+        {id: "activity", title: WebInspector.UIString("Activity")}
+    ];
+    this._dataGrid = new WebInspector.ViewportDataGrid(columns);
+    this._dataGrid.setResizeMethod(WebInspector.DataGrid.ResizeMethod.Last);
+    this._dataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._onSelectionChanged, this);
+    this._dataGrid.asWidget().show(this.element);
+}
+
+/** @enum {symbol} */
+WebInspector.TimelineStackView.Events = {
+    SelectionChanged: Symbol("SelectionChanged")
+}
+
+WebInspector.TimelineStackView.prototype = {
+    /**
+     * @param {!Array<!WebInspector.TimelineProfileTree.Node>} stack
+     * @param {!WebInspector.TimelineProfileTree.Node} selectedNode
+     */
+    setStack: function(stack, selectedNode)
+    {
+        var rootNode = this._dataGrid.rootNode();
+        rootNode.removeChildren();
+        var nodeToReveal = null;
+        var totalTime = Math.max.apply(Math, stack.map(node => node.totalTime));
+        for (var node of stack) {
+            var gridNode = new WebInspector.TimelineTreeView.GridNode(node, totalTime, totalTime, totalTime, this._treeView);
+            rootNode.appendChild(gridNode);
+            if (node === selectedNode)
+                nodeToReveal = gridNode;
+        }
+        nodeToReveal.revealAndSelect();
+    },
+
+    /**
+     * @return {?WebInspector.TimelineProfileTree.Node}
+     */
+    selectedTreeNode: function()
+    {
+        var selectedNode = this._dataGrid.selectedNode;
+        return selectedNode && /** @type {!WebInspector.TimelineTreeView.GridNode} */ (selectedNode)._profileNode;
+    },
+
+    _onSelectionChanged: function()
+    {
+        this.dispatchEventToListeners(WebInspector.TimelineStackView.Events.SelectionChanged);
+    },
+
+    __proto__: WebInspector.VBox.prototype
+}
+
+},{}],254:[function(require,module,exports){
+/*
+ * Copyright (C) 2013 Google Inc. All rights reserved.
+ * Copyright (C) 2012 Intel Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @constructor
+ */
+WebInspector.TimelineUIUtils = function() { }
+
+/**
+ * @constructor
+ * @param {string} title
+ * @param {!WebInspector.TimelineCategory} category
+ * @param {boolean=} hidden
+ */
+WebInspector.TimelineRecordStyle = function(title, category, hidden)
+{
+    this.title = title;
+    this.category = category;
+    this.hidden = !!hidden;
+}
+
+/**
+ * @return {!Object.<string, !WebInspector.TimelineRecordStyle>}
+ */
+WebInspector.TimelineUIUtils._initEventStyles = function()
+{
+    if (WebInspector.TimelineUIUtils._eventStylesMap)
+        return WebInspector.TimelineUIUtils._eventStylesMap;
+
+    var recordTypes = WebInspector.TimelineModel.RecordType;
+    var categories = WebInspector.TimelineUIUtils.categories();
+
+    var eventStyles = {};
+    eventStyles[recordTypes.Task] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Task"), categories["other"]);
+    eventStyles[recordTypes.Program] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Other"), categories["other"]);
+    eventStyles[recordTypes.Animation] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Animation"), categories["rendering"]);
+    eventStyles[recordTypes.EventDispatch] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Event"), categories["scripting"]);
+    eventStyles[recordTypes.RequestMainThreadFrame] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Request Main Thread Frame"), categories["rendering"], true);
+    eventStyles[recordTypes.BeginFrame] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Frame Start"), categories["rendering"], true);
+    eventStyles[recordTypes.BeginMainThreadFrame] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Frame Start (main thread)"), categories["rendering"], true);
+    eventStyles[recordTypes.DrawFrame] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Draw Frame"), categories["rendering"], true);
+    eventStyles[recordTypes.HitTest] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Hit Test"), categories["rendering"]);
+    eventStyles[recordTypes.ScheduleStyleRecalculation] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Schedule Style Recalculation"), categories["rendering"], true);
+    eventStyles[recordTypes.RecalculateStyles] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Recalculate Style"), categories["rendering"]);
+    eventStyles[recordTypes.UpdateLayoutTree] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Recalculate Style"), categories["rendering"]);
+    eventStyles[recordTypes.InvalidateLayout] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Invalidate Layout"), categories["rendering"], true);
+    eventStyles[recordTypes.Layout] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Layout"), categories["rendering"]);
+    eventStyles[recordTypes.PaintSetup] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Paint Setup"), categories["painting"]);
+    eventStyles[recordTypes.PaintImage] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Paint Image"), categories["painting"], true);
+    eventStyles[recordTypes.UpdateLayer] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Update Layer"), categories["painting"], true);
+    eventStyles[recordTypes.UpdateLayerTree] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Update Layer Tree"), categories["rendering"]);
+    eventStyles[recordTypes.Paint] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Paint"), categories["painting"]);
+    eventStyles[recordTypes.RasterTask] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Rasterize Paint"), categories["painting"]);
+    eventStyles[recordTypes.ScrollLayer] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Scroll"), categories["rendering"]);
+    eventStyles[recordTypes.CompositeLayers] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Composite Layers"), categories["painting"]);
+    eventStyles[recordTypes.ParseHTML] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Parse HTML"), categories["loading"]);
+    eventStyles[recordTypes.ParseAuthorStyleSheet] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Parse Stylesheet"), categories["loading"]);
+    eventStyles[recordTypes.TimerInstall] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Install Timer"), categories["scripting"]);
+    eventStyles[recordTypes.TimerRemove] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Remove Timer"), categories["scripting"]);
+    eventStyles[recordTypes.TimerFire] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Timer Fired"), categories["scripting"]);
+    eventStyles[recordTypes.XHRReadyStateChange] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("XHR Ready State Change"), categories["scripting"]);
+    eventStyles[recordTypes.XHRLoad] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("XHR Load"), categories["scripting"]);
+    eventStyles[recordTypes.CompileScript] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Compile Script"), categories["scripting"]);
+    eventStyles[recordTypes.EvaluateScript] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Evaluate Script"), categories["scripting"]);
+    eventStyles[recordTypes.ParseScriptOnBackground] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Parse Script"), categories["scripting"]);
+    eventStyles[recordTypes.MarkLoad] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Load event"), categories["scripting"], true);
+    eventStyles[recordTypes.MarkDOMContent] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("DOMContentLoaded event"), categories["scripting"], true);
+    eventStyles[recordTypes.MarkFirstPaint] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("First paint"), categories["painting"], true);
+    eventStyles[recordTypes.TimeStamp] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Timestamp"), categories["scripting"]);
+    eventStyles[recordTypes.ConsoleTime] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Console Time"), categories["scripting"]);
+    eventStyles[recordTypes.UserTiming] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("User Timing"), categories["scripting"]);
+    eventStyles[recordTypes.ResourceSendRequest] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Send Request"), categories["loading"]);
+    eventStyles[recordTypes.ResourceReceiveResponse] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Receive Response"), categories["loading"]);
+    eventStyles[recordTypes.ResourceFinish] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Finish Loading"), categories["loading"]);
+    eventStyles[recordTypes.ResourceReceivedData] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Receive Data"), categories["loading"]);
+    eventStyles[recordTypes.RunMicrotasks] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Run Microtasks"), categories["scripting"]);
+    eventStyles[recordTypes.FunctionCall] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Function Call"), categories["scripting"]);
+    eventStyles[recordTypes.GCEvent] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("GC Event"), categories["scripting"]);
+    eventStyles[recordTypes.MajorGC] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Major GC"), categories["scripting"]);
+    eventStyles[recordTypes.MinorGC] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Minor GC"), categories["scripting"]);
+    eventStyles[recordTypes.JSFrame] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("JS Frame"), categories["scripting"]);
+    eventStyles[recordTypes.RequestAnimationFrame] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Request Animation Frame"), categories["scripting"]);
+    eventStyles[recordTypes.CancelAnimationFrame] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Cancel Animation Frame"), categories["scripting"]);
+    eventStyles[recordTypes.FireAnimationFrame] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Animation Frame Fired"), categories["scripting"]);
+    eventStyles[recordTypes.RequestIdleCallback] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Request Idle Callback"), categories["scripting"]);
+    eventStyles[recordTypes.CancelIdleCallback] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Cancel Idle Callback"), categories["scripting"]);
+    eventStyles[recordTypes.FireIdleCallback] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Fire Idle Callback"), categories["scripting"]);
+    eventStyles[recordTypes.WebSocketCreate] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Create WebSocket"), categories["scripting"]);
+    eventStyles[recordTypes.WebSocketSendHandshakeRequest] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Send WebSocket Handshake"), categories["scripting"]);
+    eventStyles[recordTypes.WebSocketReceiveHandshakeResponse] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Receive WebSocket Handshake"), categories["scripting"]);
+    eventStyles[recordTypes.WebSocketDestroy] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Destroy WebSocket"), categories["scripting"]);
+    eventStyles[recordTypes.EmbedderCallback] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Embedder Callback"), categories["scripting"]);
+    eventStyles[recordTypes.DecodeImage] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Image Decode"), categories["painting"]);
+    eventStyles[recordTypes.ResizeImage] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Image Resize"), categories["painting"]);
+    eventStyles[recordTypes.GPUTask] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("GPU"), categories["gpu"]);
+    eventStyles[recordTypes.LatencyInfo] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("Input Latency"), categories["scripting"]);
+
+    eventStyles[recordTypes.GCIdleLazySweep] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("DOM GC"), categories["scripting"]);
+    eventStyles[recordTypes.GCCompleteSweep] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("DOM GC"), categories["scripting"]);
+    eventStyles[recordTypes.GCCollectGarbage] = new WebInspector.TimelineRecordStyle(WebInspector.UIString("DOM GC"), categories["scripting"]);
+
+    WebInspector.TimelineUIUtils._eventStylesMap = eventStyles;
+    return eventStyles;
+}
+
+/**
+ * @param {!WebInspector.TimelineIRModel.InputEvents} inputEventType
+ * @return {?string}
+ */
+WebInspector.TimelineUIUtils.inputEventDisplayName = function(inputEventType)
+{
+    if (!WebInspector.TimelineUIUtils._inputEventToDisplayName) {
+        var inputEvent = WebInspector.TimelineIRModel.InputEvents;
+
+        /** @type {!Map<!WebInspector.TimelineIRModel.InputEvents, string>} */
+        WebInspector.TimelineUIUtils._inputEventToDisplayName = new Map([
+            [inputEvent.Char, WebInspector.UIString("Key Character")],
+            [inputEvent.KeyDown, WebInspector.UIString("Key Down")],
+            [inputEvent.KeyDownRaw, WebInspector.UIString("Key Down")],
+            [inputEvent.KeyUp, WebInspector.UIString("Key Up")],
+            [inputEvent.Click, WebInspector.UIString("Click")],
+            [inputEvent.ContextMenu, WebInspector.UIString("Context Menu")],
+            [inputEvent.MouseDown, WebInspector.UIString("Mouse Down")],
+            [inputEvent.MouseMove, WebInspector.UIString("Mouse Move")],
+            [inputEvent.MouseUp, WebInspector.UIString("Mouse Up")],
+            [inputEvent.MouseWheel, WebInspector.UIString("Mouse Wheel")],
+            [inputEvent.ScrollBegin, WebInspector.UIString("Scroll Begin")],
+            [inputEvent.ScrollEnd, WebInspector.UIString("Scroll End")],
+            [inputEvent.ScrollUpdate, WebInspector.UIString("Scroll Update")],
+            [inputEvent.FlingStart, WebInspector.UIString("Fling Start")],
+            [inputEvent.FlingCancel, WebInspector.UIString("Fling Halt")],
+            [inputEvent.Tap, WebInspector.UIString("Tap")],
+            [inputEvent.TapCancel, WebInspector.UIString("Tap Halt")],
+            [inputEvent.ShowPress, WebInspector.UIString("Tap Begin")],
+            [inputEvent.TapDown, WebInspector.UIString("Tap Down")],
+            [inputEvent.TouchCancel, WebInspector.UIString("Touch Cancel")],
+            [inputEvent.TouchEnd, WebInspector.UIString("Touch End")],
+            [inputEvent.TouchMove, WebInspector.UIString("Touch Move")],
+            [inputEvent.TouchStart, WebInspector.UIString("Touch Start")],
+            [inputEvent.PinchBegin, WebInspector.UIString("Pinch Begin")],
+            [inputEvent.PinchEnd, WebInspector.UIString("Pinch End")],
+            [inputEvent.PinchUpdate, WebInspector.UIString("Pinch Update")]
+        ]);
+    }
+    return WebInspector.TimelineUIUtils._inputEventToDisplayName.get(inputEventType) || null;
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} traceEvent
+ * @param {!RegExp} regExp
+ * @return {boolean}
+ */
+WebInspector.TimelineUIUtils.testContentMatching = function(traceEvent, regExp)
+{
+    var title = WebInspector.TimelineUIUtils.eventStyle(traceEvent).title;
+    var tokens = [title];
+    if (traceEvent.url)
+        tokens.push(traceEvent.url);
+    for (var argName in traceEvent.args) {
+        var argValue = traceEvent.args[argName];
+        for (var key in argValue)
+            tokens.push(argValue[key]);
+    }
+    return regExp.test(tokens.join("|"));
+}
+
+/**
+ * @param {!WebInspector.TimelineModel.Record} record
+ * @return {!WebInspector.TimelineCategory}
+ */
+WebInspector.TimelineUIUtils.categoryForRecord = function(record)
+{
+    return WebInspector.TimelineUIUtils.eventStyle(record.traceEvent()).category;
+}
+
+
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @return {!{title: string, category: !WebInspector.TimelineCategory}}
+ */
+WebInspector.TimelineUIUtils.eventStyle = function(event)
+{
+    var eventStyles = WebInspector.TimelineUIUtils._initEventStyles();
+    if (event.hasCategory(WebInspector.TimelineModel.Category.Console) || event.hasCategory(WebInspector.TimelineModel.Category.UserTiming))
+        return { title: event.name, category: WebInspector.TimelineUIUtils.categories()["scripting"] };
+
+    if (event.hasCategory(WebInspector.TimelineModel.Category.LatencyInfo)) {
+        /** @const */
+        var prefix = "InputLatency::";
+        var inputEventType = event.name.startsWith(prefix) ? event.name.substr(prefix.length) : event.name;
+        var displayName = WebInspector.TimelineUIUtils.inputEventDisplayName(/** @type {!WebInspector.TimelineIRModel.InputEvents} */ (inputEventType));
+        return { title: displayName || inputEventType, category: WebInspector.TimelineUIUtils.categories()["scripting"] };
+    }
+    var result = eventStyles[event.name];
+    if (!result) {
+        result = new WebInspector.TimelineRecordStyle(event.name,  WebInspector.TimelineUIUtils.categories()["other"], true);
+        eventStyles[event.name] = result;
+    }
+    return result;
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @return {string}
+ */
+WebInspector.TimelineUIUtils.eventColor = function(event)
+{
+    if (event.name === WebInspector.TimelineModel.RecordType.JSFrame) {
+        var frame = event.args["data"];
+        if (WebInspector.TimelineUIUtils.isUserFrame(frame))
+            return WebInspector.TimelineUIUtils.colorForURL(frame.url);
+    }
+    return WebInspector.TimelineUIUtils.eventStyle(event).category.color;
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @return {string}
+ */
+WebInspector.TimelineUIUtils.eventTitle = function(event)
+{
+    var title = WebInspector.TimelineUIUtils.eventStyle(event).title;
+    if (event.hasCategory(WebInspector.TimelineModel.Category.Console))
+        return title;
+    if (event.name === WebInspector.TimelineModel.RecordType.TimeStamp)
+        return WebInspector.UIString("%s: %s", title, event.args["data"]["message"]);
+    if (event.name === WebInspector.TimelineModel.RecordType.Animation && event.args["data"] && event.args["data"]["name"])
+        return WebInspector.UIString("%s: %s", title, event.args["data"]["name"]);
+    return title;
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @return {?string}
+ */
+WebInspector.TimelineUIUtils.eventURL = function(event)
+{
+    if (event.url)
+        return event.url;
+    var data = event.args["data"] || event.args["beginData"];
+    return data && data.url || null;
+}
+
+/**
+ * !Map<!WebInspector.TimelineIRModel.Phases, !{color: string, label: string}>
+ */
+WebInspector.TimelineUIUtils._interactionPhaseStyles = function()
+{
+    var map = WebInspector.TimelineUIUtils._interactionPhaseStylesMap;
+    if (!map) {
+        map = new Map([
+            [WebInspector.TimelineIRModel.Phases.Idle, {color: "white", label: "Idle"}],
+            [WebInspector.TimelineIRModel.Phases.Response, {color: "hsl(43, 83%, 64%)", label: WebInspector.UIString("Response")}],
+            [WebInspector.TimelineIRModel.Phases.Scroll, {color: "hsl(256, 67%, 70%)", label: WebInspector.UIString("Scroll")}],
+            [WebInspector.TimelineIRModel.Phases.Fling, {color: "hsl(256, 67%, 70%)", label: WebInspector.UIString("Fling")}],
+            [WebInspector.TimelineIRModel.Phases.Drag, {color: "hsl(256, 67%, 70%)", label: WebInspector.UIString("Drag")}],
+            [WebInspector.TimelineIRModel.Phases.Animation, {color: "hsl(256, 67%, 70%)", label: WebInspector.UIString("Animation")}],
+            [WebInspector.TimelineIRModel.Phases.Uncategorized, {color: "hsl(0, 0%, 87%)", label: WebInspector.UIString("Uncategorized")}]
+        ]);
+        WebInspector.TimelineUIUtils._interactionPhaseStylesMap = map;
+    }
+    return map;
+}
+
+/**
+ * @param {!WebInspector.TimelineIRModel.Phases} phase
+ * @return {string}
+ */
+WebInspector.TimelineUIUtils.interactionPhaseColor = function(phase)
+{
+    return WebInspector.TimelineUIUtils._interactionPhaseStyles().get(phase).color;
+}
+
+/**
+ * @param {!WebInspector.TimelineIRModel.Phases} phase
+ * @return {string}
+ */
+WebInspector.TimelineUIUtils.interactionPhaseLabel = function(phase)
+{
+    return WebInspector.TimelineUIUtils._interactionPhaseStyles().get(phase).label;
+}
+
+/**
+ * @param {!RuntimeAgent.CallFrame} frame
+ * @return {boolean}
+ */
+WebInspector.TimelineUIUtils.isUserFrame = function(frame)
+{
+    return frame.scriptId !== "0" && !(frame.url && frame.url.startsWith("native "));
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @return {?RuntimeAgent.CallFrame}
+ */
+WebInspector.TimelineUIUtils.topStackFrame = function(event)
+{
+    var stackTrace = event.stackTrace || event.initiator && event.initiator.stackTrace;
+    return stackTrace && stackTrace.length ? stackTrace[0] : null;
+}
+
+/**
+ * @enum {symbol}
+ */
+WebInspector.TimelineUIUtils.NetworkCategory = {
+    HTML: Symbol("HTML"),
+    Script: Symbol("Script"),
+    Style: Symbol("Style"),
+    Media: Symbol("Media"),
+    Other: Symbol("Other")
+}
+
+/**
+ * @param {!WebInspector.TimelineModel.NetworkRequest} request
+ * @return {!WebInspector.TimelineUIUtils.NetworkCategory}
+ */
+WebInspector.TimelineUIUtils.networkRequestCategory = function(request)
+{
+    var categories = WebInspector.TimelineUIUtils.NetworkCategory;
+    switch (request.mimeType) {
+    case "text/html":
+        return categories.HTML;
+    case "application/javascript":
+    case "application/x-javascript":
+    case "text/javascript":
+        return categories.Script;
+    case "text/css":
+        return categories.Style;
+    case "audio/ogg":
+    case "image/gif":
+    case "image/jpeg":
+    case "image/png":
+    case "image/svg+xml":
+    case "image/webp":
+    case "image/x-icon":
+    case "font/opentype":
+    case "font/woff2":
+    case "application/font-woff":
+        return categories.Media;
+    default:
+        return categories.Other;
+    }
+}
+
+/**
+ * @param {!WebInspector.TimelineUIUtils.NetworkCategory} category
+ * @return {string}
+ */
+WebInspector.TimelineUIUtils.networkCategoryColor = function(category)
+{
+    var categories = WebInspector.TimelineUIUtils.NetworkCategory;
+    switch (category) {
+    case categories.HTML: return "hsl(214, 67%, 66%)";
+    case categories.Script: return "hsl(43, 83%, 64%)";
+    case categories.Style: return "hsl(256, 67%, 70%)";
+    case categories.Media: return "hsl(109, 33%, 55%)";
+    default: return "hsl(0, 0%, 70%)";
+    }
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @param {?WebInspector.Target} target
+ * @return {?string}
+ */
+WebInspector.TimelineUIUtils.buildDetailsTextForTraceEvent = function(event, target)
+{
+    var recordType = WebInspector.TimelineModel.RecordType;
+    var detailsText;
+    var eventData = event.args["data"];
+    switch (event.name) {
+    case recordType.GCEvent:
+    case recordType.MajorGC:
+    case recordType.MinorGC:
+        var delta = event.args["usedHeapSizeBefore"] - event.args["usedHeapSizeAfter"];
+        detailsText = WebInspector.UIString("%s collected", Number.bytesToString(delta));
+        break;
+    case recordType.FunctionCall:
+        // Omit internally generated script names.
+        if (eventData)
+            detailsText = linkifyLocationAsText(eventData["scriptId"], eventData["lineNumber"], 0);
+        break;
+    case recordType.JSFrame:
+        detailsText = WebInspector.beautifyFunctionName(eventData["functionName"]);
+        break;
+    case recordType.EventDispatch:
+        detailsText = eventData ? eventData["type"] : null;
+        break;
+    case recordType.Paint:
+        var width = WebInspector.TimelineUIUtils.quadWidth(eventData.clip);
+        var height = WebInspector.TimelineUIUtils.quadHeight(eventData.clip);
+        if (width && height)
+            detailsText = WebInspector.UIString("%d\u2009\u00d7\u2009%d", width, height);
+        break;
+    case recordType.ParseHTML:
+        var endLine = event.args["endData"] && event.args["endData"]["endLine"];
+        var url = WebInspector.displayNameForURL(event.args["beginData"]["url"]);
+        detailsText = WebInspector.UIString("%s [%s\u2026%s]", url, event.args["beginData"]["startLine"] + 1, endLine >= 0 ? endLine + 1 : "");
+        break;
+
+    case recordType.CompileScript:
+    case recordType.EvaluateScript:
+        var url = eventData["url"];
+        if (url)
+            detailsText = WebInspector.displayNameForURL(url) + ":" + (eventData["lineNumber"] + 1);
+        break;
+    case recordType.ParseScriptOnBackground:
+    case recordType.XHRReadyStateChange:
+    case recordType.XHRLoad:
+        var url = eventData["url"];
+        if (url)
+            detailsText = WebInspector.displayNameForURL(url);
+        break;
+    case recordType.TimeStamp:
+        detailsText = eventData["message"];
+        break;
+
+    case recordType.WebSocketCreate:
+    case recordType.WebSocketSendHandshakeRequest:
+    case recordType.WebSocketReceiveHandshakeResponse:
+    case recordType.WebSocketDestroy:
+    case recordType.ResourceSendRequest:
+    case recordType.ResourceReceivedData:
+    case recordType.ResourceReceiveResponse:
+    case recordType.ResourceFinish:
+    case recordType.PaintImage:
+    case recordType.DecodeImage:
+    case recordType.ResizeImage:
+    case recordType.DecodeLazyPixelRef:
+        if (event.url)
+            detailsText = WebInspector.displayNameForURL(event.url);
+        break;
+
+    case recordType.EmbedderCallback:
+        detailsText = eventData["callbackName"];
+        break;
+
+    case recordType.Animation:
+        detailsText = eventData && eventData["name"];
+        break;
+
+    case recordType.GCIdleLazySweep:
+        detailsText = WebInspector.UIString("idle sweep");
+        break;
+
+    case recordType.GCCompleteSweep:
+        detailsText = WebInspector.UIString("complete sweep");
+        break;
+
+    case recordType.GCCollectGarbage:
+        detailsText = WebInspector.UIString("collect");
+        break;
+
+    default:
+        if (event.hasCategory(WebInspector.TimelineModel.Category.Console))
+            detailsText = null;
+        else
+            detailsText = linkifyTopCallFrameAsText();
+        break;
+    }
+
+    return detailsText;
+
+    /**
+     * @param {string} scriptId
+     * @param {number} lineNumber
+     * @param {number} columnNumber
+     * @return {?string}
+     */
+    function linkifyLocationAsText(scriptId, lineNumber, columnNumber)
+    {
+        var debuggerModel = WebInspector.DebuggerModel.fromTarget(target);
+        if (!target || target.isDetached() || !scriptId || !debuggerModel)
+            return null;
+        var rawLocation = debuggerModel.createRawLocationByScriptId(scriptId, lineNumber, columnNumber);
+        if (!rawLocation)
+            return null;
+        var uiLocation = WebInspector.debuggerWorkspaceBinding.rawLocationToUILocation(rawLocation);
+        return uiLocation.linkText();
+    }
+
+    /**
+     * @return {?string}
+     */
+    function linkifyTopCallFrameAsText()
+    {
+        var frame = WebInspector.TimelineUIUtils.topStackFrame(event);
+        if (!frame)
+            return null;
+        var text = linkifyLocationAsText(frame.scriptId, frame.lineNumber, frame.columnNumber);
+        if (!text) {
+            text = frame.url;
+            if (typeof frame.lineNumber === "number")
+                text += ":" + (frame.lineNumber + 1);
+        }
+        return text;
+    }
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @param {?WebInspector.Target} target
+ * @param {!WebInspector.Linkifier} linkifier
+ * @return {?Node}
+ */
+WebInspector.TimelineUIUtils.buildDetailsNodeForTraceEvent = function(event, target, linkifier)
+{
+    var recordType = WebInspector.TimelineModel.RecordType;
+    var details = null;
+    var detailsText;
+    var eventData = event.args["data"];
+    switch (event.name) {
+    case recordType.GCEvent:
+    case recordType.MajorGC:
+    case recordType.MinorGC:
+    case recordType.EventDispatch:
+    case recordType.Paint:
+    case recordType.Animation:
+    case recordType.EmbedderCallback:
+    case recordType.ParseHTML:
+    case recordType.WebSocketCreate:
+    case recordType.WebSocketSendHandshakeRequest:
+    case recordType.WebSocketReceiveHandshakeResponse:
+    case recordType.WebSocketDestroy:
+    case recordType.GCIdleLazySweep:
+    case recordType.GCCompleteSweep:
+    case recordType.GCCollectGarbage:
+        detailsText = WebInspector.TimelineUIUtils.buildDetailsTextForTraceEvent(event, target);
+        break;
+    case recordType.PaintImage:
+    case recordType.DecodeImage:
+    case recordType.ResizeImage:
+    case recordType.DecodeLazyPixelRef:
+    case recordType.XHRReadyStateChange:
+    case recordType.XHRLoad:
+    case recordType.ResourceSendRequest:
+    case recordType.ResourceReceivedData:
+    case recordType.ResourceReceiveResponse:
+    case recordType.ResourceFinish:
+        if (event.url)
+            details = WebInspector.linkifyResourceAsNode(event.url);
+        break;
+    case recordType.FunctionCall:
+    case recordType.JSFrame:
+        details = createElement("span");
+        details.createTextChild(WebInspector.beautifyFunctionName(eventData["functionName"]));
+        var location = linkifyLocation(eventData["scriptId"], eventData["url"], eventData["lineNumber"], eventData["columnNumber"]);
+        if (location) {
+            details.createTextChild(" @ ");
+            details.appendChild(location);
+        }
+        break;
+    case recordType.CompileScript:
+    case recordType.EvaluateScript:
+        var url = eventData["url"];
+        if (url)
+            details = linkifyLocation("", url, eventData["lineNumber"], 0);
+        break;
+    case recordType.ParseScriptOnBackground:
+        var url = eventData["url"];
+        if (url)
+            details = linkifyLocation("", url, 0, 0);
+        break;
+    default:
+        if (event.hasCategory(WebInspector.TimelineModel.Category.Console))
+            detailsText = null;
+        else
+            details = linkifyTopCallFrame();
+        break;
+    }
+
+    if (!details && detailsText)
+        details = createTextNode(detailsText);
+    return details;
+
+    /**
+     * @param {string} scriptId
+     * @param {string} url
+     * @param {number} lineNumber
+     * @param {number=} columnNumber
+     * @return {?Element}
+     */
+    function linkifyLocation(scriptId, url, lineNumber, columnNumber)
+    {
+        return linkifier.linkifyScriptLocation(target, scriptId, url, lineNumber, columnNumber, "timeline-details");
+    }
+
+    /**
+     * @return {?Element}
+     */
+    function linkifyTopCallFrame()
+    {
+        var frame = WebInspector.TimelineUIUtils.topStackFrame(event);
+        return frame ? linkifier.maybeLinkifyConsoleCallFrame(target, frame, "timeline-details") : null;
+    }
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @param {!WebInspector.TimelineModel} model
+ * @param {!WebInspector.Linkifier} linkifier
+ * @param {boolean} detailed
+ * @param {function(!DocumentFragment)} callback
+ */
+WebInspector.TimelineUIUtils.buildTraceEventDetails = function(event, model, linkifier, detailed, callback)
+{
+    var target = model.targetByEvent(event);
+    if (!target) {
+        callbackWrapper();
+        return;
+    }
+    var relatedNodes = null;
+    var barrier = new CallbackBarrier();
+    if (!event.previewElement) {
+        if (event.url)
+            WebInspector.DOMPresentationUtils.buildImagePreviewContents(target, event.url, false, barrier.createCallback(saveImage));
+        else if (event.picture)
+            WebInspector.TimelineUIUtils.buildPicturePreviewContent(event, target, barrier.createCallback(saveImage));
+    }
+    var nodeIdsToResolve = new Set();
+    if (event.backendNodeId)
+        nodeIdsToResolve.add(event.backendNodeId);
+    if (event.invalidationTrackingEvents)
+        WebInspector.TimelineUIUtils._collectInvalidationNodeIds(nodeIdsToResolve, event.invalidationTrackingEvents);
+    if (nodeIdsToResolve.size) {
+        var domModel = WebInspector.DOMModel.fromTarget(target);
+        if (domModel)
+            domModel.pushNodesByBackendIdsToFrontend(nodeIdsToResolve, barrier.createCallback(setRelatedNodeMap));
+    }
+    barrier.callWhenDone(callbackWrapper);
+
+    /**
+     * @param {!Element=} element
+     */
+    function saveImage(element)
+    {
+        event.previewElement = element || null;
+    }
+
+    /**
+     * @param {?Map<number, ?WebInspector.DOMNode>} nodeMap
+     */
+    function setRelatedNodeMap(nodeMap)
+    {
+        relatedNodes = nodeMap;
+    }
+
+    function callbackWrapper()
+    {
+        callback(WebInspector.TimelineUIUtils._buildTraceEventDetailsSynchronously(event, model, linkifier, detailed, relatedNodes));
+    }
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @param {!WebInspector.TimelineModel} model
+ * @param {!WebInspector.Linkifier} linkifier
+ * @param {boolean} detailed
+ * @param {?Map<number, ?WebInspector.DOMNode>} relatedNodesMap
+ * @return {!DocumentFragment}
+ */
+WebInspector.TimelineUIUtils._buildTraceEventDetailsSynchronously = function(event, model, linkifier, detailed, relatedNodesMap)
+{
+    var stats = {};
+    var recordTypes = WebInspector.TimelineModel.RecordType;
+
+    // This message may vary per event.name;
+    var relatedNodeLabel;
+
+    var contentHelper = new WebInspector.TimelineDetailsContentHelper(model.targetByEvent(event), linkifier);
+    contentHelper.addSection(WebInspector.TimelineUIUtils.eventTitle(event), WebInspector.TimelineUIUtils.eventStyle(event).category);
+
+    var eventData = event.args["data"];
+    var initiator = event.initiator;
+
+    if (event.warning)
+        contentHelper.appendWarningRow(event);
+    if (event.name === recordTypes.JSFrame && eventData["deoptReason"])
+        contentHelper.appendWarningRow(event, WebInspector.TimelineModel.WarningType.V8Deopt);
+
+    if (detailed) {
+        contentHelper.appendTextRow(WebInspector.UIString("Self Time"), Number.millisToString(event.selfTime, true));
+        contentHelper.appendTextRow(WebInspector.UIString("Total Time"), Number.millisToString(event.duration || 0, true));
+    }
+
+    switch (event.name) {
+    case recordTypes.GCEvent:
+    case recordTypes.MajorGC:
+    case recordTypes.MinorGC:
+        var delta = event.args["usedHeapSizeBefore"] - event.args["usedHeapSizeAfter"];
+        contentHelper.appendTextRow(WebInspector.UIString("Collected"), Number.bytesToString(delta));
+        break;
+    case recordTypes.JSFrame:
+    case recordTypes.FunctionCall:
+        var detailsNode = WebInspector.TimelineUIUtils.buildDetailsNodeForTraceEvent(event, model.targetByEvent(event), linkifier);
+        if (detailsNode)
+            contentHelper.appendElementRow(WebInspector.UIString("Function"), detailsNode);
+        break;
+    case recordTypes.TimerFire:
+    case recordTypes.TimerInstall:
+    case recordTypes.TimerRemove:
+        contentHelper.appendTextRow(WebInspector.UIString("Timer ID"), eventData["timerId"]);
+        if (event.name === recordTypes.TimerInstall) {
+            contentHelper.appendTextRow(WebInspector.UIString("Timeout"), Number.millisToString(eventData["timeout"]));
+            contentHelper.appendTextRow(WebInspector.UIString("Repeats"), !eventData["singleShot"]);
+        }
+        break;
+    case recordTypes.FireAnimationFrame:
+        contentHelper.appendTextRow(WebInspector.UIString("Callback ID"), eventData["id"]);
+        break;
+    case recordTypes.ResourceSendRequest:
+    case recordTypes.ResourceReceiveResponse:
+    case recordTypes.ResourceReceivedData:
+    case recordTypes.ResourceFinish:
+        var url = (event.name === recordTypes.ResourceSendRequest) ? eventData["url"] : initiator && initiator.args["data"]["url"];
+        if (url)
+            contentHelper.appendElementRow(WebInspector.UIString("Resource"), WebInspector.linkifyResourceAsNode(url));
+        if (eventData["requestMethod"])
+            contentHelper.appendTextRow(WebInspector.UIString("Request Method"), eventData["requestMethod"]);
+        if (typeof eventData["statusCode"] === "number")
+            contentHelper.appendTextRow(WebInspector.UIString("Status Code"), eventData["statusCode"]);
+        if (eventData["mimeType"])
+            contentHelper.appendTextRow(WebInspector.UIString("MIME Type"), eventData["mimeType"]);
+        if ("priority" in eventData) {
+            var priority = WebInspector.uiLabelForPriority(eventData["priority"]);
+            contentHelper.appendTextRow(WebInspector.UIString("Priority"), priority);
+        }
+        if (eventData["encodedDataLength"])
+            contentHelper.appendTextRow(WebInspector.UIString("Encoded Data Length"), WebInspector.UIString("%d Bytes", eventData["encodedDataLength"]));
+        break;
+    case recordTypes.CompileScript:
+    case recordTypes.EvaluateScript:
+        var url = eventData["url"];
+        if (url)
+            contentHelper.appendLocationRow(WebInspector.UIString("Script"), url, eventData["lineNumber"], eventData["columnNumber"]);
+        break;
+    case recordTypes.Paint:
+        var clip = eventData["clip"];
+        contentHelper.appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", clip[0], clip[1]));
+        var clipWidth = WebInspector.TimelineUIUtils.quadWidth(clip);
+        var clipHeight = WebInspector.TimelineUIUtils.quadHeight(clip);
+        contentHelper.appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d × %d", clipWidth, clipHeight));
+        // Fall-through intended.
+
+    case recordTypes.PaintSetup:
+    case recordTypes.Rasterize:
+    case recordTypes.ScrollLayer:
+        relatedNodeLabel = WebInspector.UIString("Layer Root");
+        break;
+    case recordTypes.PaintImage:
+    case recordTypes.DecodeLazyPixelRef:
+    case recordTypes.DecodeImage:
+    case recordTypes.ResizeImage:
+    case recordTypes.DrawLazyPixelRef:
+        relatedNodeLabel = WebInspector.UIString("Owner Element");
+        if (event.url)
+            contentHelper.appendElementRow(WebInspector.UIString("Image URL"), WebInspector.linkifyResourceAsNode(event.url));
+        break;
+    case recordTypes.ParseAuthorStyleSheet:
+        var url = eventData["styleSheetUrl"];
+        if (url)
+            contentHelper.appendElementRow(WebInspector.UIString("Stylesheet URL"), WebInspector.linkifyResourceAsNode(url));
+        break;
+    case recordTypes.UpdateLayoutTree: // We don't want to see default details.
+    case recordTypes.RecalculateStyles:
+        contentHelper.appendTextRow(WebInspector.UIString("Elements Affected"), event.args["elementCount"]);
+        break;
+    case recordTypes.Layout:
+        var beginData = event.args["beginData"];
+        contentHelper.appendTextRow(WebInspector.UIString("Nodes That Need Layout"), WebInspector.UIString("%s of %s", beginData["dirtyObjects"], beginData["totalObjects"]));
+        relatedNodeLabel = WebInspector.UIString("Layout root");
+        break;
+    case recordTypes.ConsoleTime:
+        contentHelper.appendTextRow(WebInspector.UIString("Message"), event.name);
+        break;
+    case recordTypes.WebSocketCreate:
+    case recordTypes.WebSocketSendHandshakeRequest:
+    case recordTypes.WebSocketReceiveHandshakeResponse:
+    case recordTypes.WebSocketDestroy:
+        var initiatorData = initiator ? initiator.args["data"] : eventData;
+        if (typeof initiatorData["webSocketURL"] !== "undefined")
+            contentHelper.appendTextRow(WebInspector.UIString("URL"), initiatorData["webSocketURL"]);
+        if (typeof initiatorData["webSocketProtocol"] !== "undefined")
+            contentHelper.appendTextRow(WebInspector.UIString("WebSocket Protocol"), initiatorData["webSocketProtocol"]);
+        if (typeof eventData["message"] !== "undefined")
+            contentHelper.appendTextRow(WebInspector.UIString("Message"), eventData["message"]);
+        break;
+    case recordTypes.EmbedderCallback:
+        contentHelper.appendTextRow(WebInspector.UIString("Callback Function"), eventData["callbackName"]);
+        break;
+    case recordTypes.Animation:
+        if (event.phase === WebInspector.TracingModel.Phase.NestableAsyncInstant)
+            contentHelper.appendTextRow(WebInspector.UIString("State"), eventData["state"]);
+        break;
+    case recordTypes.ParseHTML:
+        var beginData = event.args["beginData"];
+        var url = beginData["url"];
+        var startLine = beginData["startLine"] - 1;
+        var endLine = event.args["endData"] ? event.args["endData"]["endLine"] - 1 : undefined;
+        if (url)
+            contentHelper.appendLocationRange(WebInspector.UIString("Range"), url, startLine, endLine);
+        break;
+
+    case recordTypes.FireIdleCallback:
+        contentHelper.appendTextRow(WebInspector.UIString("Allotted Time"), Number.millisToString(eventData["allottedMilliseconds"]));
+        contentHelper.appendTextRow(WebInspector.UIString("Invoked by Timeout"), eventData["timedOut"]);
+        // Fall-through intended.
+
+    case recordTypes.RequestIdleCallback:
+    case recordTypes.CancelIdleCallback:
+        contentHelper.appendTextRow(WebInspector.UIString("Callback ID"), eventData["id"]);
+        break;
+    case recordTypes.EventDispatch:
+        contentHelper.appendTextRow(WebInspector.UIString("Type"), eventData["type"]);
+        break;
+
+    default:
+        var detailsNode = WebInspector.TimelineUIUtils.buildDetailsNodeForTraceEvent(event, model.targetByEvent(event), linkifier);
+        if (detailsNode)
+            contentHelper.appendElementRow(WebInspector.UIString("Details"), detailsNode);
+        break;
+    }
+
+    if (event.timeWaitingForMainThread)
+        contentHelper.appendTextRow(WebInspector.UIString("Time Waiting for Main Thread"), Number.millisToString(event.timeWaitingForMainThread, true));
+
+    var relatedNode = relatedNodesMap && relatedNodesMap.get(event.backendNodeId);
+    if (relatedNode)
+        contentHelper.appendElementRow(relatedNodeLabel || WebInspector.UIString("Related Node"), WebInspector.DOMPresentationUtils.linkifyNodeReference(relatedNode));
+
+    if (event.previewElement) {
+        contentHelper.addSection(WebInspector.UIString("Preview"));
+        contentHelper.appendElementRow("", event.previewElement);
+    }
+
+    if (event.stackTrace || (event.initiator && event.initiator.stackTrace) || event.invalidationTrackingEvents)
+        WebInspector.TimelineUIUtils._generateCauses(event, model.targetByEvent(event), relatedNodesMap, contentHelper);
+
+    var showPieChart = detailed && WebInspector.TimelineUIUtils._aggregatedStatsForTraceEvent(stats, model, event);
+    if (showPieChart) {
+        contentHelper.addSection(WebInspector.UIString("Aggregated Time"));
+        var pieChart = WebInspector.TimelineUIUtils.generatePieChart(stats, WebInspector.TimelineUIUtils.eventStyle(event).category, event.selfTime);
+        contentHelper.appendElementRow("", pieChart);
+    }
+
+    return contentHelper.fragment;
+}
+
+WebInspector.TimelineUIUtils._aggregatedStatsKey = Symbol("aggregatedStats");
+
+/**
+ * @param {!WebInspector.TimelineModel} model
+ * @param {number} startTime
+ * @param {number} endTime
+ * @return {!DocumentFragment}
+ */
+WebInspector.TimelineUIUtils.buildRangeStats = function(model, startTime, endTime)
+{
+    var aggregatedStats = {};
+
+    /**
+     * @param {number} value
+     * @param {!WebInspector.TimelineModel.Record} task
+     * @return {number}
+     */
+    function compareEndTime(value, task)
+    {
+        return value < task.endTime() ? -1 : 1;
+    }
+    var mainThreadTasks = model.mainThreadTasks();
+    var taskIndex = mainThreadTasks.lowerBound(startTime, compareEndTime);
+    for (; taskIndex < mainThreadTasks.length; ++taskIndex) {
+        var task = mainThreadTasks[taskIndex];
+        if (task.startTime() > endTime)
+            break;
+        if (task.startTime() > startTime && task.endTime() < endTime) {
+            // cache stats for top-level entries that fit the range entirely.
+            var taskStats = task[WebInspector.TimelineUIUtils._aggregatedStatsKey];
+            if (!taskStats) {
+                taskStats = {};
+                WebInspector.TimelineUIUtils._collectAggregatedStatsForRecord(task, startTime, endTime, taskStats);
+                task[WebInspector.TimelineUIUtils._aggregatedStatsKey] = taskStats;
+            }
+            for (var key in taskStats)
+                aggregatedStats[key] = (aggregatedStats[key] || 0) + taskStats[key];
+            continue;
+        }
+        WebInspector.TimelineUIUtils._collectAggregatedStatsForRecord(task, startTime, endTime, aggregatedStats);
+    }
+
+    var aggregatedTotal = 0;
+    for (var categoryName in aggregatedStats)
+        aggregatedTotal += aggregatedStats[categoryName];
+    aggregatedStats["idle"] = Math.max(0, endTime - startTime - aggregatedTotal);
+
+    var startOffset = startTime - model.minimumRecordTime();
+    var endOffset = endTime - model.minimumRecordTime();
+
+    var contentHelper = new WebInspector.TimelineDetailsContentHelper(null, null);
+    contentHelper.addSection(WebInspector.UIString("Range:  %s \u2013 %s", Number.millisToString(startOffset), Number.millisToString(endOffset)));
+    var pieChart = WebInspector.TimelineUIUtils.generatePieChart(aggregatedStats);
+    contentHelper.appendElementRow("", pieChart);
+    return contentHelper.fragment;
+}
+
+/**
+ * @param {!WebInspector.TimelineModel.Record} record
+ * @param {number} startTime
+ * @param {number} endTime
+ * @param {!Object} aggregatedStats
+ */
+WebInspector.TimelineUIUtils._collectAggregatedStatsForRecord = function(record, startTime, endTime, aggregatedStats)
+{
+    var records = [];
+
+    if (!record.endTime() || record.endTime() < startTime || record.startTime() > endTime)
+        return;
+
+    var childrenTime = 0;
+    var children = record.children() || [];
+    for (var i = 0; i < children.length; ++i) {
+        var child = children[i];
+        if (!child.endTime() || child.endTime() < startTime || child.startTime() > endTime)
+            continue;
+        childrenTime += Math.min(endTime, child.endTime()) - Math.max(startTime, child.startTime());
+        WebInspector.TimelineUIUtils._collectAggregatedStatsForRecord(child, startTime, endTime, aggregatedStats);
+    }
+    var categoryName = WebInspector.TimelineUIUtils.categoryForRecord(record).name;
+    var ownTime = Math.min(endTime, record.endTime()) - Math.max(startTime, record.startTime()) - childrenTime;
+    aggregatedStats[categoryName] = (aggregatedStats[categoryName] || 0) + ownTime;
+}
+
+/**
+ * @param {!WebInspector.TimelineModel.NetworkRequest} request
+ * @param {!WebInspector.TimelineModel} model
+ * @param {!WebInspector.Linkifier} linkifier
+ * @return {!Promise<!DocumentFragment>}
+ */
+WebInspector.TimelineUIUtils.buildNetworkRequestDetails = function(request, model, linkifier)
+{
+    var target = model.targetByEvent(request.children[0]);
+    var contentHelper = new WebInspector.TimelineDetailsContentHelper(target, linkifier);
+
+    var duration = request.endTime - (request.startTime || -Infinity);
+    var items = [];
+    if (request.url)
+        contentHelper.appendElementRow(WebInspector.UIString("URL"), WebInspector.linkifyURLAsNode(request.url));
+    if (isFinite(duration))
+        contentHelper.appendTextRow(WebInspector.UIString("Duration"), Number.millisToString(duration, true));
+    if (request.requestMethod)
+        contentHelper.appendTextRow(WebInspector.UIString("Request Method"), request.requestMethod);
+    if (typeof request.priority === "string") {
+        var priority = WebInspector.uiLabelForPriority(/** @type {!NetworkAgent.ResourcePriority} */ (request.priority));
+        contentHelper.appendTextRow(WebInspector.UIString("Priority"), priority);
+    }
+    if (request.mimeType)
+        contentHelper.appendTextRow(WebInspector.UIString("Mime Type"), request.mimeType);
+
+    var title = WebInspector.UIString("Initiator");
+    var sendRequest = request.children[0];
+    var topFrame = WebInspector.TimelineUIUtils.topStackFrame(sendRequest);
+    if (topFrame) {
+        var link = linkifier.maybeLinkifyConsoleCallFrame(target, topFrame);
+        if (link)
+            contentHelper.appendElementRow(title, link);
+    } else if (sendRequest.initiator) {
+        var initiatorURL = WebInspector.TimelineUIUtils.eventURL(sendRequest.initiator);
+        if (initiatorURL) {
+            var link = linkifier.maybeLinkifyScriptLocation(target, null, initiatorURL, 0);
+            if (link)
+                contentHelper.appendElementRow(title, link);
+        }
+    }
+
+    /**
+     * @param {function(?Element)} fulfill
+     */
+    function action(fulfill)
+    {
+        WebInspector.DOMPresentationUtils.buildImagePreviewContents(/** @type {!WebInspector.Target} */(target), request.url, false, saveImage);
+        /**
+         * @param {!Element=} element
+         */
+        function saveImage(element)
+        {
+            request.previewElement = element || null;
+            fulfill(request.previewElement);
+        }
+    }
+    var previewPromise;
+    if (request.previewElement)
+        previewPromise = Promise.resolve(request.previewElement);
+    else
+        previewPromise = request.url && target ? new Promise(action) : Promise.resolve(null);
+    /**
+     * @param {?Element} element
+     * @return {!DocumentFragment}
+     */
+    function appendPreview(element)
+    {
+        if (element)
+            contentHelper.appendElementRow(WebInspector.UIString("Preview"), request.previewElement);
+        return contentHelper.fragment;
+    }
+    return previewPromise.then(appendPreview);
+}
+
+/**
+ * @param {!Array<!RuntimeAgent.CallFrame>} callFrames
+ * @return {!RuntimeAgent.StackTrace}
+ */
+WebInspector.TimelineUIUtils._stackTraceFromCallFrames = function(callFrames)
+{
+    return /** @type {!RuntimeAgent.StackTrace} */ ({ callFrames: callFrames });
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @param {?WebInspector.Target} target
+ * @param {?Map<number, ?WebInspector.DOMNode>} relatedNodesMap
+ * @param {!WebInspector.TimelineDetailsContentHelper} contentHelper
+ */
+WebInspector.TimelineUIUtils._generateCauses = function(event, target, relatedNodesMap, contentHelper)
+{
+    var recordTypes = WebInspector.TimelineModel.RecordType;
+
+    var callSiteStackLabel;
+    var stackLabel;
+    var initiator = event.initiator;
+
+    switch (event.name) {
+    case recordTypes.TimerFire:
+        callSiteStackLabel = WebInspector.UIString("Timer Installed");
+        break;
+    case recordTypes.FireAnimationFrame:
+        callSiteStackLabel = WebInspector.UIString("Animation Frame Requested");
+        break;
+    case recordTypes.FireIdleCallback:
+        callSiteStackLabel = WebInspector.UIString("Idle Callback Requested");
+        break;
+    case recordTypes.UpdateLayoutTree:
+    case recordTypes.RecalculateStyles:
+        stackLabel = WebInspector.UIString("Recalculation Forced");
+        break;
+    case recordTypes.Layout:
+        callSiteStackLabel = WebInspector.UIString("First Layout Invalidation");
+        stackLabel = WebInspector.UIString("Layout Forced");
+        break;
+    }
+
+    // Direct cause.
+    if (event.stackTrace && event.stackTrace.length) {
+        contentHelper.addSection(WebInspector.UIString("Call Stacks"));
+        contentHelper.appendStackTrace(stackLabel || WebInspector.UIString("Stack Trace"), WebInspector.TimelineUIUtils._stackTraceFromCallFrames(event.stackTrace));
+    }
+
+    // Indirect causes.
+    if (event.invalidationTrackingEvents && target) { // Full invalidation tracking (experimental).
+        contentHelper.addSection(WebInspector.UIString("Invalidations"));
+        WebInspector.TimelineUIUtils._generateInvalidations(event, target, relatedNodesMap, contentHelper);
+    } else if (initiator && initiator.stackTrace) { // Partial invalidation tracking.
+        contentHelper.appendStackTrace(callSiteStackLabel || WebInspector.UIString("First Invalidated"), WebInspector.TimelineUIUtils._stackTraceFromCallFrames(initiator.stackTrace));
+    }
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @param {!WebInspector.Target} target
+ * @param {?Map<number, ?WebInspector.DOMNode>} relatedNodesMap
+ * @param {!WebInspector.TimelineDetailsContentHelper} contentHelper
+ */
+WebInspector.TimelineUIUtils._generateInvalidations = function(event, target, relatedNodesMap, contentHelper)
+{
+    if (!event.invalidationTrackingEvents)
+        return;
+
+    var invalidations = {};
+    event.invalidationTrackingEvents.forEach(function(invalidation) {
+        if (!invalidations[invalidation.type])
+            invalidations[invalidation.type] = [invalidation];
+        else
+            invalidations[invalidation.type].push(invalidation);
+    });
+
+    Object.keys(invalidations).forEach(function(type) {
+        WebInspector.TimelineUIUtils._generateInvalidationsForType(
+            type, target, invalidations[type], relatedNodesMap, contentHelper);
+    });
+}
+
+/**
+ * @param {string} type
+ * @param {!WebInspector.Target} target
+ * @param {!Array.<!WebInspector.InvalidationTrackingEvent>} invalidations
+ * @param {?Map<number, ?WebInspector.DOMNode>} relatedNodesMap
+ * @param {!WebInspector.TimelineDetailsContentHelper} contentHelper
+ */
+WebInspector.TimelineUIUtils._generateInvalidationsForType = function(type, target, invalidations, relatedNodesMap, contentHelper)
+{
+    var title;
+    switch (type) {
+    case WebInspector.TimelineModel.RecordType.StyleRecalcInvalidationTracking:
+        title = WebInspector.UIString("Style Invalidations");
+        break;
+    case WebInspector.TimelineModel.RecordType.LayoutInvalidationTracking:
+        title = WebInspector.UIString("Layout Invalidations");
+        break;
+    default:
+        title = WebInspector.UIString("Other Invalidations");
+        break;
+    }
+
+    var invalidationsTreeOutline = new TreeOutlineInShadow();
+    invalidationsTreeOutline.registerRequiredCSS("timeline/invalidationsTree.css");
+    invalidationsTreeOutline.element.classList.add("invalidations-tree");
+
+    var invalidationGroups = groupInvalidationsByCause(invalidations);
+    invalidationGroups.forEach(function(group) {
+        var groupElement = new WebInspector.TimelineUIUtils.InvalidationsGroupElement(target, relatedNodesMap, contentHelper, group);
+        invalidationsTreeOutline.appendChild(groupElement);
+    });
+    contentHelper.appendElementRow(title, invalidationsTreeOutline.element, false, true);
+
+    /**
+     * @param {!Array<!WebInspector.InvalidationTrackingEvent>} invalidations
+     * @return {!Array<!Array<!WebInspector.InvalidationTrackingEvent>>}
+     */
+    function groupInvalidationsByCause(invalidations)
+    {
+        /** @type {!Map<string, !Array<!WebInspector.InvalidationTrackingEvent>>} */
+        var causeToInvalidationMap = new Map();
+        for (var index = 0; index < invalidations.length; index++) {
+            var invalidation = invalidations[index];
+            var causeKey = "";
+            if (invalidation.cause.reason)
+                causeKey += invalidation.cause.reason + ".";
+            if (invalidation.cause.stackTrace) {
+                invalidation.cause.stackTrace.forEach(function(stackFrame) {
+                    causeKey += stackFrame["functionName"] + ".";
+                    causeKey += stackFrame["scriptId"] + ".";
+                    causeKey += stackFrame["url"] + ".";
+                    causeKey += stackFrame["lineNumber"] + ".";
+                    causeKey += stackFrame["columnNumber"] + ".";
+                });
+            }
+
+            if (causeToInvalidationMap.has(causeKey))
+                causeToInvalidationMap.get(causeKey).push(invalidation);
+            else
+                causeToInvalidationMap.set(causeKey, [ invalidation ]);
+        }
+        return causeToInvalidationMap.valuesArray();
+    }
+}
+
+/**
+ * @param {!Set<number>} nodeIds
+ * @param {!WebInspector.InvalidationTrackingEvent} invalidations
+ */
+WebInspector.TimelineUIUtils._collectInvalidationNodeIds = function(nodeIds, invalidations)
+{
+    for (var i = 0; i < invalidations.length; ++i) {
+        if (invalidations[i].nodeId)
+            nodeIds.add(invalidations[i].nodeId);
+    }
+}
+
+/**
+  * @constructor
+  * @param {!WebInspector.Target} target
+  * @param {?Map<number, ?WebInspector.DOMNode>} relatedNodesMap
+  * @param {!WebInspector.TimelineDetailsContentHelper} contentHelper
+  * @param {!Array.<!WebInspector.InvalidationTrackingEvent>} invalidations
+  * @extends {TreeElement}
+  */
+WebInspector.TimelineUIUtils.InvalidationsGroupElement = function(target, relatedNodesMap, contentHelper, invalidations)
+{
+    TreeElement.call(this, "", true);
+
+    this.listItemElement.classList.add("header");
+    this.selectable = false;
+    this.toggleOnClick = true;
+
+    this._relatedNodesMap = relatedNodesMap;
+    this._contentHelper = contentHelper;
+    this._invalidations = invalidations;
+    this.title = this._createTitle(target);
+}
+
+WebInspector.TimelineUIUtils.InvalidationsGroupElement.prototype = {
+
+    /**
+     * @param {!WebInspector.Target} target
+     * @return {!Element}
+     */
+    _createTitle: function(target)
+    {
+        var first = this._invalidations[0];
+        var reason = first.cause.reason;
+        var topFrame = first.cause.stackTrace && first.cause.stackTrace[0];
+
+        var title = createElement("span");
+        if (reason)
+            title.createTextChild(WebInspector.UIString("%s for ", reason));
+        else
+            title.createTextChild(WebInspector.UIString("Unknown cause for "));
+
+        this._appendTruncatedNodeList(title, this._invalidations);
+
+        if (topFrame && this._contentHelper.linkifier()) {
+            title.createTextChild(WebInspector.UIString(". "));
+            var stack = title.createChild("span", "monospace");
+            stack.createChild("span").textContent = WebInspector.beautifyFunctionName(topFrame.functionName);
+            var link = this._contentHelper.linkifier().maybeLinkifyConsoleCallFrame(target, topFrame);
+            if (link) {
+                stack.createChild("span").textContent = " @ ";
+                stack.createChild("span").appendChild(link);
+            }
+        }
+
+        return title;
+    },
+
+    /**
+     * @override
+     */
+    onpopulate: function()
+    {
+        var content = createElementWithClass("div", "content");
+
+        var first = this._invalidations[0];
+        if (first.cause.stackTrace) {
+            var stack = content.createChild("div");
+            stack.createTextChild(WebInspector.UIString("Stack trace:"));
+            this._contentHelper.createChildStackTraceElement(stack, WebInspector.TimelineUIUtils._stackTraceFromCallFrames(first.cause.stackTrace));
+        }
+
+        content.createTextChild(this._invalidations.length > 1 ? WebInspector.UIString("Nodes:") : WebInspector.UIString("Node:"));
+        var nodeList = content.createChild("div", "node-list");
+        var firstNode = true;
+        for (var i = 0; i < this._invalidations.length; i++) {
+            var invalidation = this._invalidations[i];
+            var invalidationNode = this._createInvalidationNode(invalidation, true);
+            if (invalidationNode) {
+                if (!firstNode)
+                    nodeList.createTextChild(WebInspector.UIString(", "));
+                firstNode = false;
+
+                nodeList.appendChild(invalidationNode);
+
+                var extraData = invalidation.extraData ? ", " + invalidation.extraData : "";
+                if (invalidation.changedId)
+                    nodeList.createTextChild(WebInspector.UIString("(changed id to \"%s\"%s)", invalidation.changedId, extraData));
+                else if (invalidation.changedClass)
+                    nodeList.createTextChild(WebInspector.UIString("(changed class to \"%s\"%s)", invalidation.changedClass, extraData));
+                else if (invalidation.changedAttribute)
+                    nodeList.createTextChild(WebInspector.UIString("(changed attribute to \"%s\"%s)", invalidation.changedAttribute, extraData));
+                else if (invalidation.changedPseudo)
+                    nodeList.createTextChild(WebInspector.UIString("(changed pesudo to \"%s\"%s)", invalidation.changedPseudo, extraData));
+                else if (invalidation.selectorPart)
+                    nodeList.createTextChild(WebInspector.UIString("(changed \"%s\"%s)", invalidation.selectorPart, extraData));
+            }
+        }
+
+        var contentTreeElement = new TreeElement(content, false);
+        contentTreeElement.selectable = false;
+        this.appendChild(contentTreeElement);
+    },
+
+    /**
+     * @param {!Element} parentElement
+     * @param {!Array.<!WebInspector.InvalidationTrackingEvent>} invalidations
+     */
+    _appendTruncatedNodeList: function(parentElement, invalidations)
+    {
+        var invalidationNodes = [];
+        var invalidationNodeIdMap = {};
+        for (var i = 0; i < invalidations.length; i++) {
+            var invalidation = invalidations[i];
+            var invalidationNode = this._createInvalidationNode(invalidation, false);
+            invalidationNode.addEventListener("click", consumeEvent, false);
+            if (invalidationNode && !invalidationNodeIdMap[invalidation.nodeId]) {
+                invalidationNodes.push(invalidationNode);
+                invalidationNodeIdMap[invalidation.nodeId] = true;
+            }
+        }
+
+        if (invalidationNodes.length === 1) {
+            parentElement.appendChild(invalidationNodes[0]);
+        } else if (invalidationNodes.length === 2) {
+            parentElement.appendChild(invalidationNodes[0]);
+            parentElement.createTextChild(WebInspector.UIString(" and "));
+            parentElement.appendChild(invalidationNodes[1]);
+        } else if (invalidationNodes.length >= 3) {
+            parentElement.appendChild(invalidationNodes[0]);
+            parentElement.createTextChild(WebInspector.UIString(", "));
+            parentElement.appendChild(invalidationNodes[1]);
+            parentElement.createTextChild(WebInspector.UIString(", and %s others", invalidationNodes.length - 2));
+        }
+    },
+
+    /**
+     * @param {!WebInspector.InvalidationTrackingEvent} invalidation
+     * @param {boolean} showUnknownNodes
+     */
+    _createInvalidationNode: function(invalidation, showUnknownNodes)
+    {
+        var node = (invalidation.nodeId && this._relatedNodesMap) ? this._relatedNodesMap.get(invalidation.nodeId) : null;
+        if (node)
+            return WebInspector.DOMPresentationUtils.linkifyNodeReference(node);
+        if (invalidation.nodeName) {
+            var nodeSpan = createElement("span");
+            nodeSpan.textContent = WebInspector.UIString("[ %s ]", invalidation.nodeName);
+            return nodeSpan;
+        }
+        if (showUnknownNodes) {
+            var nodeSpan = createElement("span");
+            return nodeSpan.createTextChild(WebInspector.UIString("[ unknown node ]"));
+        }
+    },
+
+    __proto__: TreeElement.prototype
+}
+
+/**
+ * @param {!Object} total
+ * @param {!WebInspector.TimelineModel} model
+ * @param {!WebInspector.TracingModel.Event} event
+ * @return {boolean}
+ */
+WebInspector.TimelineUIUtils._aggregatedStatsForTraceEvent = function(total, model, event)
+{
+    var events = model.inspectedTargetEvents();
+    /**
+     * @param {number} startTime
+     * @param {!WebInspector.TracingModel.Event} e
+     * @return {number}
+     */
+    function eventComparator(startTime, e)
+    {
+        return startTime - e.startTime;
+    }
+    var index = events.binaryIndexOf(event.startTime, eventComparator);
+    // Not a main thread event?
+    if (index < 0)
+        return false;
+    var hasChildren = false;
+    var endTime = event.endTime;
+    if (endTime) {
+        for (var i = index; i < events.length; i++) {
+            var nextEvent = events[i];
+            if (nextEvent.startTime >= endTime)
+                break;
+            if (!nextEvent.selfTime)
+                continue;
+            if (nextEvent.thread !== event.thread)
+                continue;
+            if (i > index)
+                hasChildren = true;
+            var categoryName = WebInspector.TimelineUIUtils.eventStyle(nextEvent).category.name;
+            total[categoryName] = (total[categoryName] || 0) + nextEvent.selfTime;
+        }
+    }
+    if (WebInspector.TracingModel.isAsyncPhase(event.phase)) {
+        if (event.endTime) {
+            var aggregatedTotal = 0;
+            for (var categoryName in total)
+                aggregatedTotal += total[categoryName];
+            total["idle"] = Math.max(0, event.endTime - event.startTime - aggregatedTotal);
+        }
+        return false;
+    }
+    return hasChildren;
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @param {!WebInspector.Target} target
+ * @param {function(!Element=)} callback
+ */
+WebInspector.TimelineUIUtils.buildPicturePreviewContent = function(event, target, callback)
+{
+    new WebInspector.LayerPaintEvent(event, target).loadSnapshot(onSnapshotLoaded);
+    /**
+     * @param {?Array.<number>} rect
+     * @param {?WebInspector.PaintProfilerSnapshot} snapshot
+     */
+    function onSnapshotLoaded(rect, snapshot)
+    {
+        if (!snapshot) {
+            callback();
+            return;
+        }
+        snapshot.requestImage(null, null, 1, onGotImage);
+        snapshot.dispose();
+    }
+
+    /**
+     * @param {string=} imageURL
+     */
+    function onGotImage(imageURL)
+    {
+        if (!imageURL) {
+            callback();
+            return;
+        }
+        var container = createElement("div");
+        container.classList.add("image-preview-container", "vbox", "link");
+        var img = container.createChild("img");
+        img.src = imageURL;
+        var paintProfilerButton = container.createChild("a");
+        paintProfilerButton.textContent = WebInspector.UIString("Paint Profiler");
+        container.addEventListener("click", showPaintProfiler, false);
+        callback(container);
+    }
+
+    function showPaintProfiler()
+    {
+        WebInspector.TimelinePanel.instance().select(WebInspector.TimelineSelection.fromTraceEvent(event), WebInspector.TimelinePanel.DetailsTab.PaintProfiler);
+    }
+}
+
+/**
+ * @param {!WebInspector.TimelineModel.RecordType} recordType
+ * @param {?string} title
+ * @param {number} position
+ * @return {!Element}
+ */
+WebInspector.TimelineUIUtils.createEventDivider = function(recordType, title, position)
+{
+    var eventDivider = createElement("div");
+    eventDivider.className = "resources-event-divider";
+    var recordTypes = WebInspector.TimelineModel.RecordType;
+
+    if (recordType === recordTypes.MarkDOMContent)
+        eventDivider.className += " resources-blue-divider";
+    else if (recordType === recordTypes.MarkLoad)
+        eventDivider.className += " resources-red-divider";
+    else if (recordType === recordTypes.MarkFirstPaint)
+        eventDivider.className += " resources-green-divider";
+    else if (recordType === recordTypes.TimeStamp || recordType === recordTypes.ConsoleTime || recordType === recordTypes.UserTiming)
+        eventDivider.className += " resources-orange-divider";
+    else if (recordType === recordTypes.BeginFrame)
+        eventDivider.className += " timeline-frame-divider";
+
+    if (title)
+        eventDivider.title = title;
+    eventDivider.style.left = position + "px";
+    return eventDivider;
+}
+
+/**
+ * @param {!WebInspector.TimelineModel.Record} record
+ * @param {number} zeroTime
+ * @param {number} position
+ * @return {!Element}
+ */
+WebInspector.TimelineUIUtils.createDividerForRecord = function(record, zeroTime, position)
+{
+    var startTime = Number.millisToString(record.startTime() - zeroTime);
+    var title = WebInspector.UIString("%s at %s", WebInspector.TimelineUIUtils.eventTitle(record.traceEvent()), startTime);
+    return WebInspector.TimelineUIUtils.createEventDivider(record.type(), title, position);
+}
+
+/**
+ * @return {!Array.<string>}
+ */
+WebInspector.TimelineUIUtils._visibleTypes = function()
+{
+    var eventStyles = WebInspector.TimelineUIUtils._initEventStyles();
+    var result = [];
+    for (var name in eventStyles) {
+        if (!eventStyles[name].hidden)
+            result.push(name);
+    }
+    return result;
+}
+
+/**
+ * @return {!WebInspector.TimelineModel.Filter}
+ */
+WebInspector.TimelineUIUtils.visibleEventsFilter = function()
+{
+    return new WebInspector.TimelineVisibleEventsFilter(WebInspector.TimelineUIUtils._visibleTypes());
+}
+
+/**
+ * @return {!Object.<string, !WebInspector.TimelineCategory>}
+ */
+WebInspector.TimelineUIUtils.categories = function()
+{
+    if (WebInspector.TimelineUIUtils._categories)
+        return WebInspector.TimelineUIUtils._categories;
+    WebInspector.TimelineUIUtils._categories = {
+        loading: new WebInspector.TimelineCategory("loading", WebInspector.UIString("Loading"), true, "hsl(214, 67%, 74%)", "hsl(214, 67%, 66%)"),
+        scripting: new WebInspector.TimelineCategory("scripting", WebInspector.UIString("Scripting"), true, "hsl(43, 83%, 72%)", "hsl(43, 83%, 64%) "),
+        rendering: new WebInspector.TimelineCategory("rendering", WebInspector.UIString("Rendering"), true, "hsl(256, 67%, 76%)", "hsl(256, 67%, 70%)"),
+        painting: new WebInspector.TimelineCategory("painting", WebInspector.UIString("Painting"), true, "hsl(109, 33%, 64%)", "hsl(109, 33%, 55%)"),
+        gpu: new WebInspector.TimelineCategory("gpu", WebInspector.UIString("GPU"), false, "hsl(109, 33%, 64%)", "hsl(109, 33%, 55%)"),
+        other: new WebInspector.TimelineCategory("other", WebInspector.UIString("Other"), false, "hsl(0, 0%, 87%)", "hsl(0, 0%, 79%)"),
+        idle: new WebInspector.TimelineCategory("idle", WebInspector.UIString("Idle"), false, "hsl(0, 100%, 100%)", "hsl(0, 100%, 100%)")
+    };
+    return WebInspector.TimelineUIUtils._categories;
+};
+
+/**
+ * @param {!WebInspector.TimelineModel.AsyncEventGroup} group
+ * @return {string}
+ */
+WebInspector.TimelineUIUtils.titleForAsyncEventGroup = function(group)
+{
+    if (!WebInspector.TimelineUIUtils._titleForAsyncEventGroupMap) {
+        var groups = WebInspector.TimelineModel.AsyncEventGroup;
+        WebInspector.TimelineUIUtils._titleForAsyncEventGroupMap = new Map([
+            [groups.animation, WebInspector.UIString("Animation")],
+            [groups.console, WebInspector.UIString("Console")],
+            [groups.userTiming, WebInspector.UIString("User Timing")],
+            [groups.input, WebInspector.UIString("Input")]
+        ]);
+    }
+    return WebInspector.TimelineUIUtils._titleForAsyncEventGroupMap.get(group) || "";
+}
+
+/**
+ * @param {!Object} aggregatedStats
+ * @param {!WebInspector.TimelineCategory=} selfCategory
+ * @param {number=} selfTime
+ * @return {!Element}
+ */
+WebInspector.TimelineUIUtils.generatePieChart = function(aggregatedStats, selfCategory, selfTime)
+{
+    var total = 0;
+    for (var categoryName in aggregatedStats)
+        total += aggregatedStats[categoryName];
+
+    var element = createElementWithClass("div", "timeline-details-view-pie-chart-wrapper hbox");
+    var pieChart = new WebInspector.PieChart(100);
+    pieChart.element.classList.add("timeline-details-view-pie-chart");
+    pieChart.setTotal(total);
+    var pieChartContainer = element.createChild("div", "vbox");
+    pieChartContainer.appendChild(pieChart.element);
+    pieChartContainer.createChild("div", "timeline-details-view-pie-chart-total").textContent = WebInspector.UIString("Total: %s", Number.millisToString(total, true));
+    var footerElement = element.createChild("div", "timeline-aggregated-info-legend");
+
+    /**
+     * @param {string} name
+     * @param {string} title
+     * @param {number} value
+     * @param {string} color
+     */
+    function appendLegendRow(name, title, value, color)
+    {
+        if (!value)
+            return;
+        pieChart.addSlice(value, color);
+        var rowElement = footerElement.createChild("div");
+        rowElement.createChild("span", "timeline-aggregated-legend-value").textContent = Number.preciseMillisToString(value, 1);
+        rowElement.createChild("span", "timeline-aggregated-legend-swatch").style.backgroundColor = color;
+        rowElement.createChild("span", "timeline-aggregated-legend-title").textContent = title;
+    }
+
+    // In case of self time, first add self, then children of the same category.
+    if (selfCategory) {
+        if (selfTime)
+            appendLegendRow(selfCategory.name, WebInspector.UIString("%s (self)", selfCategory.title), selfTime, selfCategory.color);
+        // Children of the same category.
+        var categoryTime = aggregatedStats[selfCategory.name];
+        var value = categoryTime - selfTime;
+        if (value > 0)
+            appendLegendRow(selfCategory.name, WebInspector.UIString("%s (children)", selfCategory.title), value, selfCategory.childColor);
+    }
+
+    // Add other categories.
+    for (var categoryName in WebInspector.TimelineUIUtils.categories()) {
+        var category = WebInspector.TimelineUIUtils.categories()[categoryName];
+        if (category === selfCategory)
+            continue;
+        appendLegendRow(category.name, category.title, aggregatedStats[category.name], category.childColor);
+    }
+    return element;
+}
+
+/**
+ * @param {!WebInspector.TimelineFrameModel} frameModel
+ * @param {!WebInspector.TimelineFrame} frame
+ * @param {?WebInspector.FilmStripModel.Frame} filmStripFrame
+ * @return {!Element}
+ */
+WebInspector.TimelineUIUtils.generateDetailsContentForFrame = function(frameModel, frame, filmStripFrame)
+{
+    var pieChart = WebInspector.TimelineUIUtils.generatePieChart(frame.timeByCategory);
+    var contentHelper = new WebInspector.TimelineDetailsContentHelper(null, null);
+    contentHelper.addSection(WebInspector.UIString("Frame"));
+
+    var duration = WebInspector.TimelineUIUtils.frameDuration(frame);
+    contentHelper.appendElementRow(WebInspector.UIString("Duration"), duration, frame.hasWarnings());
+    if (filmStripFrame) {
+        var filmStripPreview = createElementWithClass("img", "timeline-filmstrip-preview");
+        filmStripFrame.imageDataPromise().then(onGotImageData.bind(null, filmStripPreview));
+        contentHelper.appendElementRow("", filmStripPreview);
+        filmStripPreview.addEventListener("click", frameClicked.bind(null, filmStripFrame), false);
+    }
+    var durationInMillis = frame.endTime - frame.startTime;
+    contentHelper.appendTextRow(WebInspector.UIString("FPS"), Math.floor(1000 / durationInMillis));
+    contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.millisToString(frame.cpuTime, true));
+
+    if (Runtime.experiments.isEnabled("layersPanel") && frame.layerTree) {
+        contentHelper.appendElementRow(WebInspector.UIString("Layer tree"),
+                                       WebInspector.Linkifier.linkifyUsingRevealer(frame.layerTree, WebInspector.UIString("show")));
+    }
+
+    /**
+     * @param {!Element} image
+     * @param {?string} data
+     */
+    function onGotImageData(image, data)
+    {
+        if (data)
+            image.src = "data:image/jpg;base64," + data;
+    }
+
+    /**
+     * @param {!WebInspector.FilmStripModel.Frame} filmStripFrame
+     */
+    function frameClicked(filmStripFrame)
+    {
+        new WebInspector.FilmStripView.Dialog(filmStripFrame, 0);
+    }
+
+    return contentHelper.fragment;
+}
+
+/**
+ * @param {!WebInspector.TimelineFrame} frame
+ * @return {!Element}
+ */
+WebInspector.TimelineUIUtils.frameDuration = function(frame)
+{
+    var durationText = WebInspector.UIString("%s (at %s)", Number.millisToString(frame.endTime - frame.startTime, true),
+        Number.millisToString(frame.startTimeOffset, true));
+    var element = createElement("span");
+    element.createTextChild(durationText);
+    if (!frame.hasWarnings())
+        return element;
+    element.createTextChild(WebInspector.UIString(". Long frame times are an indication of "));
+    element.appendChild(WebInspector.linkifyURLAsNode("https://developers.google.com/web/fundamentals/performance/rendering/",
+                                                      WebInspector.UIString("jank"), undefined, true));
+    element.createTextChild(".");
+    return element;
+}
+
+/**
+ * @param {!CanvasRenderingContext2D} context
+ * @param {number} width
+ * @param {number} height
+ * @param {string} color0
+ * @param {string} color1
+ * @param {string} color2
+ * @return {!CanvasGradient}
+ */
+WebInspector.TimelineUIUtils.createFillStyle = function(context, width, height, color0, color1, color2)
+{
+    var gradient = context.createLinearGradient(0, 0, width, height);
+    gradient.addColorStop(0, color0);
+    gradient.addColorStop(0.25, color1);
+    gradient.addColorStop(0.75, color1);
+    gradient.addColorStop(1, color2);
+    return gradient;
+}
+
+/**
+ * @param {!Array.<number>} quad
+ * @return {number}
+ */
+WebInspector.TimelineUIUtils.quadWidth = function(quad)
+{
+    return Math.round(Math.sqrt(Math.pow(quad[0] - quad[2], 2) + Math.pow(quad[1] - quad[3], 2)));
+}
+
+/**
+ * @param {!Array.<number>} quad
+ * @return {number}
+ */
+WebInspector.TimelineUIUtils.quadHeight = function(quad)
+{
+    return Math.round(Math.sqrt(Math.pow(quad[0] - quad[6], 2) + Math.pow(quad[1] - quad[7], 2)));
+}
+
+/**
+ * @constructor
+ * @param {number} priority
+ * @param {string} color
+ * @param {!Array.<string>} eventTypes
+ */
+WebInspector.TimelineUIUtils.EventDispatchTypeDescriptor = function(priority, color, eventTypes)
+{
+    this.priority = priority;
+    this.color = color;
+    this.eventTypes = eventTypes;
+}
+
+/**
+ * @return {!Array.<!WebInspector.TimelineUIUtils.EventDispatchTypeDescriptor>}
+ */
+WebInspector.TimelineUIUtils.eventDispatchDesciptors = function()
+{
+    if (WebInspector.TimelineUIUtils._eventDispatchDesciptors)
+        return WebInspector.TimelineUIUtils._eventDispatchDesciptors;
+    var lightOrange = "hsl(40,100%,80%)";
+    var orange = "hsl(40,100%,50%)";
+    var green = "hsl(90,100%,40%)";
+    var purple = "hsl(256,100%,75%)";
+    WebInspector.TimelineUIUtils._eventDispatchDesciptors = [
+        new WebInspector.TimelineUIUtils.EventDispatchTypeDescriptor(1, lightOrange, ["mousemove", "mouseenter", "mouseleave", "mouseout", "mouseover"]),
+        new WebInspector.TimelineUIUtils.EventDispatchTypeDescriptor(1, lightOrange, ["pointerover", "pointerout", "pointerenter", "pointerleave", "pointermove"]),
+        new WebInspector.TimelineUIUtils.EventDispatchTypeDescriptor(2, green, ["wheel"]),
+        new WebInspector.TimelineUIUtils.EventDispatchTypeDescriptor(3, orange, ["click", "mousedown", "mouseup"]),
+        new WebInspector.TimelineUIUtils.EventDispatchTypeDescriptor(3, orange, ["touchstart", "touchend", "touchmove", "touchcancel"]),
+        new WebInspector.TimelineUIUtils.EventDispatchTypeDescriptor(3, orange, ["pointerdown", "pointerup", "pointercancel", "gotpointercapture", "lostpointercapture"]),
+        new WebInspector.TimelineUIUtils.EventDispatchTypeDescriptor(3, purple, ["keydown", "keyup", "keypress"])
+    ];
+    return WebInspector.TimelineUIUtils._eventDispatchDesciptors;
+}
+
+/**
+ * @constructor
+ * @extends {WebInspector.Object}
+ * @param {string} name
+ * @param {string} title
+ * @param {boolean} visible
+ * @param {string} childColor
+ * @param {string} color
+ */
+WebInspector.TimelineCategory = function(name, title, visible, childColor, color)
+{
+    this.name = name;
+    this.title = title;
+    this.visible = visible;
+    this.childColor = childColor;
+    this.color = color;
+    this.hidden = false;
+}
+
+/** @enum {symbol} */
+WebInspector.TimelineCategory.Events = {
+    VisibilityChanged: Symbol("VisibilityChanged")
+};
+
+WebInspector.TimelineCategory.prototype = {
+    /**
+     * @return {boolean}
+     */
+    get hidden()
+    {
+        return this._hidden;
+    },
+
+    set hidden(hidden)
+    {
+        this._hidden = hidden;
+        this.dispatchEventToListeners(WebInspector.TimelineCategory.Events.VisibilityChanged, this);
+    },
+
+    __proto__: WebInspector.Object.prototype
+}
+
+/**
+ * @typedef {!{
+ *     title: string,
+ *     color: string,
+ *     lineWidth: number,
+ *     dashStyle: !Array.<number>,
+ *     tall: boolean,
+ *     lowPriority: boolean
+ * }}
+ */
+WebInspector.TimelineMarkerStyle;
+
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @return {!WebInspector.TimelineMarkerStyle}
+ */
+WebInspector.TimelineUIUtils.markerStyleForEvent = function(event)
+{
+    var red = "rgb(255, 0, 0)";
+    var blue = "rgb(0, 0, 255)";
+    var orange = "rgb(255, 178, 23)";
+    var green = "rgb(0, 130, 0)";
+    var tallMarkerDashStyle = [10, 5];
+
+    var title = WebInspector.TimelineUIUtils.eventTitle(event)
+
+    if (event.hasCategory(WebInspector.TimelineModel.Category.Console) || event.hasCategory(WebInspector.TimelineModel.Category.UserTiming)) {
+        return {
+            title: title,
+            dashStyle: tallMarkerDashStyle,
+            lineWidth: 0.5,
+            color: orange,
+            tall: false,
+            lowPriority: false,
+        };
+    }
+    var recordTypes = WebInspector.TimelineModel.RecordType;
+    var tall = false;
+    var color = green;
+    switch (event.name) {
+    case recordTypes.MarkDOMContent:
+        color = blue;
+        tall = true;
+        break;
+    case recordTypes.MarkLoad:
+        color = red;
+        tall = true;
+        break;
+    case recordTypes.MarkFirstPaint:
+        color = green;
+        tall = true;
+        break;
+    case recordTypes.TimeStamp:
+        color = orange;
+        break;
+    }
+    return {
+        title: title,
+        dashStyle: tallMarkerDashStyle,
+        lineWidth: 0.5,
+        color: color,
+        tall: tall,
+        lowPriority: false,
+    };
+}
+
+/**
+ * @return {!WebInspector.TimelineMarkerStyle}
+ */
+WebInspector.TimelineUIUtils.markerStyleForFrame = function()
+{
+    return {
+        title: WebInspector.UIString("Frame"),
+        color: "rgba(100, 100, 100, 0.4)",
+        lineWidth: 3,
+        dashStyle: [3],
+        tall: true,
+        lowPriority: true
+    };
+}
+
+/**
+ * @param {string} url
+ * @return {string}
+ */
+WebInspector.TimelineUIUtils.colorForURL = function(url)
+{
+    if (!WebInspector.TimelineUIUtils.colorForURL._colorGenerator) {
+        WebInspector.TimelineUIUtils.colorForURL._colorGenerator = new WebInspector.FlameChart.ColorGenerator(
+            { min: 30, max: 330 },
+            { min: 50, max: 80, count: 3 },
+            85);
+    }
+    return WebInspector.TimelineUIUtils.colorForURL._colorGenerator.colorForID(url);
+}
+
+/**
+ * @constructor
+ * @param {string} title
+ */
+WebInspector.TimelinePopupContentHelper = function(title)
+{
+    this._contentTable = createElement("table");
+    var titleCell = this._createCell(WebInspector.UIString("%s - Details", title), "timeline-details-title");
+    titleCell.colSpan = 2;
+    var titleRow = createElement("tr");
+    titleRow.appendChild(titleCell);
+    this._contentTable.appendChild(titleRow);
+}
+
+WebInspector.TimelinePopupContentHelper.prototype = {
+    /**
+     * @return {!Element}
+     */
+    contentTable: function()
+    {
+        return this._contentTable;
+    },
+
+    /**
+     * @param {string|number} content
+     * @param {string=} styleName
+     */
+    _createCell: function(content, styleName)
+    {
+        var text = createElement("label");
+        text.createTextChild(String(content));
+        var cell = createElement("td");
+        cell.className = "timeline-details";
+        if (styleName)
+            cell.className += " " + styleName;
+        cell.textContent = content;
+        return cell;
+    },
+
+    /**
+     * @param {string} title
+     * @param {string|number} content
+     */
+    appendTextRow: function(title, content)
+    {
+        var row = createElement("tr");
+        row.appendChild(this._createCell(title, "timeline-details-row-title"));
+        row.appendChild(this._createCell(content, "timeline-details-row-data"));
+        this._contentTable.appendChild(row);
+    },
+
+    /**
+     * @param {string} title
+     * @param {!Node|string} content
+     */
+    appendElementRow: function(title, content)
+    {
+        var row = createElement("tr");
+        var titleCell = this._createCell(title, "timeline-details-row-title");
+        row.appendChild(titleCell);
+        var cell = createElement("td");
+        cell.className = "details";
+        if (content instanceof Node)
+            cell.appendChild(content);
+        else
+            cell.createTextChild(content || "");
+        row.appendChild(cell);
+        this._contentTable.appendChild(row);
+    }
+}
+
+/**
+ * @constructor
+ * @param {?WebInspector.Target} target
+ * @param {?WebInspector.Linkifier} linkifier
+ */
+WebInspector.TimelineDetailsContentHelper = function(target, linkifier)
+{
+    this.fragment = createDocumentFragment();
+
+    this._linkifier = linkifier;
+    this._target = target;
+
+    this.element = createElementWithClass("div", "timeline-details-view-block");
+    this._tableElement = this.element.createChild("div", "vbox timeline-details-chip-body");
+    this.fragment.appendChild(this.element);
+}
+
+WebInspector.TimelineDetailsContentHelper.prototype = {
+    /**
+     * @param {string} title
+     * @param {!WebInspector.TimelineCategory=} category
+     */
+    addSection: function(title, category)
+    {
+        if (!this._tableElement.hasChildNodes()) {
+            this.element.removeChildren();
+        } else {
+            this.element = createElementWithClass("div", "timeline-details-view-block");
+            this.fragment.appendChild(this.element);
+        }
+
+        if (title) {
+            var titleElement = this.element.createChild("div", "timeline-details-chip-title");
+            if (category)
+                titleElement.createChild("div").style.backgroundColor = category.color;
+            titleElement.createTextChild(title);
+        }
+
+        this._tableElement = this.element.createChild("div", "vbox timeline-details-chip-body");
+        this.fragment.appendChild(this.element);
+    },
+
+    /**
+     * @return {?WebInspector.Linkifier}
+     */
+    linkifier: function()
+    {
+        return this._linkifier;
+    },
+
+    /**
+     * @param {string} title
+     * @param {string|number|boolean} value
+     */
+    appendTextRow: function(title, value)
+    {
+        var rowElement = this._tableElement.createChild("div", "timeline-details-view-row");
+        rowElement.createChild("div", "timeline-details-view-row-title").textContent = title;
+        rowElement.createChild("div", "timeline-details-view-row-value").textContent = value;
+    },
+
+    /**
+     * @param {string} title
+     * @param {!Node|string} content
+     * @param {boolean=} isWarning
+     * @param {boolean=} isStacked
+     */
+    appendElementRow: function(title, content, isWarning, isStacked)
+    {
+        var rowElement = this._tableElement.createChild("div", "timeline-details-view-row");
+        if (isWarning)
+            rowElement.classList.add("timeline-details-warning");
+        if (isStacked)
+            rowElement.classList.add("timeline-details-stack-values");
+        var titleElement = rowElement.createChild("div", "timeline-details-view-row-title");
+        titleElement.textContent = title;
+        var valueElement = rowElement.createChild("div", "timeline-details-view-row-value");
+        if (content instanceof Node)
+            valueElement.appendChild(content);
+        else
+            valueElement.createTextChild(content || "");
+    },
+
+    /**
+     * @param {string} title
+     * @param {string} url
+     * @param {number} startLine
+     * @param {number=} startColumn
+     */
+    appendLocationRow: function(title, url, startLine, startColumn)
+    {
+        if (!this._linkifier || !this._target)
+            return;
+        var link = this._linkifier.maybeLinkifyScriptLocation(this._target, null, url, startLine, startColumn);
+        if (!link)
+            return;
+        this.appendElementRow(title, link);
+    },
+
+    /**
+     * @param {string} title
+     * @param {string} url
+     * @param {number} startLine
+     * @param {number=} endLine
+     */
+    appendLocationRange: function(title, url, startLine, endLine)
+    {
+        if (!this._linkifier || !this._target)
+            return;
+        var locationContent = createElement("span");
+        var link = this._linkifier.maybeLinkifyScriptLocation(this._target, null, url, startLine);
+        if (!link)
+            return;
+        locationContent.appendChild(link);
+        locationContent.createTextChild(String.sprintf(" [%s\u2026%s]", startLine + 1, endLine + 1 || ""));
+        this.appendElementRow(title, locationContent);
+    },
+
+    /**
+     * @param {string} title
+     * @param {!RuntimeAgent.StackTrace} stackTrace
+     */
+    appendStackTrace: function(title, stackTrace)
+    {
+        if (!this._linkifier || !this._target)
+            return;
+
+        var rowElement = this._tableElement.createChild("div", "timeline-details-view-row");
+        rowElement.createChild("div", "timeline-details-view-row-title").textContent = title;
+        this.createChildStackTraceElement(rowElement, stackTrace);
+    },
+
+    /**
+     * @param {!Element} parentElement
+     * @param {!RuntimeAgent.StackTrace} stackTrace
+     */
+    createChildStackTraceElement: function(parentElement, stackTrace)
+    {
+        if (!this._linkifier || !this._target)
+            return;
+        parentElement.classList.add("timeline-details-stack-values");
+        var stackTraceElement = parentElement.createChild("div", "timeline-details-view-row-value timeline-details-view-row-stack-trace");
+        var callFrameElem = WebInspector.DOMPresentationUtils.buildStackTracePreviewContents(this._target, this._linkifier, stackTrace);
+        stackTraceElement.appendChild(callFrameElem);
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} event
+     * @param {string=} warningType
+     */
+    appendWarningRow: function(event, warningType)
+    {
+        var warning = WebInspector.TimelineUIUtils.eventWarning(event, warningType);
+        if (warning)
+            this.appendElementRow(WebInspector.UIString("Warning"), warning, true);
+    }
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @param {string=} warningType
+ * @return {?Element}
+ */
+WebInspector.TimelineUIUtils.eventWarning = function(event, warningType)
+{
+    var warning = warningType || event.warning;
+    if (!warning)
+        return null;
+    var warnings = WebInspector.TimelineModel.WarningType;
+    var span = createElement("span");
+    var eventData = event.args["data"];
+
+    switch (warning) {
+    case warnings.ForcedStyle:
+    case warnings.ForcedLayout:
+        span.appendChild(WebInspector.linkifyDocumentationURLAsNode("../../fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing#avoid-forced-synchronous-layouts",
+            WebInspector.UIString("Forced reflow")));
+        span.createTextChild(WebInspector.UIString(" is a likely performance bottleneck."));
+        break;
+    case warnings.IdleDeadlineExceeded:
+        span.textContent = WebInspector.UIString("Idle callback execution extended beyond deadline by " +
+            Number.millisToString(event.duration - eventData["allottedMilliseconds"], true));
+        break;
+    case warnings.V8Deopt:
+        span.appendChild(WebInspector.linkifyURLAsNode("https://github.com/GoogleChrome/devtools-docs/issues/53",
+            WebInspector.UIString("Not optimized"), undefined, true));
+        span.createTextChild(WebInspector.UIString(": %s", eventData["deoptReason"]));
+        break;
+    default:
+        console.assert(false, "Unhandled TimelineModel.WarningType");
+    }
+    return span;
+}
+
+},{}],255:[function(require,module,exports){
+/*
+ * Copyright (C) 2013 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @typedef {!{
+        bounds: {height: number, width: number},
+        children: Array.<!WebInspector.TracingLayerPayload>,
+        layer_id: number,
+        position: Array.<number>,
+        scroll_offset: Array.<number>,
+        layer_quad: Array.<number>,
+        draws_content: number,
+        gpu_memory_usage: number,
+        transform: Array.<number>,
+        owner_node: number,
+        compositing_reasons: Array.<string>
+    }}
+*/
+WebInspector.TracingLayerPayload;
+
+/** @typedef {!{
+        id: string,
+        layer_id: number,
+        gpu_memory_usage: number,
+        content_rect: !Array.<number>
+    }}
+*/
+WebInspector.TracingLayerTile;
+
+/**
+  * @constructor
+  * @extends {WebInspector.SDKModel}
+  */
+WebInspector.LayerTreeModel = function(target)
+{
+    WebInspector.SDKModel.call(this, WebInspector.LayerTreeModel, target);
+    target.registerLayerTreeDispatcher(new WebInspector.LayerTreeDispatcher(this));
+    WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Events.MainFrameNavigated, this._onMainFrameNavigated, this);
+    /** @type {?WebInspector.LayerTreeBase} */
+    this._layerTree = null;
+}
+
+/** @enum {symbol} */
+WebInspector.LayerTreeModel.Events = {
+    LayerTreeChanged: Symbol("LayerTreeChanged"),
+    LayerPainted: Symbol("LayerPainted"),
+}
+
+WebInspector.LayerTreeModel.ScrollRectType = {
+    NonFastScrollable: {name: "NonFastScrollable", description: "Non fast scrollable"},
+    TouchEventHandler: {name: "TouchEventHandler", description: "Touch event handler"},
+    WheelEventHandler: {name: "WheelEventHandler", description: "Wheel event handler"},
+    RepaintsOnScroll: {name: "RepaintsOnScroll", description: "Repaints on scroll"}
+}
+
+WebInspector.LayerTreeModel.prototype = {
+    disable: function()
+    {
+        if (!this._enabled)
+            return;
+        this._enabled = false;
+        this._layerTree = null;
+        this.target().layerTreeAgent().disable();
+    },
+
+    enable: function()
+    {
+        if (this._enabled)
+            return;
+        this._enabled = true;
+        this._forceEnable();
+    },
+
+    _forceEnable: function()
+    {
+        this._layerTree = new WebInspector.AgentLayerTree(this.target());
+        this._lastPaintRectByLayerId = {};
+        this.target().layerTreeAgent().enable();
+    },
+
+    /**
+     * @param {!WebInspector.LayerTreeBase} layerTree
+     */
+    setLayerTree: function(layerTree)
+    {
+        this.disable();
+        this._layerTree = layerTree;
+        this.dispatchEventToListeners(WebInspector.LayerTreeModel.Events.LayerTreeChanged);
+    },
+
+    /**
+     * @return {?WebInspector.LayerTreeBase}
+     */
+    layerTree: function()
+    {
+        return this._layerTree;
+    },
+
+    /**
+     * @param {?Array.<!LayerTreeAgent.Layer>} layers
+     */
+    _layerTreeChanged: function(layers)
+    {
+        if (!this._enabled)
+            return;
+        var layerTree = /** @type {!WebInspector.AgentLayerTree} */ (this._layerTree);
+        layerTree.setLayers(layers, onLayersSet.bind(this));
+
+        /**
+         * @this {WebInspector.LayerTreeModel}
+         */
+        function onLayersSet()
+        {
+            for (var layerId in this._lastPaintRectByLayerId) {
+                var lastPaintRect = this._lastPaintRectByLayerId[layerId];
+                var layer = layerTree.layerById(layerId);
+                if (layer)
+                    layer._lastPaintRect = lastPaintRect;
+            }
+            this._lastPaintRectByLayerId = {};
+
+            this.dispatchEventToListeners(WebInspector.LayerTreeModel.Events.LayerTreeChanged);
+        }
+    },
+
+    /**
+     * @param {!LayerTreeAgent.LayerId} layerId
+     * @param {!DOMAgent.Rect} clipRect
+     */
+    _layerPainted: function(layerId, clipRect)
+    {
+        if (!this._enabled)
+            return;
+        var layerTree = /** @type {!WebInspector.AgentLayerTree} */ (this._layerTree);
+        var layer = layerTree.layerById(layerId);
+        if (!layer) {
+            this._lastPaintRectByLayerId[layerId] = clipRect;
+            return;
+        }
+        layer._didPaint(clipRect);
+        this.dispatchEventToListeners(WebInspector.LayerTreeModel.Events.LayerPainted, layer);
+    },
+
+    _onMainFrameNavigated: function()
+    {
+        if (this._enabled)
+            this._forceEnable();
+    },
+
+    __proto__: WebInspector.SDKModel.prototype
+}
+
+/**
+  * @constructor
+  * @param {?WebInspector.Target} target
+  */
+WebInspector.LayerTreeBase = function(target)
+{
+    this._target = target;
+    this._domModel = target ? WebInspector.DOMModel.fromTarget(target) : null;
+    this._layersById = {};
+    /** @type Map<number, ?WebInspector.DOMNode> */
+    this._backendNodeIdToNode = new Map();
+    this._reset();
+}
+
+WebInspector.LayerTreeBase.prototype = {
+    _reset: function()
+    {
+        this._root = null;
+        this._contentRoot = null;
+        this._layers = null;
+    },
+
+    /**
+     * @return {?WebInspector.Target}
+     */
+    target: function()
+    {
+        return this._target;
+    },
+
+    /**
+     * @return {?WebInspector.Layer}
+     */
+    root: function()
+    {
+        return this._root;
+    },
+
+    /**
+     * @return {?WebInspector.Layer}
+     */
+    contentRoot: function()
+    {
+        return this._contentRoot;
+    },
+
+    /**
+     * @return {!Array.<!WebInspector.Layer>}
+     */
+    layers: function()
+    {
+        return this._layers;
+    },
+
+    /**
+     * @param {function(!WebInspector.Layer)} callback
+     * @param {?WebInspector.Layer=} root
+     * @return {boolean}
+     */
+    forEachLayer: function(callback, root)
+    {
+        if (!root) {
+            root = this.root();
+            if (!root)
+                return false;
+        }
+        return callback(root) || root.children().some(this.forEachLayer.bind(this, callback));
+    },
+
+    /**
+     * @param {string} id
+     * @return {?WebInspector.Layer}
+     */
+    layerById: function(id)
+    {
+        return this._layersById[id] || null;
+    },
+
+    /**
+     * @param {!Set<number>} requestedNodeIds
+     * @param {function()} callback
+     */
+    _resolveBackendNodeIds: function(requestedNodeIds, callback)
+    {
+        if (!requestedNodeIds.size || !this._domModel) {
+            callback();
+            return;
+        }
+        if (this._domModel)
+            this._domModel.pushNodesByBackendIdsToFrontend(requestedNodeIds, populateBackendNodeMap.bind(this));
+
+        /**
+         * @this {WebInspector.LayerTreeBase}
+         * @param {?Map<number, ?WebInspector.DOMNode>} nodesMap
+         */
+        function populateBackendNodeMap(nodesMap)
+        {
+            if (nodesMap) {
+                for (var nodeId of nodesMap.keysArray())
+                    this._backendNodeIdToNode.set(nodeId, nodesMap.get(nodeId) || null);
+            }
+            callback();
+        }
+    },
+
+    /**
+     * @param {!Object} viewportSize
+     */
+    setViewportSize: function(viewportSize)
+    {
+        this._viewportSize = viewportSize;
+    },
+
+    /**
+     * @return {!Object | undefined}
+     */
+    viewportSize: function()
+    {
+        return this._viewportSize;
+    },
+
+    /**
+     * @param {number} id
+     * @return {?WebInspector.DOMNode}
+     */
+    _nodeForId: function(id)
+    {
+        return this._domModel ? this._domModel.nodeForId(id) : null;
+    }
+}
+
+/**
+  * @constructor
+  * @extends {WebInspector.LayerTreeBase}
+  * @param {?WebInspector.Target} target
+  */
+WebInspector.TracingLayerTree = function(target)
+{
+    WebInspector.LayerTreeBase.call(this, target);
+    /** @type {!Map.<string, !WebInspector.TracingLayerTile>} */
+    this._tileById = new Map();
+}
+
+WebInspector.TracingLayerTree.prototype = {
+    /**
+     * @param {!WebInspector.TracingLayerPayload} root
+     * @param {?Array.<!WebInspector.TracingLayerPayload>} layers
+     * @param {function()} callback
+     */
+    setLayers: function(root, layers, callback)
+    {
+        var idsToResolve = new Set();
+        if (root) {
+            // This is a legacy code path for compatibility, as cc is removing
+            // layer tree hierarchy, this code will eventually be removed.
+            this._extractNodeIdsToResolve(idsToResolve, {}, root);
+        } else {
+            for (var i = 0; i < layers.length; ++i)
+                this._extractNodeIdsToResolve(idsToResolve, {}, layers[i]);
+        }
+        this._resolveBackendNodeIds(idsToResolve, onBackendNodeIdsResolved.bind(this));
+
+        /**
+         * @this {WebInspector.TracingLayerTree}
+         */
+        function onBackendNodeIdsResolved()
+        {
+            var oldLayersById = this._layersById;
+            this._layersById = {};
+            this._contentRoot = null;
+            if (root) {
+                this._root = this._innerSetLayers(oldLayersById, root);
+            } else {
+                this._layers = layers.map(this._innerSetLayers.bind(this, oldLayersById));
+                this._root = this._contentRoot;
+                for (var i = 0; i < this._layers.length; ++i) {
+                    if (this._layers[i].id() !== this._contentRoot.id()) {
+                        this._contentRoot.addChild(this._layers[i]);
+                    }
+                }
+            }
+            callback();
+        }
+    },
+
+    /**
+     * @param {!Array.<!WebInspector.TracingLayerTile>} tiles
+     */
+    setTiles: function(tiles)
+    {
+        this._tileById = new Map();
+        for (var tile of tiles)
+            this._tileById.set(tile.id, tile);
+    },
+
+    /**
+     * @param {string} id
+     * @return {?WebInspector.TracingLayerTile}
+     */
+    tileById: function(id)
+    {
+        return this._tileById.get(id) || null;
+    },
+
+    /**
+     * @param {!Object.<(string|number), !WebInspector.Layer>} oldLayersById
+     * @param {!WebInspector.TracingLayerPayload} payload
+     * @return {!WebInspector.TracingLayer}
+     */
+    _innerSetLayers: function(oldLayersById, payload)
+    {
+        var layer = /** @type {?WebInspector.TracingLayer} */ (oldLayersById[payload.layer_id]);
+        if (layer)
+            layer._reset(payload);
+        else
+            layer = new WebInspector.TracingLayer(payload);
+        this._layersById[payload.layer_id] = layer;
+        if (payload.owner_node)
+            layer._setNode(this._backendNodeIdToNode.get(payload.owner_node) || null);
+        if (!this._contentRoot && layer.drawsContent())
+            this._contentRoot = layer;
+        for (var i = 0; payload.children && i < payload.children.length; ++i)
+            layer.addChild(this._innerSetLayers(oldLayersById, payload.children[i]));
+        return layer;
+    },
+
+    /**
+     * @param {!Set<number>} nodeIdsToResolve
+     * @param {!Object} seenNodeIds
+     * @param {!WebInspector.TracingLayerPayload} payload
+     */
+    _extractNodeIdsToResolve: function(nodeIdsToResolve, seenNodeIds, payload)
+    {
+        var backendNodeId = payload.owner_node;
+        if (backendNodeId && !this._backendNodeIdToNode.has(backendNodeId))
+            nodeIdsToResolve.add(backendNodeId);
+        for (var i = 0; payload.children && i < payload.children.length; ++i)
+            this._extractNodeIdsToResolve(nodeIdsToResolve, seenNodeIds, payload.children[i]);
+    },
+
+    __proto__: WebInspector.LayerTreeBase.prototype
+}
+
+/**
+  * @constructor
+  * @param {?WebInspector.Target} target
+  * @extends {WebInspector.LayerTreeBase}
+  */
+WebInspector.AgentLayerTree = function(target)
+{
+    WebInspector.LayerTreeBase.call(this, target);
+}
+
+WebInspector.AgentLayerTree.prototype = {
+    /**
+     * @param {?Array.<!LayerTreeAgent.Layer>} payload
+     * @param {function()} callback
+     */
+    setLayers: function(payload, callback)
+    {
+        if (!payload) {
+            onBackendNodeIdsResolved.call(this);
+            return;
+        }
+
+        var idsToResolve = new Set();
+        for (var i = 0; i < payload.length; ++i) {
+            var backendNodeId = payload[i].backendNodeId;
+            if (!backendNodeId || this._backendNodeIdToNode.has(backendNodeId))
+                continue;
+            idsToResolve.add(backendNodeId);
+        }
+        this._resolveBackendNodeIds(idsToResolve, onBackendNodeIdsResolved.bind(this));
+
+        /**
+         * @this {WebInspector.AgentLayerTree}
+         */
+        function onBackendNodeIdsResolved()
+        {
+            this._innerSetLayers(payload);
+            callback();
+        }
+    },
+
+    /**
+     * @param {?Array.<!LayerTreeAgent.Layer>} layers
+     */
+    _innerSetLayers: function(layers)
+    {
+        this._reset();
+        // Payload will be null when not in the composited mode.
+        if (!layers)
+            return;
+        var oldLayersById = this._layersById;
+        this._layersById = {};
+        for (var i = 0; i < layers.length; ++i) {
+            var layerId = layers[i].layerId;
+            var layer = oldLayersById[layerId];
+            if (layer)
+                layer._reset(layers[i]);
+            else
+                layer = new WebInspector.AgentLayer(this._target, layers[i]);
+            this._layersById[layerId] = layer;
+            var backendNodeId = layers[i].backendNodeId;
+            if (backendNodeId)
+                layer._setNode(this._backendNodeIdToNode.get(backendNodeId));
+            if (!this._contentRoot && layer.drawsContent())
+                this._contentRoot = layer;
+            var parentId = layer.parentId();
+            if (parentId) {
+                var parent = this._layersById[parentId];
+                if (!parent)
+                    console.assert(parent, "missing parent " + parentId + " for layer " + layerId);
+                parent.addChild(layer);
+            } else {
+                if (this._root)
+                    console.assert(false, "Multiple root layers");
+                this._root = layer;
+            }
+        }
+        if (this._root)
+            this._root._calculateQuad(new WebKitCSSMatrix());
+    },
+
+    __proto__: WebInspector.LayerTreeBase.prototype
+}
+
+/**
+ * @interface
+ */
+WebInspector.Layer = function()
+{
+}
+
+WebInspector.Layer.prototype = {
+    /**
+     * @return {string}
+     */
+    id: function() { },
+
+    /**
+     * @return {?string}
+     */
+    parentId: function() { },
+
+    /**
+     * @return {?WebInspector.Layer}
+     */
+    parent: function() { },
+
+    /**
+     * @return {boolean}
+     */
+    isRoot: function() { },
+
+    /**
+     * @return {!Array.<!WebInspector.Layer>}
+     */
+    children: function() { },
+
+    /**
+     * @param {!WebInspector.Layer} child
+     */
+    addChild: function(child) { },
+
+    /**
+     * @return {?WebInspector.DOMNode}
+     */
+    node: function() { },
+
+    /**
+     * @return {?WebInspector.DOMNode}
+     */
+    nodeForSelfOrAncestor: function() { },
+
+    /**
+     * @return {number}
+     */
+    offsetX: function() { },
+
+    /**
+     * @return {number}
+     */
+    offsetY: function() { },
+
+    /**
+     * @return {number}
+     */
+    width: function() { },
+
+    /**
+     * @return {number}
+     */
+    height: function() { },
+
+    /**
+     * @return {?Array.<number>}
+     */
+    transform: function() { },
+
+    /**
+     * @return {!Array.<number>}
+     */
+    quad: function() { },
+
+    /**
+     * @return {!Array.<number>}
+     */
+    anchorPoint: function() { },
+
+    /**
+     * @return {boolean}
+     */
+    invisible: function() { },
+
+    /**
+     * @return {number}
+     */
+    paintCount: function() { },
+
+    /**
+     * @return {?DOMAgent.Rect}
+     */
+    lastPaintRect: function() { },
+
+    /**
+     * @return {!Array.<!LayerTreeAgent.ScrollRect>}
+     */
+    scrollRects: function() { },
+
+    /**
+     * @return {number}
+     */
+    gpuMemoryUsage: function() { },
+
+    /**
+     * @param {function(!Array.<string>)} callback
+     */
+    requestCompositingReasons: function(callback) { },
+
+    /**
+     * @return {boolean}
+     */
+    drawsContent: function() { }
+}
+
+/**
+ * @constructor
+ * @implements {WebInspector.Layer}
+ * @param {?WebInspector.Target} target
+ * @param {!LayerTreeAgent.Layer} layerPayload
+ */
+WebInspector.AgentLayer = function(target, layerPayload)
+{
+    this._target = target;
+    this._reset(layerPayload);
+}
+
+WebInspector.AgentLayer.prototype = {
+    /**
+     * @override
+     * @return {string}
+     */
+    id: function()
+    {
+        return this._layerPayload.layerId;
+    },
+
+    /**
+     * @override
+     * @return {?string}
+     */
+    parentId: function()
+    {
+        return this._layerPayload.parentLayerId;
+    },
+
+    /**
+     * @override
+     * @return {?WebInspector.Layer}
+     */
+    parent: function()
+    {
+        return this._parent;
+    },
+
+    /**
+     * @override
+     * @return {boolean}
+     */
+    isRoot: function()
+    {
+        return !this.parentId();
+    },
+
+    /**
+     * @override
+     * @return {!Array.<!WebInspector.Layer>}
+     */
+    children: function()
+    {
+        return this._children;
+    },
+
+    /**
+     * @override
+     * @param {!WebInspector.Layer} child
+     */
+    addChild: function(child)
+    {
+        if (child._parent)
+            console.assert(false, "Child already has a parent");
+        this._children.push(child);
+        child._parent = this;
+    },
+
+    /**
+     * @param {?WebInspector.DOMNode} node
+     */
+    _setNode: function(node)
+    {
+        this._node = node;
+    },
+
+    /**
+     * @override
+     * @return {?WebInspector.DOMNode}
+     */
+    node: function()
+    {
+        return this._node;
+    },
+
+    /**
+     * @override
+     * @return {?WebInspector.DOMNode}
+     */
+    nodeForSelfOrAncestor: function()
+    {
+        for (var layer = this; layer; layer = layer._parent) {
+            if (layer._node)
+                return layer._node;
+        }
+        return null;
+    },
+
+    /**
+     * @override
+     * @return {number}
+     */
+    offsetX: function()
+    {
+        return this._layerPayload.offsetX;
+    },
+
+    /**
+     * @override
+     * @return {number}
+     */
+    offsetY: function()
+    {
+        return this._layerPayload.offsetY;
+    },
+
+    /**
+     * @override
+     * @return {number}
+     */
+    width: function()
+    {
+        return this._layerPayload.width;
+    },
+
+    /**
+     * @override
+     * @return {number}
+     */
+    height: function()
+    {
+        return this._layerPayload.height;
+    },
+
+    /**
+     * @override
+     * @return {?Array.<number>}
+     */
+    transform: function()
+    {
+        return this._layerPayload.transform;
+    },
+
+    /**
+     * @override
+     * @return {!Array.<number>}
+     */
+    quad: function()
+    {
+        return this._quad;
+    },
+
+    /**
+     * @override
+     * @return {!Array.<number>}
+     */
+    anchorPoint: function()
+    {
+        return [
+            this._layerPayload.anchorX || 0,
+            this._layerPayload.anchorY || 0,
+            this._layerPayload.anchorZ || 0,
+        ];
+    },
+
+    /**
+     * @override
+     * @return {boolean}
+     */
+    invisible: function()
+    {
+        return this._layerPayload.invisible;
+    },
+
+    /**
+     * @override
+     * @return {number}
+     */
+    paintCount: function()
+    {
+        return this._paintCount || this._layerPayload.paintCount;
+    },
+
+    /**
+     * @override
+     * @return {?DOMAgent.Rect}
+     */
+    lastPaintRect: function()
+    {
+        return this._lastPaintRect;
+    },
+
+    /**
+     * @override
+     * @return {!Array.<!LayerTreeAgent.ScrollRect>}
+     */
+    scrollRects: function()
+    {
+        return this._scrollRects;
+    },
+
+    /**
+     * @override
+     * @param {function(!Array.<string>)} callback
+     */
+    requestCompositingReasons: function(callback)
+    {
+        if (!this._target) {
+            callback([]);
+            return;
+        }
+
+        var wrappedCallback = InspectorBackend.wrapClientCallback(callback, "LayerTreeAgent.reasonsForCompositingLayer(): ", undefined, []);
+        this._target.layerTreeAgent().compositingReasons(this.id(), wrappedCallback);
+    },
+
+    /**
+     * @override
+     * @return {boolean}
+     */
+    drawsContent: function()
+    {
+        return this._layerPayload.drawsContent;
+    },
+
+    /**
+     * @override
+     * @return {number}
+     */
+    gpuMemoryUsage: function()
+    {
+        /**
+         * @const
+         */
+        var bytesPerPixel = 4;
+        return this.drawsContent() ? this.width() * this.height() * bytesPerPixel : 0;
+    },
+
+    /**
+     * @param {function(!WebInspector.PaintProfilerSnapshot=)} callback
+     */
+    requestSnapshot: function(callback)
+    {
+        if (!this._target) {
+            callback();
+            return;
+        }
+
+        var wrappedCallback = InspectorBackend.wrapClientCallback(callback, "LayerTreeAgent.makeSnapshot(): ", WebInspector.PaintProfilerSnapshot.bind(null, this._target));
+        this._target.layerTreeAgent().makeSnapshot(this.id(), wrappedCallback);
+    },
+
+    /**
+     * @param {!DOMAgent.Rect} rect
+     */
+    _didPaint: function(rect)
+    {
+        this._lastPaintRect = rect;
+        this._paintCount = this.paintCount() + 1;
+        this._image = null;
+    },
+
+    /**
+     * @param {!LayerTreeAgent.Layer} layerPayload
+     */
+    _reset: function(layerPayload)
+    {
+        /** @type {?WebInspector.DOMNode} */
+        this._node = null;
+        this._children = [];
+        this._parent = null;
+        this._paintCount = 0;
+        this._layerPayload = layerPayload;
+        this._image = null;
+        this._scrollRects = this._layerPayload.scrollRects || [];
+    },
+
+    /**
+     * @param {!Array.<number>} a
+     * @return {!CSSMatrix}
+     */
+    _matrixFromArray: function(a)
+    {
+        function toFixed9(x) { return x.toFixed(9); }
+        return new WebKitCSSMatrix("matrix3d(" + a.map(toFixed9).join(",") + ")");
+    },
+
+    /**
+     * @param {!CSSMatrix} parentTransform
+     * @return {!CSSMatrix}
+     */
+    _calculateTransformToViewport: function(parentTransform)
+    {
+        var offsetMatrix = new WebKitCSSMatrix().translate(this._layerPayload.offsetX, this._layerPayload.offsetY);
+        var matrix = offsetMatrix;
+
+        if (this._layerPayload.transform) {
+            var transformMatrix = this._matrixFromArray(this._layerPayload.transform);
+            var anchorVector = new WebInspector.Geometry.Vector(this._layerPayload.width * this.anchorPoint()[0], this._layerPayload.height * this.anchorPoint()[1], this.anchorPoint()[2]);
+            var anchorPoint = WebInspector.Geometry.multiplyVectorByMatrixAndNormalize(anchorVector, matrix);
+            var anchorMatrix = new WebKitCSSMatrix().translate(-anchorPoint.x, -anchorPoint.y, -anchorPoint.z);
+            matrix = anchorMatrix.inverse().multiply(transformMatrix.multiply(anchorMatrix.multiply(matrix)));
+        }
+
+        matrix = parentTransform.multiply(matrix);
+        return matrix;
+    },
+
+    /**
+     * @param {number} width
+     * @param {number} height
+     * @return {!Array.<number>}
+     */
+    _createVertexArrayForRect: function(width, height)
+    {
+        return [0, 0, 0, width, 0, 0, width, height, 0, 0, height, 0];
+    },
+
+    /**
+     * @param {!CSSMatrix} parentTransform
+     */
+    _calculateQuad: function(parentTransform)
+    {
+        var matrix = this._calculateTransformToViewport(parentTransform);
+        this._quad = [];
+        var vertices = this._createVertexArrayForRect(this._layerPayload.width, this._layerPayload.height);
+        for (var i = 0; i < 4; ++i) {
+            var point = WebInspector.Geometry.multiplyVectorByMatrixAndNormalize(new WebInspector.Geometry.Vector(vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2]), matrix);
+            this._quad.push(point.x, point.y);
+        }
+
+        function calculateQuadForLayer(layer)
+        {
+            layer._calculateQuad(matrix);
+        }
+
+        this._children.forEach(calculateQuadForLayer);
+    }
+}
+
+/**
+ * @constructor
+ * @param {!WebInspector.TracingLayerPayload} payload
+ * @implements {WebInspector.Layer}
+ */
+WebInspector.TracingLayer = function(payload)
+{
+    this._reset(payload);
+}
+
+WebInspector.TracingLayer.prototype = {
+    /**
+     * @param {!WebInspector.TracingLayerPayload} payload
+     */
+    _reset: function(payload)
+    {
+        /** @type {?WebInspector.DOMNode} */
+        this._node = null;
+        this._layerId = String(payload.layer_id);
+        this._offsetX = payload.position[0];
+        this._offsetY = payload.position[1];
+        this._width = payload.bounds.width;
+        this._height = payload.bounds.height;
+        this._children = [];
+        this._parentLayerId = null;
+        this._parent = null;
+        this._quad = payload.layer_quad || [];
+        this._createScrollRects(payload);
+        this._compositingReasons = payload.compositing_reasons || [];
+        this._drawsContent = !!payload.draws_content;
+        this._gpuMemoryUsage = payload.gpu_memory_usage;
+    },
+
+    /**
+     * @override
+     * @return {string}
+     */
+    id: function()
+    {
+        return this._layerId;
+    },
+
+    /**
+     * @override
+     * @return {?string}
+     */
+    parentId: function()
+    {
+        return this._parentLayerId;
+    },
+
+    /**
+     * @override
+     * @return {?WebInspector.Layer}
+     */
+    parent: function()
+    {
+        return this._parent;
+    },
+
+    /**
+     * @override
+     * @return {boolean}
+     */
+    isRoot: function()
+    {
+        return !this.parentId();
+    },
+
+    /**
+     * @override
+     * @return {!Array.<!WebInspector.Layer>}
+     */
+    children: function()
+    {
+        return this._children;
+    },
+
+    /**
+     * @override
+     * @param {!WebInspector.Layer} child
+     */
+    addChild: function(child)
+    {
+        if (child._parent)
+            console.assert(false, "Child already has a parent");
+        this._children.push(child);
+        child._parent = this;
+        child._parentLayerId = this._layerId;
+    },
+
+
+    /**
+     * @param {?WebInspector.DOMNode} node
+     */
+    _setNode: function(node)
+    {
+        this._node = node;
+    },
+
+    /**
+     * @override
+     * @return {?WebInspector.DOMNode}
+     */
+    node: function()
+    {
+        return this._node;
+    },
+
+    /**
+     * @override
+     * @return {?WebInspector.DOMNode}
+     */
+    nodeForSelfOrAncestor: function()
+    {
+        for (var layer = this; layer; layer = layer._parent) {
+            if (layer._node)
+                return layer._node;
+        }
+        return null;
+    },
+
+    /**
+     * @override
+     * @return {number}
+     */
+    offsetX: function()
+    {
+        return this._offsetX;
+    },
+
+    /**
+     * @override
+     * @return {number}
+     */
+    offsetY: function()
+    {
+        return this._offsetY;
+    },
+
+    /**
+     * @override
+     * @return {number}
+     */
+    width: function()
+    {
+        return this._width;
+    },
+
+    /**
+     * @override
+     * @return {number}
+     */
+    height: function()
+    {
+        return this._height;
+    },
+
+    /**
+     * @override
+     * @return {?Array.<number>}
+     */
+    transform: function()
+    {
+        return null;
+    },
+
+    /**
+     * @override
+     * @return {!Array.<number>}
+     */
+    quad: function()
+    {
+        return this._quad;
+    },
+
+    /**
+     * @override
+     * @return {!Array.<number>}
+     */
+    anchorPoint: function()
+    {
+        return [0.5, 0.5, 0];
+    },
+
+    /**
+     * @override
+     * @return {boolean}
+     */
+    invisible: function()
+    {
+        return false;
+    },
+
+    /**
+     * @override
+     * @return {number}
+     */
+    paintCount: function()
+    {
+        return 0;
+    },
+
+    /**
+     * @override
+     * @return {?DOMAgent.Rect}
+     */
+    lastPaintRect: function()
+    {
+        return null;
+    },
+
+    /**
+     * @override
+     * @return {!Array.<!LayerTreeAgent.ScrollRect>}
+     */
+    scrollRects: function()
+    {
+        return this._scrollRects;
+    },
+
+    /**
+     * @override
+     * @return {number}
+     */
+    gpuMemoryUsage: function()
+    {
+        return this._gpuMemoryUsage;
+    },
+
+    /**
+     * @param {!Array.<number>} params
+     * @param {string} type
+     * @return {!Object}
+     */
+    _scrollRectsFromParams: function(params, type)
+    {
+        return {rect: {x: params[0], y: params[1], width: params[2], height: params[3]}, type: type};
+    },
+
+    /**
+     * @param {!WebInspector.TracingLayerPayload} payload
+     */
+    _createScrollRects: function(payload)
+    {
+        this._scrollRects = [];
+        if (payload.non_fast_scrollable_region)
+            this._scrollRects.push(this._scrollRectsFromParams(payload.non_fast_scrollable_region, WebInspector.LayerTreeModel.ScrollRectType.NonFastScrollable.name));
+        if (payload.touch_event_handler_region)
+            this._scrollRects.push(this._scrollRectsFromParams(payload.touch_event_handler_region, WebInspector.LayerTreeModel.ScrollRectType.TouchEventHandler.name));
+        if (payload.wheel_event_handler_region)
+            this._scrollRects.push(this._scrollRectsFromParams(payload.wheel_event_handler_region, WebInspector.LayerTreeModel.ScrollRectType.WheelEventHandler.name));
+        if (payload.scroll_event_handler_region)
+            this._scrollRects.push(this._scrollRectsFromParams(payload.scroll_event_handler_region, WebInspector.LayerTreeModel.ScrollRectType.RepaintsOnScroll.name));
+    },
+
+    /**
+     * @override
+     * @param {function(!Array.<string>)} callback
+     */
+    requestCompositingReasons: function(callback)
+    {
+        callback(this._compositingReasons);
+    },
+
+    /**
+     * @override
+     * @return {boolean}
+     */
+    drawsContent: function()
+    {
+        return this._drawsContent;
+    }
+}
+
+/**
+ * @constructor
+ * @param {?WebInspector.Target} target
+ */
+WebInspector.DeferredLayerTree = function(target)
+{
+    this._target = target;
+}
+
+WebInspector.DeferredLayerTree.prototype = {
+    /**
+     * @param {function(!WebInspector.LayerTreeBase)} callback
+     */
+    resolve: function(callback) { },
+
+    /**
+     * @return {?WebInspector.Target}
+     */
+    target: function()
+    {
+        return this._target;
+    }
+};
+
+/**
+ * @constructor
+ * @implements {LayerTreeAgent.Dispatcher}
+ * @param {!WebInspector.LayerTreeModel} layerTreeModel
+ */
+WebInspector.LayerTreeDispatcher = function(layerTreeModel)
+{
+    this._layerTreeModel = layerTreeModel;
+}
+
+WebInspector.LayerTreeDispatcher.prototype = {
+    /**
+     * @override
+     * @param {!Array.<!LayerTreeAgent.Layer>=} layers
+     */
+    layerTreeDidChange: function(layers)
+    {
+        this._layerTreeModel._layerTreeChanged(layers || null);
+    },
+
+    /**
+     * @override
+     * @param {!LayerTreeAgent.LayerId} layerId
+     * @param {!DOMAgent.Rect} clipRect
+     */
+    layerPainted: function(layerId, clipRect)
+    {
+        this._layerTreeModel._layerPainted(layerId, clipRect);
+    }
+}
+
+/**
+ * @param {!WebInspector.Target} target
+ * @return {?WebInspector.LayerTreeModel}
+ */
+WebInspector.LayerTreeModel.fromTarget = function(target)
+{
+    if (!target.hasDOMCapability())
+        return null;
+
+    var model = /** @type {?WebInspector.LayerTreeModel} */ (target.model(WebInspector.LayerTreeModel));
+    if (!model)
+        model = new WebInspector.LayerTreeModel(target);
+    return model;
+}
+
+},{}],256:[function(require,module,exports){
+/*
+ * Copyright (C) 2013 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @constructor
+ * @param {function(!WebInspector.TracingModel.Event):string} categoryMapper
+ */
+WebInspector.TimelineFrameModel = function(categoryMapper)
+{
+    this._categoryMapper = categoryMapper;
+    this.reset();
+}
+
+WebInspector.TimelineFrameModel._mainFrameMarkers = [
+    WebInspector.TimelineModel.RecordType.ScheduleStyleRecalculation,
+    WebInspector.TimelineModel.RecordType.InvalidateLayout,
+    WebInspector.TimelineModel.RecordType.BeginMainThreadFrame,
+    WebInspector.TimelineModel.RecordType.ScrollLayer
+];
+
+WebInspector.TimelineFrameModel.prototype = {
+    /**
+     * @return {!Array.<!WebInspector.TimelineFrame>}
+     */
+    frames: function()
+    {
+        return this._frames;
+    },
+
+    /**
+     * @param {number} startTime
+     * @param {number} endTime
+     * @return {!Array.<!WebInspector.TimelineFrame>}
+     */
+    filteredFrames: function(startTime, endTime)
+    {
+        /**
+         * @param {number} value
+         * @param {!WebInspector.TimelineFrame} object
+         * @return {number}
+         */
+        function compareStartTime(value, object)
+        {
+            return value - object.startTime;
+        }
+        /**
+         * @param {number} value
+         * @param {!WebInspector.TimelineFrame} object
+         * @return {number}
+         */
+        function compareEndTime(value, object)
+        {
+            return value - object.endTime;
+        }
+        var frames = this._frames;
+        var firstFrame = frames.lowerBound(startTime, compareEndTime);
+        var lastFrame = frames.lowerBound(endTime, compareStartTime);
+        return frames.slice(firstFrame, lastFrame);
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} rasterTask
+     * @return {boolean}
+     */
+    hasRasterTile: function(rasterTask)
+    {
+        var data = rasterTask.args["tileData"];
+        if (!data)
+            return false;
+        var frameId = data["sourceFrameNumber"];
+        var frame = frameId && this._frameById[frameId];
+        if (!frame || !frame.layerTree)
+            return false;
+        return true;
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} rasterTask
+     * @param {function(?DOMAgent.Rect, ?WebInspector.PaintProfilerSnapshot)} callback
+     */
+    requestRasterTile: function(rasterTask, callback)
+    {
+        var target = this._target;
+        if (!target) {
+            callback(null, null);
+            return;
+        }
+        var data = rasterTask.args["tileData"];
+        var frameId = data["sourceFrameNumber"];
+        var frame = frameId && this._frameById[frameId];
+        if (!frame || !frame.layerTree) {
+            callback(null, null);
+            return;
+        }
+
+        var tileId = data["tileId"] && data["tileId"]["id_ref"];
+        /** @type {!Array.<!WebInspector.PictureFragment>}> */
+        var fragments = [];
+        /** @type {?WebInspector.TracingLayerTile} */
+        var tile = null;
+        var x0 = Infinity;
+        var y0 = Infinity;
+
+        frame.layerTree.resolve(layerTreeResolved);
+        /**
+         * @param {!WebInspector.LayerTreeBase} layerTree
+         */
+        function layerTreeResolved(layerTree)
+        {
+            tile = tileId && (/** @type {!WebInspector.TracingLayerTree} */ (layerTree)).tileById("cc::Tile/" + tileId);
+            if (!tile) {
+                console.error("Tile " + tileId + " missing in frame " + frameId);
+                callback(null, null);
+                return;
+            }
+            var fetchPictureFragmentsBarrier = new CallbackBarrier();
+            for (var paint of frame.paints) {
+                if (tile.layer_id === paint.layerId())
+                    paint.loadPicture(fetchPictureFragmentsBarrier.createCallback(pictureLoaded));
+            }
+            fetchPictureFragmentsBarrier.callWhenDone(allPicturesLoaded);
+        }
+
+        /**
+         * @param {number} a1
+         * @param {number} a2
+         * @param {number} b1
+         * @param {number} b2
+         * @return {boolean}
+         */
+        function segmentsOverlap(a1, a2, b1, b2)
+        {
+            console.assert(a1 <= a2 && b1 <= b2, "segments should be specified as ordered pairs");
+            return a2 > b1 && a1 < b2;
+        }
+        /**
+         * @param {!Array.<number>} a
+         * @param {!Array.<number>} b
+         * @return {boolean}
+         */
+        function rectsOverlap(a, b)
+        {
+            return segmentsOverlap(a[0], a[0] + a[2], b[0], b[0] + b[2]) && segmentsOverlap(a[1], a[1] + a[3], b[1], b[1] + b[3]);
+        }
+
+        /**
+         * @param {?Array.<number>} rect
+         * @param {?string} picture
+         */
+        function pictureLoaded(rect, picture)
+        {
+            if (!rect || !picture)
+                return;
+            if (!rectsOverlap(rect, tile.content_rect))
+                return;
+            var x = rect[0];
+            var y = rect[1];
+            x0 = Math.min(x0, x);
+            y0 = Math.min(y0, y);
+            fragments.push({x: x, y: y, picture: picture});
+        }
+
+        function allPicturesLoaded()
+        {
+            if (!fragments.length) {
+                callback(null, null);
+                return;
+            }
+            var rectArray = tile.content_rect;
+            // Rect is in layer content coordinates, make it relative to picture by offsetting to the top left corner.
+            var rect = {x: rectArray[0] - x0, y: rectArray[1] - y0, width: rectArray[2], height: rectArray[3]};
+            WebInspector.PaintProfilerSnapshot.loadFromFragments(target, fragments, callback.bind(null, rect));
+        }
+    },
+
+    reset: function()
+    {
+        this._minimumRecordTime = Infinity;
+        this._frames = [];
+        this._frameById = {};
+        this._lastFrame = null;
+        this._lastLayerTree = null;
+        this._mainFrameCommitted = false;
+        this._mainFrameRequested = false;
+        this._framePendingCommit = null;
+        this._lastBeginFrame = null;
+        this._lastNeedsBeginFrame = null;
+        this._framePendingActivation = null;
+        this._lastTaskBeginTime = null;
+        this._target = null;
+        this._sessionId = null;
+        this._currentTaskTimeByCategory = {};
+    },
+
+    /**
+     * @param {number} startTime
+     */
+    handleBeginFrame: function(startTime)
+    {
+        if (!this._lastFrame)
+            this._startFrame(startTime);
+        this._lastBeginFrame = startTime;
+    },
+
+    /**
+     * @param {number} startTime
+     */
+    handleDrawFrame: function(startTime)
+    {
+        if (!this._lastFrame) {
+            this._startFrame(startTime);
+            return;
+        }
+
+        // - if it wasn't drawn, it didn't happen!
+        // - only show frames that either did not wait for the main thread frame or had one committed.
+        if (this._mainFrameCommitted || !this._mainFrameRequested) {
+            if (this._lastNeedsBeginFrame) {
+                var idleTimeEnd = this._framePendingActivation ? this._framePendingActivation.triggerTime : (this._lastBeginFrame || this._lastNeedsBeginFrame);
+                if (idleTimeEnd > this._lastFrame.startTime) {
+                    this._lastFrame.idle = true;
+                    this._startFrame(idleTimeEnd);
+                    if (this._framePendingActivation)
+                        this._commitPendingFrame();
+                    this._lastBeginFrame = null;
+                }
+                this._lastNeedsBeginFrame = null;
+            }
+            this._startFrame(startTime);
+        }
+        this._mainFrameCommitted = false;
+    },
+
+    handleActivateLayerTree: function()
+    {
+        if (!this._lastFrame)
+            return;
+        if (this._framePendingActivation && !this._lastNeedsBeginFrame)
+            this._commitPendingFrame();
+    },
+
+    handleRequestMainThreadFrame: function()
+    {
+        if (!this._lastFrame)
+            return;
+        this._mainFrameRequested = true;
+    },
+
+    handleCompositeLayers: function()
+    {
+        if (!this._framePendingCommit)
+            return;
+        this._framePendingActivation = this._framePendingCommit;
+        this._framePendingCommit = null;
+        this._mainFrameRequested = false;
+        this._mainFrameCommitted = true;
+    },
+
+    /**
+     * @param {!WebInspector.DeferredLayerTree} layerTree
+     */
+    handleLayerTreeSnapshot: function(layerTree)
+    {
+        this._lastLayerTree = layerTree;
+    },
+
+    /**
+     * @param {number} startTime
+     * @param {boolean} needsBeginFrame
+     */
+    handleNeedFrameChanged: function(startTime, needsBeginFrame)
+    {
+        if (needsBeginFrame)
+            this._lastNeedsBeginFrame = startTime;
+    },
+
+    /**
+     * @param {number} startTime
+     */
+    _startFrame: function(startTime)
+    {
+        if (this._lastFrame)
+            this._flushFrame(this._lastFrame, startTime);
+        this._lastFrame = new WebInspector.TimelineFrame(startTime, startTime - this._minimumRecordTime);
+    },
+
+    /**
+     * @param {!WebInspector.TimelineFrame} frame
+     * @param {number} endTime
+     */
+    _flushFrame: function(frame, endTime)
+    {
+        frame._setLayerTree(this._lastLayerTree);
+        frame._setEndTime(endTime);
+        if (this._frames.length && (frame.startTime !== this._frames.peekLast().endTime || frame.startTime > frame.endTime))
+            console.assert(false, `Inconsistent frame time for frame ${this._frames.length} (${frame.startTime} - ${frame.endTime})`);
+        this._frames.push(frame);
+        if (typeof frame._mainFrameId === "number")
+            this._frameById[frame._mainFrameId] = frame;
+    },
+
+    _commitPendingFrame: function()
+    {
+        this._lastFrame._addTimeForCategories(this._framePendingActivation.timeByCategory);
+        this._lastFrame.paints = this._framePendingActivation.paints;
+        this._lastFrame._mainFrameId = this._framePendingActivation.mainFrameId;
+        this._framePendingActivation = null;
+    },
+
+    /**
+     * @param {!Array.<string>} types
+     * @param {!WebInspector.TimelineModel.Record} record
+     * @return {?WebInspector.TimelineModel.Record} record
+     */
+    _findRecordRecursively: function(types, record)
+    {
+        if (types.indexOf(record.type()) >= 0)
+            return record;
+        if (!record.children())
+            return null;
+        for (var i = 0; i < record.children().length; ++i) {
+            var result = this._findRecordRecursively(types, record.children()[i]);
+            if (result)
+                return result;
+        }
+        return null;
+    },
+
+    /**
+     * @param {?WebInspector.Target} target
+     * @param {!Array.<!WebInspector.TracingModel.Event>} events
+     * @param {string} sessionId
+     */
+    addTraceEvents: function(target, events, sessionId)
+    {
+        this._target = target;
+        this._sessionId = sessionId;
+        if (!events.length)
+            return;
+        if (events[0].startTime < this._minimumRecordTime)
+            this._minimumRecordTime = events[0].startTime;
+        for (var i = 0; i < events.length; ++i)
+            this._addTraceEvent(events[i]);
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} event
+     */
+    _addTraceEvent: function(event)
+    {
+        var eventNames = WebInspector.TimelineModel.RecordType;
+
+        if (event.name === eventNames.SetLayerTreeId) {
+            var sessionId = event.args["sessionId"] || event.args["data"]["sessionId"];
+            if (this._sessionId === sessionId)
+                this._layerTreeId = event.args["layerTreeId"] || event.args["data"]["layerTreeId"];
+        } else if (event.name === eventNames.TracingStartedInPage) {
+            this._mainThread = event.thread;
+        } else if (event.phase === WebInspector.TracingModel.Phase.SnapshotObject && event.name === eventNames.LayerTreeHostImplSnapshot && parseInt(event.id, 0) === this._layerTreeId) {
+            var snapshot = /** @type {!WebInspector.TracingModel.ObjectSnapshot} */ (event);
+            this.handleLayerTreeSnapshot(new WebInspector.DeferredTracingLayerTree(snapshot, this._target));
+        } else {
+            this._processCompositorEvents(event);
+            if (event.thread === this._mainThread)
+                this._addMainThreadTraceEvent(event);
+            else if (this._lastFrame && event.selfTime && !WebInspector.TracingModel.isTopLevelEvent(event))
+                this._lastFrame._addTimeForCategory(this._categoryMapper(event), event.selfTime);
+        }
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} event
+     */
+    _processCompositorEvents: function(event)
+    {
+        var eventNames = WebInspector.TimelineModel.RecordType;
+
+        if (event.args["layerTreeId"] !== this._layerTreeId)
+            return;
+
+        var timestamp = event.startTime;
+        if (event.name === eventNames.BeginFrame)
+            this.handleBeginFrame(timestamp);
+        else if (event.name === eventNames.DrawFrame)
+            this.handleDrawFrame(timestamp);
+        else if (event.name === eventNames.ActivateLayerTree)
+            this.handleActivateLayerTree();
+        else if (event.name === eventNames.RequestMainThreadFrame)
+            this.handleRequestMainThreadFrame();
+        else if (event.name === eventNames.NeedsBeginFrameChanged)
+            this.handleNeedFrameChanged(timestamp, event.args["data"] && event.args["data"]["needsBeginFrame"]);
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} event
+     */
+    _addMainThreadTraceEvent: function(event)
+    {
+        var eventNames = WebInspector.TimelineModel.RecordType;
+        var timestamp = event.startTime;
+        var selfTime = event.selfTime || 0;
+
+        if (WebInspector.TracingModel.isTopLevelEvent(event)) {
+            this._currentTaskTimeByCategory = {};
+            this._lastTaskBeginTime = event.startTime;
+        }
+        if (!this._framePendingCommit && WebInspector.TimelineFrameModel._mainFrameMarkers.indexOf(event.name) >= 0)
+            this._framePendingCommit = new WebInspector.PendingFrame(this._lastTaskBeginTime || event.startTime, this._currentTaskTimeByCategory);
+        if (!this._framePendingCommit) {
+            this._addTimeForCategory(this._currentTaskTimeByCategory, event);
+            return;
+        }
+        this._addTimeForCategory(this._framePendingCommit.timeByCategory, event);
+
+        if (event.name === eventNames.BeginMainThreadFrame && event.args["data"] && event.args["data"]["frameId"])
+            this._framePendingCommit.mainFrameId = event.args["data"]["frameId"];
+        if (event.name === eventNames.Paint && event.args["data"]["layerId"] && event.picture && this._target)
+            this._framePendingCommit.paints.push(new WebInspector.LayerPaintEvent(event, this._target));
+        if (event.name === eventNames.CompositeLayers && event.args["layerTreeId"] === this._layerTreeId)
+            this.handleCompositeLayers();
+    },
+
+    /**
+     * @param {!Object.<string, number>} timeByCategory
+     * @param {!WebInspector.TracingModel.Event} event
+     */
+    _addTimeForCategory: function(timeByCategory, event)
+    {
+        if (!event.selfTime)
+            return;
+        var categoryName = this._categoryMapper(event);
+        timeByCategory[categoryName] = (timeByCategory[categoryName] || 0) + event.selfTime;
+    },
+}
+
+/**
+ * @constructor
+ * @extends {WebInspector.DeferredLayerTree}
+ * @param {!WebInspector.TracingModel.ObjectSnapshot} snapshot
+ * @param {?WebInspector.Target} target
+ */
+WebInspector.DeferredTracingLayerTree = function(snapshot, target)
+{
+    WebInspector.DeferredLayerTree.call(this, target);
+    this._snapshot = snapshot;
+}
+
+WebInspector.DeferredTracingLayerTree.prototype = {
+    /**
+     * @override
+     * @param {function(!WebInspector.LayerTreeBase)} callback
+     */
+    resolve: function(callback)
+    {
+        this._snapshot.requestObject(onGotObject.bind(this));
+        /**
+         * @this {WebInspector.DeferredTracingLayerTree}
+         * @param {?Object} result
+         */
+        function onGotObject(result)
+        {
+            if (!result)
+                return;
+            var viewport = result["device_viewport_size"];
+            var tiles = result["active_tiles"];
+            var rootLayer = result["active_tree"]["root_layer"];
+            var layers = result["active_tree"]["layers"];
+            var layerTree = new WebInspector.TracingLayerTree(this._target);
+            layerTree.setViewportSize(viewport);
+            layerTree.setTiles(tiles);
+            layerTree.setLayers(rootLayer, layers, callback.bind(null, layerTree));
+        }
+    },
+
+    __proto__: WebInspector.DeferredLayerTree.prototype
+};
+
+
+/**
+ * @constructor
+ * @param {number} startTime
+ * @param {number} startTimeOffset
+ */
+WebInspector.TimelineFrame = function(startTime, startTimeOffset)
+{
+    this.startTime = startTime;
+    this.startTimeOffset = startTimeOffset;
+    this.endTime = this.startTime;
+    this.duration = 0;
+    this.timeByCategory = {};
+    this.cpuTime = 0;
+    this.idle = false;
+    /** @type {?WebInspector.DeferredLayerTree} */
+    this.layerTree = null;
+    /** @type {!Array.<!WebInspector.LayerPaintEvent>} */
+    this.paints = [];
+    /** @type {number|undefined} */
+    this._mainFrameId = undefined;
+}
+
+WebInspector.TimelineFrame.prototype = {
+    /**
+     * @return {boolean}
+     */
+    hasWarnings: function()
+    {
+        var /** @const */ longFrameDurationThresholdMs = 22;
+        return !this.idle && this.duration > longFrameDurationThresholdMs;
+    },
+
+    /**
+     * @param {number} endTime
+     */
+    _setEndTime: function(endTime)
+    {
+        this.endTime = endTime;
+        this.duration = this.endTime - this.startTime;
+    },
+
+    /**
+     * @param {?WebInspector.DeferredLayerTree} layerTree
+     */
+    _setLayerTree: function(layerTree)
+    {
+        this.layerTree = layerTree;
+    },
+
+    /**
+     * @param {!Object} timeByCategory
+     */
+    _addTimeForCategories: function(timeByCategory)
+    {
+        for (var category in timeByCategory)
+            this._addTimeForCategory(category, timeByCategory[category]);
+    },
+
+    /**
+     * @param {string} category
+     * @param {number} time
+     */
+    _addTimeForCategory: function(category, time)
+    {
+        this.timeByCategory[category] = (this.timeByCategory[category] || 0) + time;
+        this.cpuTime += time;
+    },
+}
+
+/**
+ * @constructor
+ * @param {!WebInspector.TracingModel.Event} event
+ * @param {?WebInspector.Target} target
+ */
+WebInspector.LayerPaintEvent = function(event, target)
+{
+    this._event = event;
+    this._target = target;
+}
+
+WebInspector.LayerPaintEvent.prototype = {
+    /**
+     * @return {string}
+     */
+    layerId: function()
+    {
+        return this._event.args["data"]["layerId"];
+    },
+
+    /**
+     * @return {!WebInspector.TracingModel.Event}
+     */
+    event: function()
+    {
+        return this._event;
+    },
+
+    /**
+     * @param {function(?Array.<number>, ?string)} callback
+     */
+    loadPicture: function(callback)
+    {
+        this._event.picture.requestObject(onGotObject);
+        /**
+         * @param {?Object} result
+         */
+        function onGotObject(result)
+        {
+            if (!result || !result["skp64"]) {
+                callback(null, null);
+                return;
+            }
+            var rect = result["params"] && result["params"]["layer_rect"];
+            callback(rect, result["skp64"]);
+        }
+    },
+
+    /**
+     * @param {function(?Array.<number>, ?WebInspector.PaintProfilerSnapshot)} callback
+     */
+    loadSnapshot: function(callback)
+    {
+        this.loadPicture(onGotPicture.bind(this));
+        /**
+         * @param {?Array.<number>} rect
+         * @param {?string} picture
+         * @this {WebInspector.LayerPaintEvent}
+         */
+        function onGotPicture(rect, picture)
+        {
+            if (!rect || !picture || !this._target) {
+                callback(null, null);
+                return;
+            }
+            WebInspector.PaintProfilerSnapshot.load(this._target, picture, callback.bind(null, rect));
+        }
+    }
+};
+
+/**
+ * @constructor
+ * @param {number} triggerTime
+ * @param {!Object.<string, number>} timeByCategory
+ */
+WebInspector.PendingFrame = function(triggerTime, timeByCategory)
+{
+    /** @type {!Object.<string, number>} */
+    this.timeByCategory = timeByCategory;
+    /** @type {!Array.<!WebInspector.LayerPaintEvent>} */
+    this.paints = [];
+    /** @type {number|undefined} */
+    this.mainFrameId = undefined;
+    this.triggerTime = triggerTime;
+}
 
 },{}],257:[function(require,module,exports){
-'use strict';
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
 
-exports.__esModule=true;
+/**
+ * @constructor
+ */
+WebInspector.TimelineIRModel = function()
+{
+    this.reset();
+}
 
-exports['default']=function(instance){
-instance.registerHelper('lookup',function(obj,field){
-return obj&&obj[field];
-});
+/**
+ * @enum {string}
+ */
+WebInspector.TimelineIRModel.Phases = {
+    Idle: "Idle",
+    Response: "Response",
+    Scroll: "Scroll",
+    Fling: "Fling",
+    Drag: "Drag",
+    Animation: "Animation",
+    Uncategorized: "Uncategorized"
 };
 
-module.exports=exports['default'];
+/**
+ * @enum {string}
+ */
+WebInspector.TimelineIRModel.InputEvents = {
+    Char: "Char",
+    Click: "GestureClick",
+    ContextMenu: "ContextMenu",
+    FlingCancel: "GestureFlingCancel",
+    FlingStart: "GestureFlingStart",
+    ImplSideFling: WebInspector.TimelineModel.RecordType.ImplSideFling,
+    KeyDown: "KeyDown",
+    KeyDownRaw: "RawKeyDown",
+    KeyUp: "KeyUp",
+    LatencyScrollUpdate: "ScrollUpdate",
+    MouseDown: "MouseDown",
+    MouseMove: "MouseMove",
+    MouseUp: "MouseUp",
+    MouseWheel: "MouseWheel",
+    PinchBegin: "GesturePinchBegin",
+    PinchEnd: "GesturePinchEnd",
+    PinchUpdate: "GesturePinchUpdate",
+    ScrollBegin: "GestureScrollBegin",
+    ScrollEnd: "GestureScrollEnd",
+    ScrollUpdate: "GestureScrollUpdate",
+    ScrollUpdateRenderer: "ScrollUpdate",
+    ShowPress: "GestureShowPress",
+    Tap: "GestureTap",
+    TapCancel: "GestureTapCancel",
+    TapDown: "GestureTapDown",
+    TouchCancel: "TouchCancel",
+    TouchEnd: "TouchEnd",
+    TouchMove: "TouchMove",
+    TouchStart: "TouchStart"
+};
+
+WebInspector.TimelineIRModel._mergeThresholdsMs = {
+    animation: 1,
+    mouse: 40,
+};
+
+WebInspector.TimelineIRModel._eventIRPhase = Symbol("eventIRPhase");
+
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @return {!WebInspector.TimelineIRModel.Phases}
+ */
+WebInspector.TimelineIRModel.phaseForEvent = function(event)
+{
+    return event[WebInspector.TimelineIRModel._eventIRPhase];
+}
+
+WebInspector.TimelineIRModel.prototype = {
+    /**
+     * @param {?Array<!WebInspector.TracingModel.AsyncEvent>} inputLatencies
+     * @param {?Array<!WebInspector.TracingModel.AsyncEvent>} animations
+     */
+    populate: function(inputLatencies, animations)
+    {
+        var eventTypes = WebInspector.TimelineIRModel.InputEvents;
+        var phases = WebInspector.TimelineIRModel.Phases;
+
+        this.reset();
+        if (!inputLatencies)
+            return;
+        this._processInputLatencies(inputLatencies);
+        if (animations)
+            this._processAnimations(animations);
+        var range = new WebInspector.SegmentedRange();
+        range.appendRange(this._drags); // Drags take lower precedence than animation, as we can't detect them reliably.
+        range.appendRange(this._cssAnimations);
+        range.appendRange(this._scrolls);
+        range.appendRange(this._responses);
+        this._segments = range.segments();
+    },
+
+    /**
+     * @param {!Array<!WebInspector.TracingModel.AsyncEvent>} events
+     */
+    _processInputLatencies: function(events)
+    {
+        var eventTypes = WebInspector.TimelineIRModel.InputEvents;
+        var phases = WebInspector.TimelineIRModel.Phases;
+        var thresholdsMs = WebInspector.TimelineIRModel._mergeThresholdsMs;
+
+        var scrollStart;
+        var flingStart;
+        var touchStart;
+        var firstTouchMove;
+        var mouseWheel;
+        var mouseDown;
+        var mouseMove;
+
+        for (var i = 0; i < events.length; ++i) {
+            var event = events[i];
+            if (i > 0 && events[i].startTime < events[i - 1].startTime)
+                console.assert(false, "Unordered input events");
+            var type = this._inputEventType(event.name);
+            switch (type) {
+
+            case eventTypes.ScrollBegin:
+                this._scrolls.append(this._segmentForEvent(event, phases.Scroll));
+                scrollStart = event;
+                break;
+
+            case eventTypes.ScrollEnd:
+                if (scrollStart)
+                    this._scrolls.append(this._segmentForEventRange(scrollStart, event, phases.Scroll));
+                else
+                    this._scrolls.append(this._segmentForEvent(event, phases.Scroll));
+                scrollStart = null;
+                break;
+
+            case eventTypes.ScrollUpdate:
+                touchStart = null; // Since we're scrolling now, disregard other touch gestures.
+                this._scrolls.append(this._segmentForEvent(event, phases.Scroll));
+                break;
+
+            case eventTypes.FlingStart:
+                if (flingStart) {
+                    WebInspector.console.error(WebInspector.UIString("Two flings at the same time? %s vs %s", flingStart.startTime, event.startTime));
+                    break;
+                }
+                flingStart = event;
+                break;
+
+            case eventTypes.FlingCancel:
+                // FIXME: also process renderer fling events.
+                if (!flingStart)
+                    break;
+                this._scrolls.append(this._segmentForEventRange(flingStart, event, phases.Fling));
+                flingStart = null;
+                break;
+
+            case eventTypes.ImplSideFling:
+                this._scrolls.append(this._segmentForEvent(event, phases.Fling));
+                break;
+
+            case eventTypes.ShowPress:
+            case eventTypes.Tap:
+            case eventTypes.KeyDown:
+            case eventTypes.KeyDownRaw:
+            case eventTypes.KeyUp:
+            case eventTypes.Char:
+            case eventTypes.Click:
+            case eventTypes.ContextMenu:
+                this._responses.append(this._segmentForEvent(event, phases.Response));
+                break;
+
+            case eventTypes.TouchStart:
+                // We do not produce any response segment for TouchStart -- there's either going to be one upon
+                // TouchMove for drag, or one for GestureTap.
+                if (touchStart) {
+                    WebInspector.console.error(WebInspector.UIString("Two touches at the same time? %s vs %s", touchStart.startTime, event.startTime));
+                    break;
+                }
+                touchStart = event;
+                event.steps[0][WebInspector.TimelineIRModel._eventIRPhase] = phases.Response;
+                firstTouchMove = null;
+                break;
+
+            case eventTypes.TouchCancel:
+                touchStart = null;
+                break;
+
+            case eventTypes.TouchMove:
+                if (firstTouchMove) {
+                    this._drags.append(this._segmentForEvent(event, phases.Drag));
+                } else if (touchStart) {
+                    firstTouchMove = event;
+                    this._responses.append(this._segmentForEventRange(touchStart, event, phases.Response));
+                }
+                break;
+
+            case eventTypes.TouchEnd:
+                touchStart = null;
+                break;
+
+            case eventTypes.MouseDown:
+                mouseDown = event;
+                mouseMove = null;
+                break;
+
+            case eventTypes.MouseMove:
+                if (mouseDown && !mouseMove && mouseDown.startTime + thresholdsMs.mouse > event.startTime) {
+                    this._responses.append(this._segmentForEvent(mouseDown, phases.Response));
+                    this._responses.append(this._segmentForEvent(event, phases.Response));
+                } else if (mouseDown) {
+                    this._drags.append(this._segmentForEvent(event, phases.Drag));
+                }
+                mouseMove = event;
+                break;
+
+            case eventTypes.MouseUp:
+                this._responses.append(this._segmentForEvent(event, phases.Response));
+                mouseDown = null;
+                break;
+
+            case eventTypes.MouseWheel:
+                // Do not consider first MouseWheel as trace viewer's implementation does -- in case of MouseWheel it's not really special.
+                if (mouseWheel && canMerge(thresholdsMs.mouse, mouseWheel, event))
+                    this._scrolls.append(this._segmentForEventRange(mouseWheel, event, phases.Scroll));
+                else
+                    this._scrolls.append(this._segmentForEvent(event, phases.Scroll));
+                mouseWheel = event;
+                break;
+            }
+        }
+
+        /**
+         * @param {number} threshold
+         * @param {!WebInspector.TracingModel.AsyncEvent} first
+         * @param {!WebInspector.TracingModel.AsyncEvent} second
+         * @return {boolean}
+         */
+        function canMerge(threshold, first, second)
+        {
+            return first.endTime < second.startTime && second.startTime < first.endTime + threshold;
+        }
+    },
+
+    /**
+     * @param {!Array<!WebInspector.TracingModel.AsyncEvent>} events
+     */
+    _processAnimations: function(events)
+    {
+        for (var i = 0; i < events.length; ++i)
+            this._cssAnimations.append(this._segmentForEvent(events[i], WebInspector.TimelineIRModel.Phases.Animation));
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.AsyncEvent} event
+     * @param {!WebInspector.TimelineIRModel.Phases} phase
+     * @return {!WebInspector.Segment}
+     */
+    _segmentForEvent: function(event, phase)
+    {
+        this._setPhaseForEvent(event, phase);
+        return new WebInspector.Segment(event.startTime, event.endTime, phase);
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.AsyncEvent} startEvent
+     * @param {!WebInspector.TracingModel.AsyncEvent} endEvent
+     * @param {!WebInspector.TimelineIRModel.Phases} phase
+     * @return {!WebInspector.Segment}
+     */
+    _segmentForEventRange: function(startEvent, endEvent, phase)
+    {
+        this._setPhaseForEvent(startEvent, phase);
+        this._setPhaseForEvent(endEvent, phase);
+        return new WebInspector.Segment(startEvent.startTime, endEvent.endTime, phase);
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.AsyncEvent} asyncEvent
+     * @param {!WebInspector.TimelineIRModel.Phases} phase
+     */
+    _setPhaseForEvent: function(asyncEvent, phase)
+    {
+        asyncEvent.steps[0][WebInspector.TimelineIRModel._eventIRPhase] = phase;
+    },
+
+    /**
+     * @return {!Array<!WebInspector.Segment>}
+     */
+    interactionRecords: function()
+    {
+        return this._segments;
+    },
+
+    reset: function()
+    {
+        var thresholdsMs = WebInspector.TimelineIRModel._mergeThresholdsMs;
+
+        this._segments = [];
+        this._drags = new WebInspector.SegmentedRange(merge.bind(null, thresholdsMs.mouse));
+        this._cssAnimations = new WebInspector.SegmentedRange(merge.bind(null, thresholdsMs.animation));
+        this._responses = new WebInspector.SegmentedRange(merge.bind(null, 0));
+        this._scrolls = new WebInspector.SegmentedRange(merge.bind(null, thresholdsMs.animation));
+
+        /**
+         * @param {number} threshold
+         * @param {!WebInspector.Segment} first
+         * @param {!WebInspector.Segment} second
+         */
+        function merge(threshold, first, second)
+        {
+            return first.end + threshold >= second.begin && first.data === second.data ? first : null;
+        }
+    },
+
+    /**
+     * @param {string} eventName
+     * @return {?WebInspector.TimelineIRModel.InputEvents}
+     */
+    _inputEventType: function(eventName)
+    {
+        var prefix = "InputLatency::";
+        if (!eventName.startsWith(prefix)) {
+            if (eventName === WebInspector.TimelineIRModel.InputEvents.ImplSideFling)
+                return /** @type {!WebInspector.TimelineIRModel.InputEvents} */ (eventName);
+            console.error("Unrecognized input latency event: " + eventName);
+            return null;
+        }
+        return /** @type {!WebInspector.TimelineIRModel.InputEvents} */ (eventName.substr(prefix.length));
+    }
+};
 
 
 },{}],258:[function(require,module,exports){
-'use strict';
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
 
-exports.__esModule=true;
 
-var _utils=require('../utils');
+WebInspector.TimelineJSProfileProcessor = { };
 
-exports['default']=function(instance){
-instance.registerHelper('with',function(context,options){
-if(_utils.isFunction(context)){
-context=context.call(this);
+/**
+ * @param {!WebInspector.CPUProfileDataModel} jsProfileModel
+ * @param {!WebInspector.TracingModel.Thread} thread
+ * @return {!Array<!WebInspector.TracingModel.Event>}
+ */
+WebInspector.TimelineJSProfileProcessor.generateTracingEventsFromCpuProfile = function(jsProfileModel, thread)
+{
+    var idleNode = jsProfileModel.idleNode;
+    var programNode = jsProfileModel.programNode;
+    var gcNode = jsProfileModel.gcNode;
+    var samples = jsProfileModel.samples;
+    var timestamps = jsProfileModel.timestamps;
+    var jsEvents = [];
+    /** @type {!Map<!Object, !Array<!RuntimeAgent.CallFrame>>} */
+    var nodeToStackMap = new Map();
+    nodeToStackMap.set(programNode, []);
+    for (var i = 0; i < samples.length; ++i) {
+        var node = jsProfileModel.nodeByIndex(i);
+        if (!node) {
+            console.error(`Node with unknown id ${samples[i]} at index ${i}`);
+            continue;
+        }
+        if (node === gcNode || node === idleNode)
+            continue;
+        var callFrames = nodeToStackMap.get(node);
+        if (!callFrames) {
+            callFrames = /** @type {!Array<!RuntimeAgent.CallFrame>} */ (new Array(node.depth + 1));
+            nodeToStackMap.set(node, callFrames);
+            for (var j = 0; node.parent; node = node.parent)
+                callFrames[j++] = /** @type {!RuntimeAgent.CallFrame} */ (node);
+        }
+        var jsSampleEvent = new WebInspector.TracingModel.Event(WebInspector.TracingModel.DevToolsTimelineEventCategory,
+            WebInspector.TimelineModel.RecordType.JSSample,
+            WebInspector.TracingModel.Phase.Instant, timestamps[i], thread);
+        jsSampleEvent.args["data"] = { stackTrace: callFrames };
+        jsEvents.push(jsSampleEvent);
+    }
+    return jsEvents;
 }
 
-var fn=options.fn;
+/**
+ * @param {!Array<!WebInspector.TracingModel.Event>} events
+ * @return {!Array<!WebInspector.TracingModel.Event>}
+ */
+WebInspector.TimelineJSProfileProcessor.generateJSFrameEvents = function(events)
+{
+    /**
+     * @param {!RuntimeAgent.CallFrame} frame1
+     * @param {!RuntimeAgent.CallFrame} frame2
+     * @return {boolean}
+     */
+    function equalFrames(frame1, frame2)
+    {
+        return frame1.scriptId === frame2.scriptId && frame1.functionName === frame2.functionName;
+    }
 
-if(!_utils.isEmpty(context)){
-var data=options.data;
-if(options.data&&options.ids){
-data=_utils.createFrame(options.data);
-data.contextPath=_utils.appendContextPath(options.data.contextPath,options.ids[0]);
+    /**
+     * @param {!WebInspector.TracingModel.Event} e
+     * @return {number}
+     */
+    function eventEndTime(e)
+    {
+        return e.endTime || e.startTime;
+    }
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} e
+     * @return {boolean}
+     */
+    function isJSInvocationEvent(e)
+    {
+        switch (e.name) {
+        case WebInspector.TimelineModel.RecordType.RunMicrotasks:
+        case WebInspector.TimelineModel.RecordType.FunctionCall:
+        case WebInspector.TimelineModel.RecordType.EvaluateScript:
+            return true;
+        }
+        return false;
+    }
+
+    var jsFrameEvents = [];
+    var jsFramesStack = [];
+    var lockedJsStackDepth = [];
+    var ordinal = 0;
+    var filterNativeFunctions = !WebInspector.moduleSetting("showNativeFunctionsInJSProfile").get();
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} e
+     */
+    function onStartEvent(e)
+    {
+        e.ordinal = ++ordinal;
+        extractStackTrace(e);
+        // For the duration of the event we cannot go beyond the stack associated with it.
+        lockedJsStackDepth.push(jsFramesStack.length);
+    }
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} e
+     * @param {?WebInspector.TracingModel.Event} parent
+     */
+    function onInstantEvent(e, parent)
+    {
+        e.ordinal = ++ordinal;
+        if (parent && isJSInvocationEvent(parent))
+            extractStackTrace(e);
+    }
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} e
+     */
+    function onEndEvent(e)
+    {
+        truncateJSStack(lockedJsStackDepth.pop(), e.endTime);
+    }
+
+    /**
+     * @param {number} depth
+     * @param {number} time
+     */
+    function truncateJSStack(depth, time)
+    {
+        if (lockedJsStackDepth.length) {
+            var lockedDepth = lockedJsStackDepth.peekLast();
+            if (depth < lockedDepth) {
+                console.error("Child stack is shallower (" + depth + ") than the parent stack (" + lockedDepth + ") at " + time);
+                depth = lockedDepth;
+            }
+        }
+        if (jsFramesStack.length < depth) {
+            console.error("Trying to truncate higher than the current stack size at " + time);
+            depth = jsFramesStack.length;
+        }
+        for (var k = 0; k < jsFramesStack.length; ++k)
+            jsFramesStack[k].setEndTime(time);
+        jsFramesStack.length = depth;
+    }
+
+    /**
+     * @param {!Array<!RuntimeAgent.CallFrame>} stack
+     */
+    function filterStackFrames(stack)
+    {
+        for (var i = 0, j = 0; i < stack.length; ++i) {
+            var url = stack[i].url;
+            if (url && url.startsWith("native "))
+                continue;
+            stack[j++] = stack[i];
+        }
+        stack.length = j;
+    }
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} e
+     */
+    function extractStackTrace(e)
+    {
+        var recordTypes = WebInspector.TimelineModel.RecordType;
+        var callFrames;
+        if (e.name === recordTypes.JSSample) {
+            var eventData = e.args["data"] || e.args["beginData"];
+            callFrames = /** @type {!Array<!RuntimeAgent.CallFrame>} */ (eventData && eventData["stackTrace"]);
+        } else {
+            callFrames = /** @type {!Array<!RuntimeAgent.CallFrame>} */ (jsFramesStack.map(frameEvent => frameEvent.args["data"]).reverse());
+        }
+        if (filterNativeFunctions)
+            filterStackFrames(callFrames);
+        var endTime = eventEndTime(e);
+        var numFrames = callFrames.length;
+        var minFrames = Math.min(numFrames, jsFramesStack.length);
+        var i;
+        for (i = lockedJsStackDepth.peekLast() || 0; i < minFrames; ++i) {
+            var newFrame = callFrames[numFrames - 1 - i];
+            var oldFrame = jsFramesStack[i].args["data"];
+            if (!equalFrames(newFrame, oldFrame))
+                break;
+            jsFramesStack[i].setEndTime(Math.max(jsFramesStack[i].endTime, endTime));
+        }
+        truncateJSStack(i, e.startTime);
+        for (; i < numFrames; ++i) {
+            var frame = callFrames[numFrames - 1 - i];
+            var jsFrameEvent = new WebInspector.TracingModel.Event(WebInspector.TracingModel.DevToolsTimelineEventCategory, recordTypes.JSFrame,
+                WebInspector.TracingModel.Phase.Complete, e.startTime, e.thread);
+            jsFrameEvent.ordinal = e.ordinal;
+            jsFrameEvent.addArgs({ data: frame });
+            jsFrameEvent.setEndTime(endTime);
+            jsFramesStack.push(jsFrameEvent);
+            jsFrameEvents.push(jsFrameEvent);
+        }
+    }
+
+    /**
+     * @param {!Array<!WebInspector.TracingModel.Event>} events
+     * @return {?WebInspector.TracingModel.Event}
+     */
+    function findFirstTopLevelEvent(events)
+    {
+        for (var i = 0; i < events.length; ++i) {
+            if (WebInspector.TracingModel.isTopLevelEvent(events[i]))
+                return events[i];
+        }
+        return null;
+    }
+
+    var firstTopLevelEvent = findFirstTopLevelEvent(events);
+    if (firstTopLevelEvent)
+        WebInspector.TimelineModel.forEachEvent(events, onStartEvent, onEndEvent, onInstantEvent, firstTopLevelEvent.startTime);
+    return jsFrameEvents;
 }
 
-return fn(context,{
-data:data,
-blockParams:_utils.blockParams([context],[data&&data.contextPath])});
-
-}else{
-return options.inverse(this);
+/**
+ * @constructor
+ */
+WebInspector.TimelineJSProfileProcessor.CodeMap = function()
+{
+    /** @type {!Map<string, !WebInspector.TimelineJSProfileProcessor.CodeMap.Bank>} */
+    this._banks = new Map();
 }
-});
+
+/**
+ * @constructor
+ * @param {number} address
+ * @param {number} size
+ * @param {!RuntimeAgent.CallFrame} callFrame
+ */
+WebInspector.TimelineJSProfileProcessor.CodeMap.Entry = function(address, size, callFrame)
+{
+    this.address = address;
+    this.size = size;
+    this.callFrame = callFrame;
+}
+
+/**
+ * @param {number} address
+ * @param {!WebInspector.TimelineJSProfileProcessor.CodeMap.Entry} entry
+ * @return {number}
+ */
+WebInspector.TimelineJSProfileProcessor.CodeMap.comparator = function(address, entry)
+{
+    return address - entry.address;
+}
+
+WebInspector.TimelineJSProfileProcessor.CodeMap.prototype = {
+    /**
+     * @param {string} addressHex
+     * @param {number} size
+     * @param {!RuntimeAgent.CallFrame} callFrame
+     */
+    addEntry: function(addressHex, size, callFrame)
+    {
+        var entry = new WebInspector.TimelineJSProfileProcessor.CodeMap.Entry(this._getAddress(addressHex), size, callFrame);
+        this._addEntry(addressHex, entry);
+    },
+
+    /**
+     * @param {string} oldAddressHex
+     * @param {string} newAddressHex
+     * @param {number} size
+     */
+    moveEntry: function(oldAddressHex, newAddressHex, size)
+    {
+        var entry = this._getBank(oldAddressHex).removeEntry(this._getAddress(oldAddressHex));
+        if (!entry) {
+            console.error("Entry at address " + oldAddressHex + " not found");
+            return;
+        }
+        entry.address = this._getAddress(newAddressHex);
+        entry.size = size;
+        this._addEntry(newAddressHex, entry);
+    },
+
+    /**
+     * @param {string} addressHex
+     * @return {?RuntimeAgent.CallFrame}
+     */
+    lookupEntry: function(addressHex)
+    {
+        return this._getBank(addressHex).lookupEntry(this._getAddress(addressHex));
+    },
+
+    /**
+     * @param {string} addressHex
+     * @param {!WebInspector.TimelineJSProfileProcessor.CodeMap.Entry} entry
+     */
+    _addEntry: function(addressHex, entry)
+    {
+        // FIXME: deal with entries that span across [multiple] banks.
+        this._getBank(addressHex).addEntry(entry);
+    },
+
+    /**
+     * @param {string} addressHex
+     * @return {!WebInspector.TimelineJSProfileProcessor.CodeMap.Bank}
+     */
+    _getBank: function(addressHex)
+    {
+        addressHex = addressHex.slice(2);  // cut 0x prefix.
+        // 13 hex digits == 52 bits, double mantissa fits 53 bits.
+        var /** @const */ bankSizeHexDigits = 13;
+        var /** @const */ maxHexDigits = 16;
+        var bankName = addressHex.slice(-maxHexDigits, -bankSizeHexDigits);
+        var bank = this._banks.get(bankName);
+        if (!bank) {
+            bank = new WebInspector.TimelineJSProfileProcessor.CodeMap.Bank();
+            this._banks.set(bankName, bank);
+        }
+        return bank;
+    },
+
+    /**
+     * @param {string} addressHex
+     * @return {number}
+     */
+    _getAddress: function(addressHex)
+    {
+        // 13 hex digits == 52 bits, double mantissa fits 53 bits.
+        var /** @const */ bankSizeHexDigits = 13;
+        addressHex = addressHex.slice(2);  // cut 0x prefix.
+        return parseInt(addressHex.slice(-bankSizeHexDigits), 16);
+    }
+}
+
+/**
+ * @constructor
+ */
+WebInspector.TimelineJSProfileProcessor.CodeMap.Bank = function()
+{
+    /** @type {!Array<!WebInspector.TimelineJSProfileProcessor.CodeMap.Entry>} */
+    this._entries = [];
+}
+
+WebInspector.TimelineJSProfileProcessor.CodeMap.Bank.prototype = {
+    /**
+     * @param {number} address
+     * @return {?WebInspector.TimelineJSProfileProcessor.CodeMap.Entry}
+     */
+    removeEntry: function(address)
+    {
+        var index = this._entries.lowerBound(address, WebInspector.TimelineJSProfileProcessor.CodeMap.comparator);
+        var entry = this._entries[index];
+        if (!entry || entry.address !== address)
+            return null;
+        this._entries.splice(index, 1);
+        return entry;
+    },
+
+    /**
+     * @param {number} address
+     * @return {?RuntimeAgent.CallFrame}
+     */
+    lookupEntry: function(address)
+    {
+        var index = this._entries.upperBound(address, WebInspector.TimelineJSProfileProcessor.CodeMap.comparator) - 1;
+        var entry = this._entries[index];
+        return entry && address < entry.address + entry.size ? entry.callFrame : null;
+    },
+
+    /**
+     * @param {!WebInspector.TimelineJSProfileProcessor.CodeMap.Entry} newEntry
+     */
+    addEntry: function(newEntry)
+    {
+        var endAddress = newEntry.address + newEntry.size;
+        var lastIndex = this._entries.lowerBound(endAddress, WebInspector.TimelineJSProfileProcessor.CodeMap.comparator);
+        var index;
+        for (index = lastIndex - 1; index >= 0; --index) {
+            var entry = this._entries[index];
+            var entryEndAddress = entry.address + entry.size;
+            if (entryEndAddress <= newEntry.address)
+                break;
+        }
+        ++index;
+        this._entries.splice(index, lastIndex - index, newEntry);
+    }
+}
+
+/**
+ * @param {string} name
+ * @param {number} scriptId
+ * @return {!RuntimeAgent.CallFrame}
+ */
+WebInspector.TimelineJSProfileProcessor._buildCallFrame = function(name, scriptId)
+{
+    /**
+     * @param {string} functionName
+     * @param {string=} url
+     * @param {string=} scriptId
+     * @param {number=} line
+     * @param {number=} column
+     * @param {boolean=} isNative
+     * @return {!RuntimeAgent.CallFrame}
+     */
+    function createFrame(functionName, url, scriptId, line, column, isNative)
+    {
+        return /** @type {!RuntimeAgent.CallFrame} */ ({
+            "functionName": functionName,
+            "url": url || "",
+            "scriptId": scriptId || "0",
+            "lineNumber": line || 0,
+            "columnNumber": column || 0,
+            "isNative": isNative || false
+        });
+    }
+
+    // Code states:
+    // (empty) -> compiled
+    //    ~    -> optimizable
+    //    *    -> optimized
+    var rePrefix = /^(\w*:)?[*~]?(.*)$/m;
+    var tokens = rePrefix.exec(name);
+    var prefix = tokens[1];
+    var body = tokens[2];
+    var rawName;
+    var rawUrl;
+    if (prefix === "Script:") {
+        rawName = "";
+        rawUrl = body;
+    } else {
+        var spacePos = body.lastIndexOf(" ");
+        rawName = spacePos !== -1 ? body.substr(0, spacePos) : body;
+        rawUrl = spacePos !== -1 ? body.substr(spacePos + 1) : "";
+    }
+    var nativeSuffix = " native";
+    var isNative = rawName.endsWith(nativeSuffix);
+    var functionName = isNative ? rawName.slice(0, -nativeSuffix.length) : rawName;
+    var urlData = WebInspector.ParsedURL.splitLineAndColumn(rawUrl);
+    var url = urlData.url || "";
+    var line = urlData.lineNumber || 0;
+    var column = urlData.columnNumber || 0;
+    return createFrame(functionName, url, String(scriptId), line, column, isNative);
+}
+
+/**
+ * @param {!Array<!WebInspector.TracingModel.Event>} events
+ * @return {!Array<!WebInspector.TracingModel.Event>}
+ */
+WebInspector.TimelineJSProfileProcessor.processRawV8Samples = function(events)
+{
+    var missingAddesses = new Set();
+
+    /**
+     * @param {string} address
+     * @return {?RuntimeAgent.CallFrame}
+     */
+    function convertRawFrame(address)
+    {
+        var entry = codeMap.lookupEntry(address);
+        if (entry)
+            return entry.isNative ? null : entry;
+        if (!missingAddesses.has(address)) {
+            missingAddesses.add(address);
+            console.error("Address " + address + " has missing code entry");
+        }
+        return null;
+    }
+
+    var recordTypes = WebInspector.TimelineModel.RecordType;
+    var samples = [];
+    var codeMap = new WebInspector.TimelineJSProfileProcessor.CodeMap();
+    for (var i = 0; i < events.length; ++i) {
+        var e = events[i];
+        var data = e.args["data"];
+        switch (e.name) {
+        case recordTypes.JitCodeAdded:
+            var frame = WebInspector.TimelineJSProfileProcessor._buildCallFrame(data["name"], data["script_id"]);
+            codeMap.addEntry(data["code_start"], data["code_len"], frame);
+            break;
+        case recordTypes.JitCodeMoved:
+            codeMap.moveEntry(data["code_start"], data["new_code_start"], data["code_len"]);
+            break;
+        case recordTypes.V8Sample:
+            var rawStack = data["stack"];
+            // Sometimes backend fails to collect a stack and returns an empty stack.
+            // Skip these bogus samples.
+            if (data["vm_state"] === "js" && !rawStack.length)
+                break;
+            var stack = rawStack.map(convertRawFrame);
+            stack.remove(null);
+            var sampleEvent = new WebInspector.TracingModel.Event(
+                WebInspector.TracingModel.DevToolsTimelineEventCategory,
+                WebInspector.TimelineModel.RecordType.JSSample,
+                WebInspector.TracingModel.Phase.Instant, e.startTime, e.thread);
+            sampleEvent.ordinal = e.ordinal;
+            sampleEvent.args = {"data": {"stackTrace": stack }};
+            samples.push(sampleEvent);
+            break;
+        }
+    }
+
+    return samples;
+}
+
+},{}],259:[function(require,module,exports){
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @constructor
+ * @param {!WebInspector.TimelineModel.Filter} eventFilter
+ */
+WebInspector.TimelineModel = function(eventFilter)
+{
+    this._eventFilter = eventFilter;
+    this.reset();
+}
+
+/**
+ * @enum {string}
+ */
+WebInspector.TimelineModel.RecordType = {
+    Task: "Task",
+    Program: "Program",
+    EventDispatch: "EventDispatch",
+
+    GPUTask: "GPUTask",
+
+    Animation: "Animation",
+    RequestMainThreadFrame: "RequestMainThreadFrame",
+    BeginFrame: "BeginFrame",
+    NeedsBeginFrameChanged: "NeedsBeginFrameChanged",
+    BeginMainThreadFrame: "BeginMainThreadFrame",
+    ActivateLayerTree: "ActivateLayerTree",
+    DrawFrame: "DrawFrame",
+    HitTest: "HitTest",
+    ScheduleStyleRecalculation: "ScheduleStyleRecalculation",
+    RecalculateStyles: "RecalculateStyles", // For backwards compatibility only, now replaced by UpdateLayoutTree.
+    UpdateLayoutTree: "UpdateLayoutTree",
+    InvalidateLayout: "InvalidateLayout",
+    Layout: "Layout",
+    UpdateLayer: "UpdateLayer",
+    UpdateLayerTree: "UpdateLayerTree",
+    PaintSetup: "PaintSetup",
+    Paint: "Paint",
+    PaintImage: "PaintImage",
+    Rasterize: "Rasterize",
+    RasterTask: "RasterTask",
+    ScrollLayer: "ScrollLayer",
+    CompositeLayers: "CompositeLayers",
+
+    ScheduleStyleInvalidationTracking: "ScheduleStyleInvalidationTracking",
+    StyleRecalcInvalidationTracking: "StyleRecalcInvalidationTracking",
+    StyleInvalidatorInvalidationTracking: "StyleInvalidatorInvalidationTracking",
+    LayoutInvalidationTracking: "LayoutInvalidationTracking",
+    LayerInvalidationTracking: "LayerInvalidationTracking",
+    PaintInvalidationTracking: "PaintInvalidationTracking",
+    ScrollInvalidationTracking: "ScrollInvalidationTracking",
+
+    ParseHTML: "ParseHTML",
+    ParseAuthorStyleSheet: "ParseAuthorStyleSheet",
+
+    TimerInstall: "TimerInstall",
+    TimerRemove: "TimerRemove",
+    TimerFire: "TimerFire",
+
+    XHRReadyStateChange: "XHRReadyStateChange",
+    XHRLoad: "XHRLoad",
+    CompileScript: "v8.compile",
+    EvaluateScript: "EvaluateScript",
+
+    CommitLoad: "CommitLoad",
+    MarkLoad: "MarkLoad",
+    MarkDOMContent: "MarkDOMContent",
+    MarkFirstPaint: "MarkFirstPaint",
+
+    TimeStamp: "TimeStamp",
+    ConsoleTime: "ConsoleTime",
+    UserTiming: "UserTiming",
+
+    ResourceSendRequest: "ResourceSendRequest",
+    ResourceReceiveResponse: "ResourceReceiveResponse",
+    ResourceReceivedData: "ResourceReceivedData",
+    ResourceFinish: "ResourceFinish",
+
+    RunMicrotasks: "RunMicrotasks",
+    FunctionCall: "FunctionCall",
+    GCEvent: "GCEvent", // For backwards compatibility only, now replaced by MinorGC/MajorGC.
+    MajorGC: "MajorGC",
+    MinorGC: "MinorGC",
+    JSFrame: "JSFrame",
+    JSSample: "JSSample",
+    // V8Sample events are coming from tracing and contain raw stacks with function addresses.
+    // After being processed with help of JitCodeAdded and JitCodeMoved events they
+    // get translated into function infos and stored as stacks in JSSample events.
+    V8Sample: "V8Sample",
+    JitCodeAdded: "JitCodeAdded",
+    JitCodeMoved: "JitCodeMoved",
+    ParseScriptOnBackground: "v8.parseOnBackground",
+
+    UpdateCounters: "UpdateCounters",
+
+    RequestAnimationFrame: "RequestAnimationFrame",
+    CancelAnimationFrame: "CancelAnimationFrame",
+    FireAnimationFrame: "FireAnimationFrame",
+
+    RequestIdleCallback: "RequestIdleCallback",
+    CancelIdleCallback: "CancelIdleCallback",
+    FireIdleCallback: "FireIdleCallback",
+
+    WebSocketCreate : "WebSocketCreate",
+    WebSocketSendHandshakeRequest : "WebSocketSendHandshakeRequest",
+    WebSocketReceiveHandshakeResponse : "WebSocketReceiveHandshakeResponse",
+    WebSocketDestroy : "WebSocketDestroy",
+
+    EmbedderCallback : "EmbedderCallback",
+
+    SetLayerTreeId: "SetLayerTreeId",
+    TracingStartedInPage: "TracingStartedInPage",
+    TracingSessionIdForWorker: "TracingSessionIdForWorker",
+
+    DecodeImage: "Decode Image",
+    ResizeImage: "Resize Image",
+    DrawLazyPixelRef: "Draw LazyPixelRef",
+    DecodeLazyPixelRef: "Decode LazyPixelRef",
+
+    LazyPixelRef: "LazyPixelRef",
+    LayerTreeHostImplSnapshot: "cc::LayerTreeHostImpl",
+    PictureSnapshot: "cc::Picture",
+    DisplayItemListSnapshot: "cc::DisplayItemList",
+    LatencyInfo: "LatencyInfo",
+    LatencyInfoFlow: "LatencyInfo.Flow",
+    InputLatencyMouseMove: "InputLatency::MouseMove",
+    InputLatencyMouseWheel: "InputLatency::MouseWheel",
+    ImplSideFling: "InputHandlerProxy::HandleGestureFling::started",
+    GCIdleLazySweep: "ThreadState::performIdleLazySweep",
+    GCCompleteSweep: "ThreadState::completeSweep",
+    GCCollectGarbage: "BlinkGCMarking",
+
+    // CpuProfile is a virtual event created on frontend to support
+    // serialization of CPU Profiles within tracing timeline data.
+    CpuProfile: "CpuProfile"
+}
+
+WebInspector.TimelineModel.Category = {
+    Console: "blink.console",
+    UserTiming: "blink.user_timing",
+    LatencyInfo: "latencyInfo"
 };
 
-module.exports=exports['default'];
-
-
-},{"../utils":263}],259:[function(require,module,exports){
-'use strict';
-
-exports.__esModule=true;
-
-var _utils=require('./utils');
-
-var logger={
-methodMap:['debug','info','warn','error'],
-level:'info',
-
-
-lookupLevel:function lookupLevel(level){
-if(typeof level==='string'){
-var levelMap=_utils.indexOf(logger.methodMap,level.toLowerCase());
-if(levelMap>=0){
-level=levelMap;
-}else{
-level=parseInt(level,10);
-}
+/**
+ * @enum {string}
+ */
+WebInspector.TimelineModel.WarningType = {
+    ForcedStyle: "ForcedStyle",
+    ForcedLayout: "ForcedLayout",
+    IdleDeadlineExceeded: "IdleDeadlineExceeded",
+    V8Deopt: "V8Deopt"
 }
 
-return level;
-},
+WebInspector.TimelineModel.MainThreadName = "main";
+WebInspector.TimelineModel.WorkerThreadName = "DedicatedWorker Thread";
+WebInspector.TimelineModel.RendererMainThreadName = "CrRendererMain";
 
-
-log:function log(level){
-level=logger.lookupLevel(level);
-
-if(typeof console!=='undefined'&&logger.lookupLevel(logger.level)<=level){
-var method=logger.methodMap[level];
-if(!console[method]){
-
-method='log';
-}
-
-for(var _len=arguments.length,message=Array(_len>1?_len-1:0),_key=1;_key<_len;_key++){
-message[_key-1]=arguments[_key];
-}
-
-console[method].apply(console,message);
-}
-}};
-
-
-exports['default']=logger;
-module.exports=exports['default'];
-
-
-},{"./utils":263}],260:[function(require,module,exports){
-(function(global){
-
-'use strict';
-
-exports.__esModule=true;
-
-exports['default']=function(Handlebars){
-
-var root=typeof global!=='undefined'?global:window,
-$Handlebars=root.Handlebars;
-
-Handlebars.noConflict=function(){
-if(root.Handlebars===Handlebars){
-root.Handlebars=$Handlebars;
-}
-return Handlebars;
-};
+/**
+ * @enum {symbol}
+ */
+WebInspector.TimelineModel.AsyncEventGroup = {
+    animation: Symbol("animation"),
+    console: Symbol("console"),
+    userTiming: Symbol("userTiming"),
+    input: Symbol("input")
 };
 
-module.exports=exports['default'];
+/**
+ * @param {!Array.<!WebInspector.TracingModel.Event>} events
+ * @param {function(!WebInspector.TracingModel.Event)} onStartEvent
+ * @param {function(!WebInspector.TracingModel.Event)} onEndEvent
+ * @param {function(!WebInspector.TracingModel.Event,?WebInspector.TracingModel.Event)|undefined=} onInstantEvent
+ * @param {number=} startTime
+ * @param {number=} endTime
+ */
+WebInspector.TimelineModel.forEachEvent = function(events, onStartEvent, onEndEvent, onInstantEvent, startTime, endTime)
+{
+    startTime = startTime || 0;
+    endTime = endTime || Infinity;
+    var stack = [];
+    for (var i = 0; i < events.length; ++i) {
+        var e = events[i];
+        if ((e.endTime || e.startTime) < startTime)
+            continue;
+        if (e.startTime >= endTime)
+            break;
+        if (WebInspector.TracingModel.isAsyncPhase(e.phase) || WebInspector.TracingModel.isFlowPhase(e.phase))
+            continue;
+        while (stack.length && stack.peekLast().endTime <= e.startTime)
+            onEndEvent(stack.pop());
+        if (e.duration) {
+            onStartEvent(e);
+            stack.push(e);
+        } else {
+            onInstantEvent && onInstantEvent(e, stack.peekLast() || null);
+        }
+    }
+    while (stack.length)
+        onEndEvent(stack.pop());
+}
 
+WebInspector.TimelineModel.DevToolsMetadataEvent = {
+    TracingStartedInBrowser: "TracingStartedInBrowser",
+    TracingStartedInPage: "TracingStartedInPage",
+    TracingSessionIdForWorker: "TracingSessionIdForWorker",
+};
 
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
+/**
+ * @constructor
+ * @param {string} name
+ */
+WebInspector.TimelineModel.VirtualThread = function(name)
+{
+    this.name = name;
+    /** @type {!Array<!WebInspector.TracingModel.Event>} */
+    this.events = [];
+    /** @type {!Map<!WebInspector.TimelineModel.AsyncEventGroup, !Array<!WebInspector.TracingModel.AsyncEvent>>} */
+    this.asyncEventsByGroup = new Map();
+}
+
+WebInspector.TimelineModel.VirtualThread.prototype = {
+    /**
+     * @return {boolean}
+     */
+    isWorker: function()
+    {
+        return this.name === WebInspector.TimelineModel.WorkerThreadName;
+    }
+}
+
+/**
+ * @constructor
+ * @param {!WebInspector.TracingModel.Event} traceEvent
+ */
+WebInspector.TimelineModel.Record = function(traceEvent)
+{
+    this._event = traceEvent;
+    this._children = [];
+}
+
+/**
+ * @param {!WebInspector.TimelineModel.Record} a
+ * @param {!WebInspector.TimelineModel.Record} b
+ * @return {number}
+ */
+WebInspector.TimelineModel.Record._compareStartTime = function(a, b)
+{
+    // Never return 0 as otherwise equal records would be merged.
+    return a.startTime() <= b.startTime() ? -1 : 1;
+}
+
+WebInspector.TimelineModel.Record.prototype = {
+    /**
+     * @return {?WebInspector.Target}
+     */
+    target: function()
+    {
+        var threadName = this._event.thread.name();
+        // FIXME: correctly specify target
+        return threadName === WebInspector.TimelineModel.RendererMainThreadName ? WebInspector.targetManager.targets()[0] || null : null;
+    },
+
+    /**
+     * @return {!Array.<!WebInspector.TimelineModel.Record>}
+     */
+    children: function()
+    {
+        return this._children;
+    },
+
+    /**
+     * @return {number}
+     */
+    startTime: function()
+    {
+        return this._event.startTime;
+    },
+
+    /**
+     * @return {number}
+     */
+    endTime: function()
+    {
+        return this._event.endTime || this._event.startTime;
+    },
+
+    /**
+     * @return {string}
+     */
+    thread: function()
+    {
+        if (this._event.thread.name() === WebInspector.TimelineModel.RendererMainThreadName)
+            return WebInspector.TimelineModel.MainThreadName;
+        return this._event.thread.name();
+    },
+
+    /**
+     * @return {!WebInspector.TimelineModel.RecordType}
+     */
+    type: function()
+    {
+        return WebInspector.TimelineModel._eventType(this._event);
+    },
+
+    /**
+     * @param {string} key
+     * @return {?Object}
+     */
+    getUserObject: function(key)
+    {
+        if (key === "TimelineUIUtils::preview-element")
+            return this._event.previewElement;
+        throw new Error("Unexpected key: " + key);
+    },
+
+    /**
+     * @param {string} key
+     * @param {?Object|undefined} value
+     */
+    setUserObject: function(key, value)
+    {
+        if (key !== "TimelineUIUtils::preview-element")
+            throw new Error("Unexpected key: " + key);
+        this._event.previewElement = /** @type {?Element} */ (value);
+    },
+
+    /**
+     * @return {!WebInspector.TracingModel.Event}
+     */
+    traceEvent: function()
+    {
+        return this._event;
+    },
+
+    /**
+     * @param {!WebInspector.TimelineModel.Record} child
+     */
+    _addChild: function(child)
+    {
+        this._children.push(child);
+        child.parent = this;
+    }
+}
+
+/** @typedef {!{page: !Array<!WebInspector.TracingModel.Event>, workers: !Array<!WebInspector.TracingModel.Event>}} */
+WebInspector.TimelineModel.MetadataEvents;
+
+/**
+ * @return {!WebInspector.TimelineModel.RecordType}
+ */
+WebInspector.TimelineModel._eventType = function(event)
+{
+    if (event.hasCategory(WebInspector.TimelineModel.Category.Console))
+        return WebInspector.TimelineModel.RecordType.ConsoleTime;
+    if (event.hasCategory(WebInspector.TimelineModel.Category.UserTiming))
+        return WebInspector.TimelineModel.RecordType.UserTiming;
+    if (event.hasCategory(WebInspector.TimelineModel.Category.LatencyInfo))
+        return WebInspector.TimelineModel.RecordType.LatencyInfo;
+    return /** @type !WebInspector.TimelineModel.RecordType */ (event.name);
+}
+
+WebInspector.TimelineModel.prototype = {
+    /**
+     * @deprecated Test use only!
+     * @param {?function(!WebInspector.TimelineModel.Record)|?function(!WebInspector.TimelineModel.Record,number)} preOrderCallback
+     * @param {function(!WebInspector.TimelineModel.Record)|function(!WebInspector.TimelineModel.Record,number)=} postOrderCallback
+     * @return {boolean}
+     */
+    forAllRecords: function(preOrderCallback, postOrderCallback)
+    {
+        /**
+         * @param {!Array.<!WebInspector.TimelineModel.Record>} records
+         * @param {number} depth
+         * @return {boolean}
+         */
+        function processRecords(records, depth)
+        {
+            for (var i = 0; i < records.length; ++i) {
+                var record = records[i];
+                if (preOrderCallback && preOrderCallback(record, depth))
+                    return true;
+                if (processRecords(record.children(), depth + 1))
+                    return true;
+                if (postOrderCallback && postOrderCallback(record, depth))
+                    return true;
+            }
+            return false;
+        }
+        return processRecords(this._records, 0);
+    },
+
+    /**
+     * @param {!Array<!WebInspector.TimelineModel.Filter>} filters
+     * @param {function(!WebInspector.TimelineModel.Record)|function(!WebInspector.TimelineModel.Record,number)} callback
+     */
+    forAllFilteredRecords: function(filters, callback)
+    {
+        /**
+         * @param {!WebInspector.TimelineModel.Record} record
+         * @param {number} depth
+         * @this {WebInspector.TimelineModel}
+         * @return {boolean}
+         */
+        function processRecord(record, depth)
+        {
+            var visible = WebInspector.TimelineModel.isVisible(filters, record.traceEvent());
+            if (visible && callback(record, depth))
+                return true;
+
+            for (var i = 0; i < record.children().length; ++i) {
+                if (processRecord.call(this, record.children()[i], visible ? depth + 1 : depth))
+                    return true;
+            }
+            return false;
+        }
+
+        for (var i = 0; i < this._records.length; ++i)
+            processRecord.call(this, this._records[i], 0);
+    },
+
+    /**
+     * @return {!Array.<!WebInspector.TimelineModel.Record>}
+     */
+    records: function()
+    {
+        return this._records;
+    },
+
+    /**
+     * @return {!Array<!WebInspector.CPUProfileDataModel>}
+     */
+    cpuProfiles: function()
+    {
+        return this._cpuProfiles;
+    },
+
+    /**
+     * @return {?string}
+     */
+    sessionId: function()
+    {
+        return this._sessionId;
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} event
+     * @return {?WebInspector.Target}
+     */
+    targetByEvent: function(event)
+    {
+        // FIXME: Consider returning null for loaded traces.
+        var workerId = this._workerIdByThread.get(event.thread);
+        var mainTarget = WebInspector.targetManager.mainTarget();
+        return workerId ? mainTarget.workerManager.targetByWorkerId(workerId) : mainTarget;
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel} tracingModel
+     * @param {boolean=} produceTraceStartedInPage
+     */
+    setEvents: function(tracingModel, produceTraceStartedInPage)
+    {
+        this.reset();
+        this._resetProcessingState();
+
+        this._minimumRecordTime = tracingModel.minimumRecordTime();
+        this._maximumRecordTime = tracingModel.maximumRecordTime();
+
+        var metadataEvents = this._processMetadataEvents(tracingModel, !!produceTraceStartedInPage);
+        if (Runtime.experiments.isEnabled("timelineShowAllProcesses")) {
+            var lastPageMetaEvent = metadataEvents.page.peekLast();
+            for (var process of tracingModel.sortedProcesses()) {
+                for (var thread of process.sortedThreads())
+                    this._processThreadEvents(0, Infinity, thread, thread === lastPageMetaEvent.thread);
+            }
+        } else {
+            var startTime = 0;
+            for (var i = 0, length = metadataEvents.page.length; i < length; i++) {
+                var metaEvent = metadataEvents.page[i];
+                var process = metaEvent.thread.process();
+                var endTime = i + 1 < length ? metadataEvents.page[i + 1].startTime : Infinity;
+                this._currentPage = metaEvent.args["data"] && metaEvent.args["data"]["page"];
+                for (var thread of process.sortedThreads()) {
+                    if (thread.name() === WebInspector.TimelineModel.WorkerThreadName) {
+                        var workerMetaEvent = metadataEvents.workers.find(e => e.args["data"]["workerThreadId"] === thread.id());
+                        if (!workerMetaEvent)
+                            continue;
+                        var workerId = workerMetaEvent.args["data"]["workerId"];
+                        if (workerId)
+                            this._workerIdByThread.set(thread, workerId);
+                    }
+                    this._processThreadEvents(startTime, endTime, thread, thread === metaEvent.thread);
+                }
+                startTime = endTime;
+            }
+        }
+        this._inspectedTargetEvents.sort(WebInspector.TracingModel.Event.compareStartTime);
+
+        this._processBrowserEvents(tracingModel);
+        this._buildTimelineRecords();
+        this._buildGPUEvents(tracingModel);
+        this._insertFirstPaintEvent();
+        this._resetProcessingState();
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel} tracingModel
+     * @param {boolean} produceTraceStartedInPage
+     * @return {!WebInspector.TimelineModel.MetadataEvents}
+     */
+    _processMetadataEvents: function(tracingModel, produceTraceStartedInPage)
+    {
+        var metadataEvents = tracingModel.devToolsMetadataEvents();
+
+        var pageDevToolsMetadataEvents = [];
+        var workersDevToolsMetadataEvents = [];
+        for (var event of metadataEvents) {
+            if (event.name === WebInspector.TimelineModel.DevToolsMetadataEvent.TracingStartedInPage) {
+                pageDevToolsMetadataEvents.push(event);
+            } else if (event.name === WebInspector.TimelineModel.DevToolsMetadataEvent.TracingSessionIdForWorker) {
+                workersDevToolsMetadataEvents.push(event);
+            } else if (event.name === WebInspector.TimelineModel.DevToolsMetadataEvent.TracingStartedInBrowser) {
+                console.assert(!this._mainFrameNodeId, "Multiple sessions in trace");
+                this._mainFrameNodeId = event.args["frameTreeNodeId"];
+            }
+        }
+        if (!pageDevToolsMetadataEvents.length) {
+            // The trace is probably coming not from DevTools. Make a mock Metadata event.
+            var pageMetaEvent = produceTraceStartedInPage ? this._makeMockPageMetadataEvent(tracingModel) : null;
+            if (!pageMetaEvent) {
+                console.error(WebInspector.TimelineModel.DevToolsMetadataEvent.TracingStartedInPage + " event not found.");
+                return {page: [], workers: []};
+            }
+            pageDevToolsMetadataEvents.push(pageMetaEvent);
+        }
+        var sessionId = pageDevToolsMetadataEvents[0].args["sessionId"] || pageDevToolsMetadataEvents[0].args["data"]["sessionId"];
+        this._sessionId = sessionId;
+
+        var mismatchingIds = new Set();
+        /**
+         * @param {!WebInspector.TracingModel.Event} event
+         * @return {boolean}
+         */
+        function checkSessionId(event)
+        {
+            var args = event.args;
+            // FIXME: put sessionId into args["data"] for TracingStartedInPage event.
+            if (args["data"])
+                args = args["data"];
+            var id = args["sessionId"];
+            if (id === sessionId)
+                return true;
+            mismatchingIds.add(id);
+            return false;
+        }
+        var result = {
+            page: pageDevToolsMetadataEvents.filter(checkSessionId).sort(WebInspector.TracingModel.Event.compareStartTime),
+            workers: workersDevToolsMetadataEvents.filter(checkSessionId).sort(WebInspector.TracingModel.Event.compareStartTime)
+        };
+        if (mismatchingIds.size)
+            WebInspector.console.error("Timeline recording was started in more than one page simultaneously. Session id mismatch: " + this._sessionId + " and " + mismatchingIds.valuesArray() + ".");
+        return result;
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel} tracingModel
+     * @return {?WebInspector.TracingModel.Event}
+     */
+    _makeMockPageMetadataEvent: function(tracingModel)
+    {
+        var rendererMainThreadName = WebInspector.TimelineModel.RendererMainThreadName;
+        // FIXME: pick up the first renderer process for now.
+        var process = tracingModel.sortedProcesses().filter(function(p) { return p.threadByName(rendererMainThreadName); })[0];
+        var thread = process && process.threadByName(rendererMainThreadName);
+        if (!thread)
+            return null;
+        var pageMetaEvent = new WebInspector.TracingModel.Event(
+            WebInspector.TracingModel.DevToolsMetadataEventCategory,
+            WebInspector.TimelineModel.DevToolsMetadataEvent.TracingStartedInPage,
+            WebInspector.TracingModel.Phase.Metadata,
+            tracingModel.minimumRecordTime(), thread);
+        pageMetaEvent.addArgs({"data": {"sessionId": "mockSessionId"}});
+        return pageMetaEvent;
+    },
+
+    _insertFirstPaintEvent: function()
+    {
+        if (!this._firstCompositeLayers)
+            return;
+
+        // First Paint is actually a DrawFrame that happened after first CompositeLayers following last CommitLoadEvent.
+        var recordTypes = WebInspector.TimelineModel.RecordType;
+        var i = this._inspectedTargetEvents.lowerBound(this._firstCompositeLayers, WebInspector.TracingModel.Event.compareStartTime);
+        for (; i < this._inspectedTargetEvents.length && this._inspectedTargetEvents[i].name !== recordTypes.DrawFrame; ++i) { }
+        if (i >= this._inspectedTargetEvents.length)
+            return;
+        var drawFrameEvent = this._inspectedTargetEvents[i];
+        var firstPaintEvent = new WebInspector.TracingModel.Event(drawFrameEvent.categoriesString, recordTypes.MarkFirstPaint, WebInspector.TracingModel.Phase.Instant, drawFrameEvent.startTime, drawFrameEvent.thread);
+        this._mainThreadEvents.splice(this._mainThreadEvents.lowerBound(firstPaintEvent, WebInspector.TracingModel.Event.compareStartTime), 0, firstPaintEvent);
+        var firstPaintRecord = new WebInspector.TimelineModel.Record(firstPaintEvent);
+        this._eventDividerRecords.splice(this._eventDividerRecords.lowerBound(firstPaintRecord, WebInspector.TimelineModel.Record._compareStartTime), 0, firstPaintRecord);
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel} tracingModel
+     */
+    _processBrowserEvents: function(tracingModel)
+    {
+        var browserMain = WebInspector.TracingModel.browserMainThread(tracingModel);
+        if (!browserMain)
+            return;
+
+        // Disregard regular events, we don't need them yet, but still process to get proper metadata.
+        browserMain.events().forEach(this._processBrowserEvent, this);
+        /** @type {!Map<!WebInspector.TimelineModel.AsyncEventGroup, !Array<!WebInspector.TracingModel.AsyncEvent>>} */
+        var asyncEventsByGroup = new Map();
+        this._processAsyncEvents(asyncEventsByGroup, browserMain.asyncEvents());
+        this._mergeAsyncEvents(this._mainThreadAsyncEventsByGroup, asyncEventsByGroup);
+    },
+
+    _buildTimelineRecords: function()
+    {
+        var topLevelRecords = this._buildTimelineRecordsForThread(this.mainThreadEvents());
+        for (var i = 0; i < topLevelRecords.length; i++) {
+            var record = topLevelRecords[i];
+            if (WebInspector.TracingModel.isTopLevelEvent(record.traceEvent()))
+                this._mainThreadTasks.push(record);
+        }
+
+        /**
+         * @param {!WebInspector.TimelineModel.VirtualThread} virtualThread
+         * @this {!WebInspector.TimelineModel}
+         */
+        function processVirtualThreadEvents(virtualThread)
+        {
+            var threadRecords = this._buildTimelineRecordsForThread(virtualThread.events);
+            topLevelRecords = topLevelRecords.mergeOrdered(threadRecords, WebInspector.TimelineModel.Record._compareStartTime);
+        }
+        this.virtualThreads().forEach(processVirtualThreadEvents.bind(this));
+        this._records = topLevelRecords;
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel} tracingModel
+     */
+    _buildGPUEvents: function(tracingModel)
+    {
+        var thread = tracingModel.threadByName("GPU Process", "CrGpuMain");
+        if (!thread)
+            return;
+        var gpuEventName = WebInspector.TimelineModel.RecordType.GPUTask;
+        this._gpuEvents = thread.events().filter(event => event.name === gpuEventName);
+    },
+
+    /**
+     * @param {!Array.<!WebInspector.TracingModel.Event>} threadEvents
+     * @return {!Array.<!WebInspector.TimelineModel.Record>}
+     */
+    _buildTimelineRecordsForThread: function(threadEvents)
+    {
+        var recordStack = [];
+        var topLevelRecords = [];
+
+        for (var i = 0, size = threadEvents.length; i < size; ++i) {
+            var event = threadEvents[i];
+            for (var top = recordStack.peekLast(); top && top._event.endTime <= event.startTime; top = recordStack.peekLast())
+                recordStack.pop();
+            if (event.phase === WebInspector.TracingModel.Phase.AsyncEnd || event.phase === WebInspector.TracingModel.Phase.NestableAsyncEnd)
+                continue;
+            var parentRecord = recordStack.peekLast();
+            // Maintain the back-end logic of old timeline, skip console.time() / console.timeEnd() that are not properly nested.
+            if (WebInspector.TracingModel.isAsyncBeginPhase(event.phase) && parentRecord && event.endTime > parentRecord._event.endTime)
+                continue;
+            var record = new WebInspector.TimelineModel.Record(event);
+            if (WebInspector.TimelineModel.isMarkerEvent(event))
+                this._eventDividerRecords.push(record);
+            if (!this._eventFilter.accept(event) && !WebInspector.TracingModel.isTopLevelEvent(event))
+                continue;
+            if (parentRecord)
+                parentRecord._addChild(record);
+            else
+                topLevelRecords.push(record);
+            if (event.endTime)
+                recordStack.push(record);
+        }
+
+        return topLevelRecords;
+    },
+
+    _resetProcessingState: function()
+    {
+        this._asyncEventTracker = new WebInspector.TimelineAsyncEventTracker();
+        this._invalidationTracker = new WebInspector.InvalidationTracker();
+        this._layoutInvalidate = {};
+        this._lastScheduleStyleRecalculation = {};
+        this._paintImageEventByPixelRefId = {};
+        this._lastPaintForLayer = {};
+        this._lastRecalculateStylesEvent = null;
+        this._currentScriptEvent = null;
+        this._eventStack = [];
+        this._hadCommitLoad = false;
+        this._firstCompositeLayers = null;
+        /** @type {!Set<string>} */
+        this._knownInputEvents = new Set();
+        this._currentPage = null;
+    },
+
+    /**
+     * @param {number} startTime
+     * @param {number} endTime
+     * @param {!WebInspector.TracingModel.Thread} thread
+     * @param {boolean} isMainThread
+     */
+    _processThreadEvents: function(startTime, endTime, thread, isMainThread)
+    {
+        var events = thread.events();
+        var asyncEvents = thread.asyncEvents();
+
+        var jsSamples;
+        if (Runtime.experiments.isEnabled("timelineTracingJSProfile")) {
+            jsSamples = WebInspector.TimelineJSProfileProcessor.processRawV8Samples(events);
+        } else {
+            var cpuProfileEvent = events.peekLast();
+            if (cpuProfileEvent && cpuProfileEvent.name === WebInspector.TimelineModel.RecordType.CpuProfile) {
+                var cpuProfile = cpuProfileEvent.args["data"]["cpuProfile"];
+                if (cpuProfile) {
+                    var jsProfileModel = new WebInspector.CPUProfileDataModel(cpuProfile);
+                    this._cpuProfiles.push(jsProfileModel);
+                    jsSamples = WebInspector.TimelineJSProfileProcessor.generateTracingEventsFromCpuProfile(jsProfileModel, thread);
+                }
+            }
+        }
+
+        if (jsSamples && jsSamples.length)
+            events = events.mergeOrdered(jsSamples, WebInspector.TracingModel.Event.orderedCompareStartTime);
+        if (jsSamples || events.some(function(e) { return e.name === WebInspector.TimelineModel.RecordType.JSSample; })) {
+            var jsFrameEvents = WebInspector.TimelineJSProfileProcessor.generateJSFrameEvents(events);
+            if (jsFrameEvents && jsFrameEvents.length)
+                events = jsFrameEvents.mergeOrdered(events, WebInspector.TracingModel.Event.orderedCompareStartTime);
+        }
+
+        var threadEvents;
+        var threadAsyncEventsByGroup;
+        if (isMainThread) {
+            threadEvents = this._mainThreadEvents;
+            threadAsyncEventsByGroup = this._mainThreadAsyncEventsByGroup;
+        } else {
+            var virtualThread = new WebInspector.TimelineModel.VirtualThread(thread.name());
+            this._virtualThreads.push(virtualThread);
+            threadEvents = virtualThread.events;
+            threadAsyncEventsByGroup = virtualThread.asyncEventsByGroup;
+        }
+
+        this._eventStack = [];
+        var i = events.lowerBound(startTime, function(time, event) { return time - event.startTime });
+        var length = events.length;
+        for (; i < length; i++) {
+            var event = events[i];
+            if (endTime && event.startTime >= endTime)
+                break;
+            if (!this._processEvent(event))
+                continue;
+            threadEvents.push(event);
+            this._inspectedTargetEvents.push(event);
+        }
+        this._processAsyncEvents(threadAsyncEventsByGroup, asyncEvents, startTime, endTime);
+        // Pretend the compositor's async events are on the main thread.
+        if (thread.name() === "Compositor") {
+            this._mergeAsyncEvents(this._mainThreadAsyncEventsByGroup, threadAsyncEventsByGroup);
+            threadAsyncEventsByGroup.clear();
+        }
+    },
+
+    /**
+     * @param {!Map<!WebInspector.TimelineModel.AsyncEventGroup, !Array<!WebInspector.TracingModel.AsyncEvent>>} asyncEventsByGroup
+     * @param {!Array<!WebInspector.TracingModel.AsyncEvent>} asyncEvents
+     * @param {number=} startTime
+     * @param {number=} endTime
+     */
+    _processAsyncEvents: function(asyncEventsByGroup, asyncEvents, startTime, endTime)
+    {
+        var i = startTime ? asyncEvents.lowerBound(startTime, function(time, asyncEvent) { return time - asyncEvent.startTime }) : 0;
+        for (; i < asyncEvents.length; ++i) {
+            var asyncEvent = asyncEvents[i];
+            if (endTime && asyncEvent.startTime >= endTime)
+                break;
+            var asyncGroup = this._processAsyncEvent(asyncEvent);
+            if (!asyncGroup)
+                continue;
+            var groupAsyncEvents = asyncEventsByGroup.get(asyncGroup);
+            if (!groupAsyncEvents) {
+                groupAsyncEvents = [];
+                asyncEventsByGroup.set(asyncGroup, groupAsyncEvents);
+            }
+            groupAsyncEvents.push(asyncEvent);
+        }
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} event
+     * @return {boolean}
+     */
+    _processEvent: function(event)
+    {
+        var eventStack = this._eventStack;
+        while (eventStack.length && eventStack.peekLast().endTime <= event.startTime)
+            eventStack.pop();
+
+        var recordTypes = WebInspector.TimelineModel.RecordType;
+
+        if (this._currentScriptEvent && event.startTime > this._currentScriptEvent.endTime)
+            this._currentScriptEvent = null;
+
+        var eventData = event.args["data"] || event.args["beginData"] || {};
+        if (eventData["stackTrace"])
+            event.stackTrace = eventData["stackTrace"];
+        if (event.stackTrace && event.name !== recordTypes.JSSample) {
+            // TraceEvents come with 1-based line & column numbers. The frontend code
+            // requires 0-based ones. Adjust the values.
+            for (var i = 0; i < event.stackTrace.length; ++i) {
+                --event.stackTrace[i].lineNumber;
+                --event.stackTrace[i].columnNumber;
+            }
+        }
+
+        if (eventStack.length && eventStack.peekLast().name === recordTypes.EventDispatch)
+            eventStack.peekLast().hasChildren = true;
+        this._asyncEventTracker.processEvent(event);
+        if (event.initiator && event.initiator.url)
+            event.url = event.initiator.url;
+        switch (event.name) {
+        case recordTypes.ResourceSendRequest:
+        case recordTypes.WebSocketCreate:
+            event.url = eventData["url"];
+            event.initiator = eventStack.peekLast() || null;
+            break;
+
+        case recordTypes.ScheduleStyleRecalculation:
+            this._lastScheduleStyleRecalculation[eventData["frame"]] = event;
+            break;
+
+        case recordTypes.UpdateLayoutTree:
+        case recordTypes.RecalculateStyles:
+            this._invalidationTracker.didRecalcStyle(event);
+            if (event.args["beginData"])
+                event.initiator = this._lastScheduleStyleRecalculation[event.args["beginData"]["frame"]];
+            this._lastRecalculateStylesEvent = event;
+            if (this._currentScriptEvent)
+                event.warning = WebInspector.TimelineModel.WarningType.ForcedStyle;
+            break;
+
+        case recordTypes.ScheduleStyleInvalidationTracking:
+        case recordTypes.StyleRecalcInvalidationTracking:
+        case recordTypes.StyleInvalidatorInvalidationTracking:
+        case recordTypes.LayoutInvalidationTracking:
+        case recordTypes.LayerInvalidationTracking:
+        case recordTypes.PaintInvalidationTracking:
+        case recordTypes.ScrollInvalidationTracking:
+            this._invalidationTracker.addInvalidation(new WebInspector.InvalidationTrackingEvent(event));
+            break;
+
+        case recordTypes.InvalidateLayout:
+            // Consider style recalculation as a reason for layout invalidation,
+            // but only if we had no earlier layout invalidation records.
+            var layoutInitator = event;
+            var frameId = eventData["frame"];
+            if (!this._layoutInvalidate[frameId] && this._lastRecalculateStylesEvent && this._lastRecalculateStylesEvent.endTime >  event.startTime)
+                layoutInitator = this._lastRecalculateStylesEvent.initiator;
+            this._layoutInvalidate[frameId] = layoutInitator;
+            break;
+
+        case recordTypes.Layout:
+            this._invalidationTracker.didLayout(event);
+            var frameId = event.args["beginData"]["frame"];
+            event.initiator = this._layoutInvalidate[frameId];
+            // In case we have no closing Layout event, endData is not available.
+            if (event.args["endData"]) {
+                event.backendNodeId = event.args["endData"]["rootNode"];
+                event.highlightQuad =  event.args["endData"]["root"];
+            }
+            this._layoutInvalidate[frameId] = null;
+            if (this._currentScriptEvent)
+                event.warning = WebInspector.TimelineModel.WarningType.ForcedLayout;
+            break;
+
+        case recordTypes.FunctionCall:
+            // Compatibility with old format.
+            if (typeof eventData["scriptName"] === "string")
+                eventData["url"] = eventData["scriptName"];
+            if (typeof eventData["scriptLine"] === "number")
+                eventData["lineNumber"] = eventData["scriptLine"];
+            // Fallthrough.
+        case recordTypes.EvaluateScript:
+        case recordTypes.CompileScript:
+            if (typeof eventData["lineNumber"] === "number")
+                --eventData["lineNumber"];
+            if (typeof eventData["columnNumber"] === "number")
+                --eventData["columnNumber"];
+            if (!this._currentScriptEvent)
+                this._currentScriptEvent = event;
+            break;
+
+        case recordTypes.SetLayerTreeId:
+            this._inspectedTargetLayerTreeId = event.args["layerTreeId"] || event.args["data"]["layerTreeId"];
+            break;
+
+        case recordTypes.Paint:
+            this._invalidationTracker.didPaint(event);
+            event.highlightQuad = eventData["clip"];
+            event.backendNodeId = eventData["nodeId"];
+            // Only keep layer paint events, skip paints for subframes that get painted to the same layer as parent.
+            if (!eventData["layerId"])
+                break;
+            var layerId = eventData["layerId"];
+            this._lastPaintForLayer[layerId] = event;
+            break;
+
+        case recordTypes.DisplayItemListSnapshot:
+        case recordTypes.PictureSnapshot:
+            var layerUpdateEvent = this._findAncestorEvent(recordTypes.UpdateLayer);
+            if (!layerUpdateEvent || layerUpdateEvent.args["layerTreeId"] !== this._inspectedTargetLayerTreeId)
+                break;
+            var paintEvent = this._lastPaintForLayer[layerUpdateEvent.args["layerId"]];
+            if (paintEvent)
+                paintEvent.picture = event;
+            break;
+
+        case recordTypes.ScrollLayer:
+            event.backendNodeId = eventData["nodeId"];
+            break;
+
+        case recordTypes.PaintImage:
+            event.backendNodeId = eventData["nodeId"];
+            event.url = eventData["url"];
+            break;
+
+        case recordTypes.DecodeImage:
+        case recordTypes.ResizeImage:
+            var paintImageEvent = this._findAncestorEvent(recordTypes.PaintImage);
+            if (!paintImageEvent) {
+                var decodeLazyPixelRefEvent = this._findAncestorEvent(recordTypes.DecodeLazyPixelRef);
+                paintImageEvent = decodeLazyPixelRefEvent && this._paintImageEventByPixelRefId[decodeLazyPixelRefEvent.args["LazyPixelRef"]];
+            }
+            if (!paintImageEvent)
+                break;
+            event.backendNodeId = paintImageEvent.backendNodeId;
+            event.url = paintImageEvent.url;
+            break;
+
+        case recordTypes.DrawLazyPixelRef:
+            var paintImageEvent = this._findAncestorEvent(recordTypes.PaintImage);
+            if (!paintImageEvent)
+                break;
+            this._paintImageEventByPixelRefId[event.args["LazyPixelRef"]] = paintImageEvent;
+            event.backendNodeId = paintImageEvent.backendNodeId;
+            event.url = paintImageEvent.url;
+            break;
+
+        case recordTypes.MarkDOMContent:
+        case recordTypes.MarkLoad:
+            var page = eventData["page"];
+            if (page && page !== this._currentPage)
+                return false;
+            break;
+
+        case recordTypes.CommitLoad:
+            var page = eventData["page"];
+            if (page && page !== this._currentPage)
+                return false;
+            if (!eventData["isMainFrame"])
+                break;
+            this._hadCommitLoad = true;
+            this._firstCompositeLayers = null;
+            break;
+
+        case recordTypes.CompositeLayers:
+            if (!this._firstCompositeLayers && this._hadCommitLoad)
+                this._firstCompositeLayers = event;
+            break;
+
+        case recordTypes.FireIdleCallback:
+            if (event.duration > eventData["allottedMilliseconds"]) {
+                event.warning = WebInspector.TimelineModel.WarningType.IdleDeadlineExceeded;
+            }
+            break;
+        }
+        if (WebInspector.TracingModel.isAsyncPhase(event.phase))
+            return true;
+        var duration = event.duration;
+        if (!duration)
+            return true;
+        if (eventStack.length) {
+            var parent = eventStack.peekLast();
+            parent.selfTime -= duration;
+            if (parent.selfTime < 0) {
+                var epsilon = 1e-3;
+                if (parent.selfTime < -epsilon)
+                    console.error("Children are longer than parent at " + event.startTime + " (" + (event.startTime - this.minimumRecordTime()).toFixed(3) + ") by " + parent.selfTime.toFixed(3));
+                parent.selfTime = 0;
+            }
+        }
+        event.selfTime = duration;
+        eventStack.push(event);
+        return true;
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} event
+     */
+    _processBrowserEvent: function(event)
+    {
+        if (event.name !== WebInspector.TimelineModel.RecordType.LatencyInfoFlow)
+            return;
+        var frameId = event.args["frameTreeNodeId"];
+        if (typeof frameId === "number" && frameId === this._mainFrameNodeId)
+            this._knownInputEvents.add(event.bind_id);
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.AsyncEvent} asyncEvent
+     * @return {?WebInspector.TimelineModel.AsyncEventGroup}
+     */
+    _processAsyncEvent: function(asyncEvent)
+    {
+        var groups = WebInspector.TimelineModel.AsyncEventGroup;
+        if (asyncEvent.hasCategory(WebInspector.TimelineModel.Category.Console))
+            return groups.console;
+        if (asyncEvent.hasCategory(WebInspector.TimelineModel.Category.UserTiming))
+            return groups.userTiming;
+        if (asyncEvent.name === WebInspector.TimelineModel.RecordType.Animation)
+            return groups.animation;
+        if (asyncEvent.hasCategory(WebInspector.TimelineModel.Category.LatencyInfo) || asyncEvent.name === WebInspector.TimelineModel.RecordType.ImplSideFling) {
+            var lastStep = asyncEvent.steps.peekLast();
+            // FIXME: fix event termination on the back-end instead.
+            if (lastStep.phase !== WebInspector.TracingModel.Phase.AsyncEnd)
+                return null;
+            var data = lastStep.args["data"];
+            asyncEvent.causedFrame = !!(data && data["INPUT_EVENT_LATENCY_RENDERER_SWAP_COMPONENT"]);
+            if (asyncEvent.hasCategory(WebInspector.TimelineModel.Category.LatencyInfo)) {
+                if (!this._knownInputEvents.has(lastStep.id))
+                    return null;
+                if (asyncEvent.name === WebInspector.TimelineModel.RecordType.InputLatencyMouseMove && !asyncEvent.causedFrame)
+                    return null;
+                var rendererMain = data["INPUT_EVENT_LATENCY_RENDERER_MAIN_COMPONENT"];
+                if (rendererMain) {
+                    var time = rendererMain["time"] / 1000;
+                    asyncEvent.steps[0].timeWaitingForMainThread = time - asyncEvent.steps[0].startTime;
+                }
+            }
+            return groups.input;
+        }
+        return null;
+    },
+
+    /**
+     * @param {string} name
+     * @return {?WebInspector.TracingModel.Event}
+     */
+    _findAncestorEvent: function(name)
+    {
+        for (var i = this._eventStack.length - 1; i >= 0; --i) {
+            var event = this._eventStack[i];
+            if (event.name === name)
+                return event;
+        }
+        return null;
+    },
+
+    /**
+     * @param {!Map<!WebInspector.TimelineModel.AsyncEventGroup, !Array<!WebInspector.TracingModel.AsyncEvent>>} target
+     * @param {!Map<!WebInspector.TimelineModel.AsyncEventGroup, !Array<!WebInspector.TracingModel.AsyncEvent>>} source
+     */
+    _mergeAsyncEvents: function(target, source)
+    {
+        for (var group of source.keys()) {
+            var events = target.get(group) || [];
+            events = events.mergeOrdered(source.get(group) || [], WebInspector.TracingModel.Event.compareStartAndEndTime);
+            target.set(group, events);
+        }
+    },
+
+    reset: function()
+    {
+        this._virtualThreads = [];
+        /** @type {!Array<!WebInspector.TracingModel.Event>} */
+        this._mainThreadEvents = [];
+        /** @type {!Map<!WebInspector.TimelineModel.AsyncEventGroup, !Array<!WebInspector.TracingModel.AsyncEvent>>} */
+        this._mainThreadAsyncEventsByGroup = new Map();
+        /** @type {!Array<!WebInspector.TracingModel.Event>} */
+        this._inspectedTargetEvents = [];
+        /** @type {!Array<!WebInspector.TimelineModel.Record>} */
+        this._records = [];
+        /** @type {!Array<!WebInspector.TimelineModel.Record>} */
+        this._mainThreadTasks = [];
+        /** @type {!Array<!WebInspector.TracingModel.Event>} */
+        this._gpuEvents = [];
+        /** @type {!Array<!WebInspector.TimelineModel.Record>} */
+        this._eventDividerRecords = [];
+        /** @type {?string} */
+        this._sessionId = null;
+        /** @type {?number} */
+        this._mainFrameNodeId = null;
+        /** @type {!Array<!WebInspector.CPUProfileDataModel>} */
+        this._cpuProfiles = [];
+        /** @type {!WeakMap<!WebInspector.TracingModel.Thread, string>} */
+        this._workerIdByThread = new WeakMap();
+        this._minimumRecordTime = 0;
+        this._maximumRecordTime = 0;
+    },
+
+    /**
+     * @return {number}
+     */
+    minimumRecordTime: function()
+    {
+        return this._minimumRecordTime;
+    },
+
+    /**
+     * @return {number}
+     */
+    maximumRecordTime: function()
+    {
+        return this._maximumRecordTime;
+    },
+
+    /**
+     * @return {!Array.<!WebInspector.TracingModel.Event>}
+     */
+    inspectedTargetEvents: function()
+    {
+        return this._inspectedTargetEvents;
+    },
+
+    /**
+     * @return {!Array.<!WebInspector.TracingModel.Event>}
+     */
+    mainThreadEvents: function()
+    {
+        return this._mainThreadEvents;
+    },
+
+    /**
+     * @param {!Array.<!WebInspector.TracingModel.Event>} events
+     */
+    _setMainThreadEvents: function(events)
+    {
+        this._mainThreadEvents = events;
+    },
+
+    /**
+     * @return {!Map<!WebInspector.TimelineModel.AsyncEventGroup, !Array.<!WebInspector.TracingModel.AsyncEvent>>}
+     */
+    mainThreadAsyncEvents: function()
+    {
+        return this._mainThreadAsyncEventsByGroup;
+    },
+
+    /**
+     * @return {!Array.<!WebInspector.TimelineModel.VirtualThread>}
+     */
+    virtualThreads: function()
+    {
+        return this._virtualThreads;
+    },
+
+    /**
+     * @return {boolean}
+     */
+    isEmpty: function()
+    {
+        return this.minimumRecordTime() === 0 && this.maximumRecordTime() === 0;
+    },
+
+    /**
+     * @return {!Array.<!WebInspector.TimelineModel.Record>}
+     */
+    mainThreadTasks: function()
+    {
+        return this._mainThreadTasks;
+    },
+
+    /**
+     * @return {!Array<!WebInspector.TracingModel.Event>}
+     */
+    gpuEvents: function()
+    {
+        return this._gpuEvents;
+    },
+
+    /**
+     * @return {!Array.<!WebInspector.TimelineModel.Record>}
+     */
+    eventDividerRecords: function()
+    {
+        return this._eventDividerRecords;
+    },
+
+    /**
+     * @return {!Array<!WebInspector.TimelineModel.NetworkRequest>}
+     */
+    networkRequests: function()
+    {
+        /** @type {!Map<string,!WebInspector.TimelineModel.NetworkRequest>} */
+        var requests = new Map();
+        /** @type {!Array<!WebInspector.TimelineModel.NetworkRequest>} */
+        var requestsList = [];
+        /** @type {!Array<!WebInspector.TimelineModel.NetworkRequest>} */
+        var zeroStartRequestsList = [];
+        var types = WebInspector.TimelineModel.RecordType;
+        var resourceTypes = new Set([
+            types.ResourceSendRequest,
+            types.ResourceReceiveResponse,
+            types.ResourceReceivedData,
+            types.ResourceFinish
+        ]);
+        var events = this.mainThreadEvents();
+        for (var i = 0; i < events.length; ++i) {
+            var e = events[i];
+            if (!resourceTypes.has(e.name))
+                continue;
+            var id = e.args["data"]["requestId"];
+            var request = requests.get(id);
+            if (request) {
+                request.addEvent(e);
+            } else {
+                request = new WebInspector.TimelineModel.NetworkRequest(e);
+                requests.set(id, request);
+                if (request.startTime)
+                    requestsList.push(request);
+                else
+                    zeroStartRequestsList.push(request);
+            }
+        }
+        return zeroStartRequestsList.concat(requestsList);
+    },
+}
+
+/**
+ * @param {!Array<!WebInspector.TimelineModel.Filter>} filters
+ * @param {!WebInspector.TracingModel.Event} event
+ * @return {boolean}
+ */
+WebInspector.TimelineModel.isVisible = function(filters, event)
+{
+    for (var i = 0; i < filters.length; ++i) {
+        if (!filters[i].accept(event))
+            return false;
+    }
+    return true;
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @return {boolean}
+ */
+WebInspector.TimelineModel.isMarkerEvent = function(event)
+{
+    var recordTypes = WebInspector.TimelineModel.RecordType;
+    switch (event.name) {
+    case recordTypes.TimeStamp:
+    case recordTypes.MarkFirstPaint:
+        return true;
+    case recordTypes.MarkDOMContent:
+    case recordTypes.MarkLoad:
+        return event.args["data"]["isMainFrame"];
+    default:
+        return false;
+    }
+}
+
+/**
+ * @constructor
+ * @param {!WebInspector.TracingModel.Event} event
+ */
+WebInspector.TimelineModel.NetworkRequest = function(event)
+{
+    this.startTime = event.name === WebInspector.TimelineModel.RecordType.ResourceSendRequest ? event.startTime : 0;
+    this.endTime = Infinity;
+    /** @type {!Array<!WebInspector.TracingModel.Event>} */
+    this.children = [];
+    this.addEvent(event);
+}
+
+WebInspector.TimelineModel.NetworkRequest.prototype = {
+    /**
+     * @param {!WebInspector.TracingModel.Event} event
+     */
+    addEvent: function(event)
+    {
+        this.children.push(event);
+        var recordType = WebInspector.TimelineModel.RecordType;
+        this.startTime = Math.min(this.startTime, event.startTime);
+        var eventData = event.args["data"];
+        if (eventData["mimeType"])
+            this.mimeType = eventData["mimeType"];
+        if ("priority" in eventData)
+            this.priority = eventData["priority"];
+        if (event.name === recordType.ResourceFinish)
+            this.endTime = event.startTime;
+        if (!this.responseTime && (event.name === recordType.ResourceReceiveResponse || event.name === recordType.ResourceReceivedData))
+            this.responseTime = event.startTime;
+        if (!this.url)
+            this.url = eventData["url"];
+        if (!this.requestMethod)
+            this.requestMethod = eventData["requestMethod"];
+    }
+}
+
+/**
+ * @constructor
+ */
+WebInspector.TimelineModel.Filter = function()
+{
+}
+
+WebInspector.TimelineModel.Filter.prototype = {
+    /**
+     * @param {!WebInspector.TracingModel.Event} event
+     * @return {boolean}
+     */
+    accept: function(event)
+    {
+        return true;
+    }
+}
+
+/**
+ * @constructor
+ * @extends {WebInspector.TimelineModel.Filter}
+ * @param {!Array.<string>} visibleTypes
+ */
+WebInspector.TimelineVisibleEventsFilter = function(visibleTypes)
+{
+    WebInspector.TimelineModel.Filter.call(this);
+    this._visibleTypes = new Set(visibleTypes);
+}
+
+WebInspector.TimelineVisibleEventsFilter.prototype = {
+    /**
+     * @override
+     * @param {!WebInspector.TracingModel.Event} event
+     * @return {boolean}
+     */
+    accept: function(event)
+    {
+        return this._visibleTypes.has(WebInspector.TimelineModel._eventType(event));
+    },
+
+    __proto__: WebInspector.TimelineModel.Filter.prototype
+}
+
+/**
+ * @constructor
+ * @extends {WebInspector.TimelineModel.Filter}
+ * @param {!Array<string>} excludeNames
+ */
+WebInspector.ExclusiveNameFilter = function(excludeNames)
+{
+    WebInspector.TimelineModel.Filter.call(this);
+    this._excludeNames = new Set(excludeNames);
+}
+
+WebInspector.ExclusiveNameFilter.prototype = {
+    /**
+     * @override
+     * @param {!WebInspector.TracingModel.Event} event
+     * @return {boolean}
+     */
+    accept: function(event)
+    {
+        return !this._excludeNames.has(event.name);
+    },
+
+    __proto__: WebInspector.TimelineModel.Filter.prototype
+}
+
+/**
+ * @constructor
+ * @extends {WebInspector.TimelineModel.Filter}
+ */
+WebInspector.ExcludeTopLevelFilter = function()
+{
+    WebInspector.TimelineModel.Filter.call(this);
+}
+
+WebInspector.ExcludeTopLevelFilter.prototype = {
+    /**
+     * @override
+     * @param {!WebInspector.TracingModel.Event} event
+     * @return {boolean}
+     */
+    accept: function(event)
+    {
+        return !WebInspector.TracingModel.isTopLevelEvent(event);
+    },
+
+    __proto__: WebInspector.TimelineModel.Filter.prototype
+}
+
+/**
+ * @constructor
+ * @param {!WebInspector.TracingModel.Event} event
+ */
+WebInspector.InvalidationTrackingEvent = function(event)
+{
+    /** @type {string} */
+    this.type = event.name;
+    /** @type {number} */
+    this.startTime = event.startTime;
+    /** @type {!WebInspector.TracingModel.Event} */
+    this._tracingEvent = event;
+
+    var eventData = event.args["data"];
+
+    /** @type {number} */
+    this.frame = eventData["frame"];
+    /** @type {?number} */
+    this.nodeId = eventData["nodeId"];
+    /** @type {?string} */
+    this.nodeName = eventData["nodeName"];
+    /** @type {?number} */
+    this.paintId = eventData["paintId"];
+    /** @type {?number} */
+    this.invalidationSet = eventData["invalidationSet"];
+    /** @type {?string} */
+    this.invalidatedSelectorId = eventData["invalidatedSelectorId"];
+    /** @type {?string} */
+    this.changedId = eventData["changedId"];
+    /** @type {?string} */
+    this.changedClass = eventData["changedClass"];
+    /** @type {?string} */
+    this.changedAttribute = eventData["changedAttribute"];
+    /** @type {?string} */
+    this.changedPseudo = eventData["changedPseudo"];
+    /** @type {?string} */
+    this.selectorPart = eventData["selectorPart"];
+    /** @type {?string} */
+    this.extraData = eventData["extraData"];
+    /** @type {?Array.<!Object.<string, number>>} */
+    this.invalidationList = eventData["invalidationList"];
+    /** @type {!WebInspector.InvalidationCause} */
+    this.cause = {reason: eventData["reason"], stackTrace: eventData["stackTrace"]};
+
+    // FIXME: Move this to TimelineUIUtils.js.
+    if (!this.cause.reason && this.cause.stackTrace && this.type === WebInspector.TimelineModel.RecordType.LayoutInvalidationTracking)
+        this.cause.reason = "Layout forced";
+}
+
+/** @typedef {{reason: string, stackTrace: ?Array<!RuntimeAgent.CallFrame>}} */
+WebInspector.InvalidationCause;
+
+/**
+ * @constructor
+ */
+WebInspector.InvalidationTracker = function()
+{
+    this._initializePerFrameState();
+}
+
+WebInspector.InvalidationTracker.prototype = {
+    /**
+     * @param {!WebInspector.InvalidationTrackingEvent} invalidation
+     */
+    addInvalidation: function(invalidation)
+    {
+        this._startNewFrameIfNeeded();
+
+        if (!invalidation.nodeId && !invalidation.paintId) {
+            console.error("Invalidation lacks node information.");
+            console.error(invalidation);
+            return;
+        }
+
+        // PaintInvalidationTracking events provide a paintId and a nodeId which
+        // we can use to update the paintId for all other invalidation tracking
+        // events.
+        var recordTypes = WebInspector.TimelineModel.RecordType;
+        if (invalidation.type === recordTypes.PaintInvalidationTracking && invalidation.nodeId) {
+            var invalidations = this._invalidationsByNodeId[invalidation.nodeId] || [];
+            for (var i = 0; i < invalidations.length; ++i)
+                invalidations[i].paintId = invalidation.paintId;
+
+            // PaintInvalidationTracking is only used for updating paintIds.
+            return;
+        }
+
+        // Suppress StyleInvalidator StyleRecalcInvalidationTracking invalidations because they
+        // will be handled by StyleInvalidatorInvalidationTracking.
+        // FIXME: Investigate if we can remove StyleInvalidator invalidations entirely.
+        if (invalidation.type === recordTypes.StyleRecalcInvalidationTracking && invalidation.cause.reason === "StyleInvalidator")
+            return;
+
+        // Style invalidation events can occur before and during recalc style. didRecalcStyle
+        // handles style invalidations that occur before the recalc style event but we need to
+        // handle style recalc invalidations during recalc style here.
+        var styleRecalcInvalidation = (invalidation.type === recordTypes.ScheduleStyleInvalidationTracking
+            || invalidation.type === recordTypes.StyleInvalidatorInvalidationTracking
+            || invalidation.type === recordTypes.StyleRecalcInvalidationTracking);
+        if (styleRecalcInvalidation) {
+            var duringRecalcStyle = invalidation.startTime && this._lastRecalcStyle
+                && invalidation.startTime >= this._lastRecalcStyle.startTime
+                && invalidation.startTime <= this._lastRecalcStyle.endTime;
+            if (duringRecalcStyle)
+                this._associateWithLastRecalcStyleEvent(invalidation);
+        }
+
+        // Record the invalidation so later events can look it up.
+        if (this._invalidations[invalidation.type])
+            this._invalidations[invalidation.type].push(invalidation);
+        else
+            this._invalidations[invalidation.type] = [ invalidation ];
+        if (invalidation.nodeId) {
+            if (this._invalidationsByNodeId[invalidation.nodeId])
+                this._invalidationsByNodeId[invalidation.nodeId].push(invalidation);
+            else
+                this._invalidationsByNodeId[invalidation.nodeId] = [ invalidation ];
+        }
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} recalcStyleEvent
+     */
+    didRecalcStyle: function(recalcStyleEvent)
+    {
+        this._lastRecalcStyle = recalcStyleEvent;
+        var types = [WebInspector.TimelineModel.RecordType.ScheduleStyleInvalidationTracking,
+                WebInspector.TimelineModel.RecordType.StyleInvalidatorInvalidationTracking,
+                WebInspector.TimelineModel.RecordType.StyleRecalcInvalidationTracking];
+        for (var invalidation of this._invalidationsOfTypes(types))
+            this._associateWithLastRecalcStyleEvent(invalidation);
+    },
+
+    /**
+     * @param {!WebInspector.InvalidationTrackingEvent} invalidation
+     */
+    _associateWithLastRecalcStyleEvent: function(invalidation)
+    {
+        if (invalidation.linkedRecalcStyleEvent)
+            return;
+
+        var recordTypes = WebInspector.TimelineModel.RecordType;
+        var recalcStyleFrameId = this._lastRecalcStyle.args["beginData"]["frame"];
+        if (invalidation.type === recordTypes.StyleInvalidatorInvalidationTracking) {
+            // Instead of calling _addInvalidationToEvent directly, we create synthetic
+            // StyleRecalcInvalidationTracking events which will be added in _addInvalidationToEvent.
+            this._addSyntheticStyleRecalcInvalidations(this._lastRecalcStyle, recalcStyleFrameId, invalidation);
+        } else if (invalidation.type === recordTypes.ScheduleStyleInvalidationTracking) {
+            // ScheduleStyleInvalidationTracking events are only used for adding information to
+            // StyleInvalidatorInvalidationTracking events. See: _addSyntheticStyleRecalcInvalidations.
+        } else {
+            this._addInvalidationToEvent(this._lastRecalcStyle, recalcStyleFrameId, invalidation);
+        }
+
+        invalidation.linkedRecalcStyleEvent = true;
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} event
+     * @param {number} frameId
+     * @param {!WebInspector.InvalidationTrackingEvent} styleInvalidatorInvalidation
+     */
+    _addSyntheticStyleRecalcInvalidations: function(event, frameId, styleInvalidatorInvalidation)
+    {
+        if (!styleInvalidatorInvalidation.invalidationList) {
+            this._addSyntheticStyleRecalcInvalidation(styleInvalidatorInvalidation._tracingEvent, styleInvalidatorInvalidation);
+            return;
+        }
+        if (!styleInvalidatorInvalidation.nodeId) {
+            console.error("Invalidation lacks node information.");
+            console.error(invalidation);
+            return;
+        }
+        for (var i = 0; i < styleInvalidatorInvalidation.invalidationList.length; i++) {
+            var setId = styleInvalidatorInvalidation.invalidationList[i]["id"];
+            var lastScheduleStyleRecalculation;
+            var nodeInvalidations = this._invalidationsByNodeId[styleInvalidatorInvalidation.nodeId] || [];
+            for (var j = 0; j < nodeInvalidations.length; j++) {
+                var invalidation = nodeInvalidations[j];
+                if (invalidation.frame !== frameId || invalidation.invalidationSet !== setId || invalidation.type !== WebInspector.TimelineModel.RecordType.ScheduleStyleInvalidationTracking)
+                    continue;
+                lastScheduleStyleRecalculation = invalidation;
+            }
+            if (!lastScheduleStyleRecalculation) {
+                console.error("Failed to lookup the event that scheduled a style invalidator invalidation.");
+                continue;
+            }
+            this._addSyntheticStyleRecalcInvalidation(lastScheduleStyleRecalculation._tracingEvent, styleInvalidatorInvalidation);
+        }
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} baseEvent
+     * @param {!WebInspector.InvalidationTrackingEvent} styleInvalidatorInvalidation
+     */
+    _addSyntheticStyleRecalcInvalidation: function(baseEvent, styleInvalidatorInvalidation)
+    {
+        var invalidation = new WebInspector.InvalidationTrackingEvent(baseEvent);
+        invalidation.type = WebInspector.TimelineModel.RecordType.StyleRecalcInvalidationTracking;
+        invalidation.synthetic = true;
+        if (styleInvalidatorInvalidation.cause.reason)
+            invalidation.cause.reason = styleInvalidatorInvalidation.cause.reason;
+        if (styleInvalidatorInvalidation.selectorPart)
+            invalidation.selectorPart = styleInvalidatorInvalidation.selectorPart;
+
+        this.addInvalidation(invalidation);
+        if (!invalidation.linkedRecalcStyleEvent)
+            this._associateWithLastRecalcStyleEvent(invalidation);
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} layoutEvent
+     */
+    didLayout: function(layoutEvent)
+    {
+        var layoutFrameId = layoutEvent.args["beginData"]["frame"];
+        for (var invalidation of this._invalidationsOfTypes([WebInspector.TimelineModel.RecordType.LayoutInvalidationTracking])) {
+            if (invalidation.linkedLayoutEvent)
+                continue;
+            this._addInvalidationToEvent(layoutEvent, layoutFrameId, invalidation);
+            invalidation.linkedLayoutEvent = true;
+        }
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} paintEvent
+     */
+    didPaint: function(paintEvent)
+    {
+        this._didPaint = true;
+
+        // If a paint doesn't have a corresponding graphics layer id, it paints
+        // into its parent so add an effectivePaintId to these events.
+        var layerId = paintEvent.args["data"]["layerId"];
+        if (layerId)
+            this._lastPaintWithLayer = paintEvent;
+        // Quietly discard top-level paints without layerId, as these are likely
+        // to come from overlay.
+        if (!this._lastPaintWithLayer)
+            return;
+
+        var effectivePaintId = this._lastPaintWithLayer.args["data"]["nodeId"];
+        var paintFrameId = paintEvent.args["data"]["frame"];
+        var types = [WebInspector.TimelineModel.RecordType.StyleRecalcInvalidationTracking,
+            WebInspector.TimelineModel.RecordType.LayoutInvalidationTracking,
+            WebInspector.TimelineModel.RecordType.PaintInvalidationTracking,
+            WebInspector.TimelineModel.RecordType.ScrollInvalidationTracking];
+        for (var invalidation of this._invalidationsOfTypes(types)) {
+            if (invalidation.paintId === effectivePaintId)
+                this._addInvalidationToEvent(paintEvent, paintFrameId, invalidation);
+        }
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} event
+     * @param {number} eventFrameId
+     * @param {!WebInspector.InvalidationTrackingEvent} invalidation
+     */
+    _addInvalidationToEvent: function(event, eventFrameId, invalidation)
+    {
+        if (eventFrameId !== invalidation.frame)
+            return;
+        if (!event.invalidationTrackingEvents)
+            event.invalidationTrackingEvents = [ invalidation ];
+        else
+            event.invalidationTrackingEvents.push(invalidation);
+    },
+
+    /**
+     * @param {!Array.<string>=} types
+     * @return {!Iterator.<!WebInspector.InvalidationTrackingEvent>}
+     */
+    _invalidationsOfTypes: function(types)
+    {
+        var invalidations = this._invalidations;
+        if (!types)
+            types = Object.keys(invalidations);
+        function* generator()
+        {
+            for (var i = 0; i < types.length; ++i) {
+                var invalidationList = invalidations[types[i]] || [];
+                for (var j = 0; j < invalidationList.length; ++j)
+                    yield invalidationList[j];
+            }
+        }
+        return generator();
+    },
+
+    _startNewFrameIfNeeded: function()
+    {
+        if (!this._didPaint)
+            return;
+
+        this._initializePerFrameState();
+    },
+
+    _initializePerFrameState: function()
+    {
+        /** @type {!Object.<string, !Array.<!WebInspector.InvalidationTrackingEvent>>} */
+        this._invalidations = {};
+        /** @type {!Object.<number, !Array.<!WebInspector.InvalidationTrackingEvent>>} */
+        this._invalidationsByNodeId = {};
+
+        this._lastRecalcStyle = undefined;
+        this._lastPaintWithLayer = undefined;
+        this._didPaint = false;
+    }
+}
+
+/**
+ * @constructor
+ */
+WebInspector.TimelineAsyncEventTracker = function()
+{
+    WebInspector.TimelineAsyncEventTracker._initialize();
+    /** @type {!Map<!WebInspector.TimelineModel.RecordType, !Map<string, !WebInspector.TracingModel.Event>>} */
+    this._initiatorByType = new Map();
+    for (var initiator of WebInspector.TimelineAsyncEventTracker._asyncEvents.keys())
+        this._initiatorByType.set(initiator, new Map());
+}
+
+WebInspector.TimelineAsyncEventTracker._initialize = function()
+{
+    if (WebInspector.TimelineAsyncEventTracker._asyncEvents)
+        return;
+    var events = new Map();
+    var type = WebInspector.TimelineModel.RecordType;
+
+    events.set(type.TimerInstall, {causes: [type.TimerFire], joinBy: "timerId"});
+    events.set(type.ResourceSendRequest, {causes: [type.ResourceReceiveResponse, type.ResourceReceivedData, type.ResourceFinish], joinBy: "requestId"});
+    events.set(type.RequestAnimationFrame, {causes: [type.FireAnimationFrame], joinBy: "id"});
+    events.set(type.RequestIdleCallback, {causes: [type.FireIdleCallback], joinBy: "id"});
+    events.set(type.WebSocketCreate, {causes: [type.WebSocketSendHandshakeRequest, type.WebSocketReceiveHandshakeResponse, type.WebSocketDestroy], joinBy: "identifier"});
+
+    WebInspector.TimelineAsyncEventTracker._asyncEvents = events;
+    /** @type {!Map<!WebInspector.TimelineModel.RecordType, !WebInspector.TimelineModel.RecordType>} */
+    WebInspector.TimelineAsyncEventTracker._typeToInitiator = new Map();
+    for (var entry of events) {
+        var types = entry[1].causes;
+        for (type of types)
+            WebInspector.TimelineAsyncEventTracker._typeToInitiator.set(type, entry[0]);
+    }
+}
+
+WebInspector.TimelineAsyncEventTracker.prototype = {
+    /**
+     * @param {!WebInspector.TracingModel.Event} event
+     */
+    processEvent: function(event)
+    {
+        var initiatorType = WebInspector.TimelineAsyncEventTracker._typeToInitiator.get(/** @type {!WebInspector.TimelineModel.RecordType} */ (event.name));
+        var isInitiator = !initiatorType;
+        if (!initiatorType)
+            initiatorType = /** @type {!WebInspector.TimelineModel.RecordType} */ (event.name);
+        var initiatorInfo = WebInspector.TimelineAsyncEventTracker._asyncEvents.get(initiatorType);
+        if (!initiatorInfo)
+            return;
+        var id = event.args["data"][initiatorInfo.joinBy];
+        if (!id)
+            return;
+        /** @type {!Map<string, !WebInspector.TracingModel.Event>|undefined} */
+        var initiatorMap = this._initiatorByType.get(initiatorType);
+        if (isInitiator)
+            initiatorMap.set(id, event);
+        else
+            event.initiator = initiatorMap.get(id) || null;
+    }
+}
+
+},{}],260:[function(require,module,exports){
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+WebInspector.TimelineProfileTree = { };
+
+/**
+ * @constructor
+ */
+WebInspector.TimelineProfileTree.Node = function()
+{
+    /** @type {number} */
+    this.totalTime;
+    /** @type {number} */
+    this.selfTime;
+    /** @type {string} */
+    this.id;
+    /** @type {!WebInspector.TracingModel.Event} */
+    this.event;
+    /** @type {?Map<string|symbol,!WebInspector.TimelineProfileTree.Node>} */
+    this.children;
+    /** @type {?WebInspector.TimelineProfileTree.Node} */
+    this.parent;
+    this._isGroupNode = false;
+}
+
+WebInspector.TimelineProfileTree.Node.prototype = {
+    /**
+     * @return {boolean}
+     */
+    isGroupNode: function()
+    {
+        return this._isGroupNode;
+    }
+}
+
+/**
+ * @param {!Array<!WebInspector.TracingModel.Event>} events
+ * @param {!Array<!WebInspector.TimelineModel.Filter>} filters
+ * @param {number} startTime
+ * @param {number} endTime
+ * @param {function(!WebInspector.TracingModel.Event):(string|symbol)=} eventIdCallback
+ * @return {!WebInspector.TimelineProfileTree.Node}
+ */
+WebInspector.TimelineProfileTree.buildTopDown = function(events, filters, startTime, endTime, eventIdCallback)
+{
+    // Temporarily deposit a big enough value that exceeds the max recording time.
+    var /** @const */ initialTime = 1e7;
+    var root = new WebInspector.TimelineProfileTree.Node();
+    root.totalTime = initialTime;
+    root.selfTime = initialTime;
+    root.children = /** @type {!Map<string, !WebInspector.TimelineProfileTree.Node>} */ (new Map());
+    var parent = root;
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} e
+     */
+    function onStartEvent(e)
+    {
+        if (!WebInspector.TimelineModel.isVisible(filters, e))
+            return;
+        var time = e.endTime ? Math.min(endTime, e.endTime) - Math.max(startTime, e.startTime) : 0;
+        var id = eventIdCallback ? eventIdCallback(e) : Symbol("uniqueEventId");
+        if (!parent.children)
+            parent.children = /** @type {!Map<string,!WebInspector.TimelineProfileTree.Node>} */ (new Map());
+        var node = parent.children.get(id);
+        if (node) {
+            node.selfTime += time;
+            node.totalTime += time;
+        } else {
+            node = new WebInspector.TimelineProfileTree.Node();
+            node.totalTime = time;
+            node.selfTime = time;
+            node.parent = parent;
+            node.id = id;
+            node.event = e;
+            parent.children.set(id, node);
+        }
+        parent.selfTime -= time;
+        if (parent.selfTime < 0) {
+            console.log("Error: Negative self of " + parent.selfTime, e);
+            parent.selfTime = 0;
+        }
+        if (e.endTime)
+            parent = node;
+    }
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} e
+     */
+    function onEndEvent(e)
+    {
+        if (!WebInspector.TimelineModel.isVisible(filters, e))
+            return;
+        parent = parent.parent;
+    }
+
+    var instantEventCallback = eventIdCallback ? undefined : onStartEvent; // Ignore instant events when aggregating.
+    WebInspector.TimelineModel.forEachEvent(events, onStartEvent, onEndEvent, instantEventCallback, startTime, endTime);
+    root.totalTime -= root.selfTime;
+    root.selfTime = 0;
+    return root;
+}
+
+/**
+ * @param {!WebInspector.TimelineProfileTree.Node} topDownTree
+ * @param {?function(!WebInspector.TimelineProfileTree.Node):!WebInspector.TimelineProfileTree.Node=} groupingCallback
+ * @return {!WebInspector.TimelineProfileTree.Node}
+ */
+WebInspector.TimelineProfileTree.buildBottomUp = function(topDownTree, groupingCallback)
+{
+    var buRoot = new WebInspector.TimelineProfileTree.Node();
+    buRoot.selfTime = 0;
+    buRoot.totalTime = 0;
+    /** @type {!Map<string, !WebInspector.TimelineProfileTree.Node>} */
+    buRoot.children = new Map();
+    var nodesOnStack = /** @type {!Set<string>} */ (new Set());
+    if (topDownTree.children)
+        topDownTree.children.forEach(processNode);
+    buRoot.totalTime = topDownTree.totalTime;
+
+    /**
+     * @param {!WebInspector.TimelineProfileTree.Node} tdNode
+     */
+    function processNode(tdNode)
+    {
+        var buParent = groupingCallback && groupingCallback(tdNode) || buRoot;
+        if (buParent !== buRoot) {
+            buRoot.children.set(buParent.id, buParent);
+            buParent.parent = buRoot;
+        }
+        appendNode(tdNode, buParent);
+        var hadNode = nodesOnStack.has(tdNode.id);
+        if (!hadNode)
+            nodesOnStack.add(tdNode.id);
+        if (tdNode.children)
+            tdNode.children.forEach(processNode);
+        if (!hadNode)
+            nodesOnStack.delete(tdNode.id);
+    }
+
+    /**
+     * @param {!WebInspector.TimelineProfileTree.Node} tdNode
+     * @param {!WebInspector.TimelineProfileTree.Node} buParent
+     */
+    function appendNode(tdNode, buParent)
+    {
+        var selfTime = tdNode.selfTime;
+        var totalTime = tdNode.totalTime;
+        buParent.selfTime += selfTime;
+        buParent.totalTime += selfTime;
+        while (tdNode.parent) {
+            if (!buParent.children)
+                buParent.children = /** @type {!Map<string,!WebInspector.TimelineProfileTree.Node>} */ (new Map());
+            var id = tdNode.id;
+            var buNode = buParent.children.get(id);
+            if (!buNode) {
+                buNode = new WebInspector.TimelineProfileTree.Node();
+                buNode.selfTime = selfTime;
+                buNode.totalTime = totalTime;
+                buNode.event = tdNode.event;
+                buNode.id = id;
+                buNode.parent = buParent;
+                buParent.children.set(id, buNode);
+            } else {
+                buNode.selfTime += selfTime;
+                if (!nodesOnStack.has(id))
+                    buNode.totalTime += totalTime;
+            }
+            tdNode = tdNode.parent;
+            buParent = buNode;
+        }
+    }
+
+    // Purge zero self time nodes.
+    var rootChildren = buRoot.children;
+    for (var item of rootChildren.entries()) {
+        if (item[1].selfTime === 0)
+            rootChildren.delete(/** @type {string} */(item[0]));
+    }
+
+    return buRoot;
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @return {?string}
+ */
+WebInspector.TimelineProfileTree.eventURL = function(event)
+{
+    var data = event.args["data"] || event.args["beginData"];
+    if (data && data["url"])
+        return data["url"];
+    var frame = WebInspector.TimelineProfileTree.eventStackFrame(event);
+    while (frame) {
+        var url = frame["url"];
+        if (url)
+            return url;
+        frame = frame.parent;
+    }
+    return null;
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @return {?RuntimeAgent.CallFrame}
+ */
+WebInspector.TimelineProfileTree.eventStackFrame = function(event)
+{
+    if (event.name === WebInspector.TimelineModel.RecordType.JSFrame)
+        return /** @type {?RuntimeAgent.CallFrame} */ (event.args["data"] || null);
+    var topFrame = event.stackTrace && event.stackTrace[0];
+    if (topFrame)
+        return /** @type {!RuntimeAgent.CallFrame} */ (topFrame);
+    var initiator = event.initiator;
+    return /** @type {?RuntimeAgent.CallFrame} */ (initiator && initiator.stackTrace && initiator.stackTrace[0] || null);
+}
+
+/**
+ * @constructor
+ * @param {function(!WebInspector.TracingModel.Event):string} titleMapper
+ * @param {function(!WebInspector.TracingModel.Event):string} categoryMapper
+ */
+WebInspector.TimelineAggregator = function(titleMapper, categoryMapper)
+{
+    this._titleMapper = titleMapper;
+    this._categoryMapper = categoryMapper;
+    /** @type {!Map<string, !WebInspector.TimelineProfileTree.Node>} */
+    this._groupNodes = new Map();
+}
+
+/**
+ * @enum {string}
+ */
+WebInspector.TimelineAggregator.GroupBy = {
+    None: "None",
+    EventName: "EventName",
+    Category: "Category",
+    Domain: "Domain",
+    Subdomain: "Subdomain",
+    URL: "URL"
+}
+
+/**
+ * @param {!WebInspector.TracingModel.Event} event
+ * @return {string}
+ */
+WebInspector.TimelineAggregator.eventId = function(event)
+{
+    if (event.name === WebInspector.TimelineModel.RecordType.JSFrame) {
+        var data = event.args["data"];
+        return "f:" + data["functionName"] + "@" + (data["scriptId"] || data["url"] || "");
+    }
+    return event.name + ":@" + WebInspector.TimelineProfileTree.eventURL(event);
+}
+
+WebInspector.TimelineAggregator._extensionInternalPrefix = "extensions::";
+WebInspector.TimelineAggregator._groupNodeFlag = Symbol("groupNode");
+
+/**
+ * @param {string} url
+ * @return {boolean}
+ */
+WebInspector.TimelineAggregator.isExtensionInternalURL = function(url)
+{
+    return url.startsWith(WebInspector.TimelineAggregator._extensionInternalPrefix);
+}
+
+WebInspector.TimelineAggregator.prototype = {
+    /**
+     * @param {!WebInspector.TimelineAggregator.GroupBy} groupBy
+     * @return {?function(!WebInspector.TimelineProfileTree.Node):!WebInspector.TimelineProfileTree.Node}
+     */
+    groupFunction: function(groupBy)
+    {
+        var idMapper = this._nodeToGroupIdFunction(groupBy);
+        return idMapper && this._nodeToGroupNode.bind(this, idMapper);
+    },
+
+    /**
+     * @param {!WebInspector.TimelineProfileTree.Node} root
+     * @param {!WebInspector.TimelineAggregator.GroupBy} groupBy
+     * @return {!WebInspector.TimelineProfileTree.Node}
+     */
+    performGrouping: function(root, groupBy)
+    {
+        var nodeMapper = this.groupFunction(groupBy);
+        if (!nodeMapper)
+            return root;
+        for (var node of root.children.values()) {
+            var groupNode = nodeMapper(node);
+            groupNode.parent = root;
+            groupNode.selfTime += node.selfTime;
+            groupNode.totalTime += node.totalTime;
+            groupNode.children.set(node.id, node);
+            node.parent = root;
+        }
+        root.children = this._groupNodes;
+        return root;
+    },
+
+    /**
+     * @param {!WebInspector.TimelineAggregator.GroupBy} groupBy
+     * @return {?function(!WebInspector.TimelineProfileTree.Node):string}
+     */
+    _nodeToGroupIdFunction: function(groupBy)
+    {
+        /**
+         * @param {!WebInspector.TimelineProfileTree.Node} node
+         * @return {string}
+         */
+        function groupByURL(node)
+        {
+            return WebInspector.TimelineProfileTree.eventURL(node.event) || "";
+        }
+
+        /**
+         * @param {boolean} groupSubdomains
+         * @param {!WebInspector.TimelineProfileTree.Node} node
+         * @return {string}
+         */
+        function groupByDomain(groupSubdomains, node)
+        {
+            var url = WebInspector.TimelineProfileTree.eventURL(node.event) || "";
+            if (WebInspector.TimelineAggregator.isExtensionInternalURL(url))
+                return WebInspector.TimelineAggregator._extensionInternalPrefix;
+            var parsedURL = url.asParsedURL();
+            if (!parsedURL)
+                return "";
+            if (parsedURL.scheme === "chrome-extension")
+                return parsedURL.scheme + "://" + parsedURL.host;
+            if (!groupSubdomains)
+                return parsedURL.host;
+            if (/^[.0-9]+$/.test(parsedURL.host))
+                return parsedURL.host;
+            var domainMatch = /([^.]*\.)?[^.]*$/.exec(parsedURL.host);
+            return domainMatch && domainMatch[0] || "";
+        }
+
+        switch (groupBy) {
+        case WebInspector.TimelineAggregator.GroupBy.None: return null;
+        case WebInspector.TimelineAggregator.GroupBy.EventName: return node => node.event ? this._titleMapper(node.event) : "";
+        case WebInspector.TimelineAggregator.GroupBy.Category: return node => node.event ? this._categoryMapper(node.event) : "";
+        case WebInspector.TimelineAggregator.GroupBy.Subdomain: return groupByDomain.bind(null, false);
+        case WebInspector.TimelineAggregator.GroupBy.Domain: return groupByDomain.bind(null, true);
+        case WebInspector.TimelineAggregator.GroupBy.URL: return groupByURL;
+        default: return null;
+        }
+    },
+
+    /**
+     * @param {string} id
+     * @param {!WebInspector.TracingModel.Event} event
+     * @return {!WebInspector.TimelineProfileTree.Node}
+     */
+    _buildGroupNode: function(id, event)
+    {
+        var groupNode = new WebInspector.TimelineProfileTree.Node();
+        groupNode.id = id;
+        groupNode.selfTime = 0;
+        groupNode.totalTime = 0;
+        groupNode.children = new Map();
+        groupNode.event = event;
+        groupNode._isGroupNode = true;
+        this._groupNodes.set(id, groupNode);
+        return groupNode;
+    },
+
+    /**
+     * @param {function(!WebInspector.TimelineProfileTree.Node):string} nodeToGroupId
+     * @param {!WebInspector.TimelineProfileTree.Node} node
+     * @return {!WebInspector.TimelineProfileTree.Node}
+     */
+    _nodeToGroupNode: function(nodeToGroupId, node)
+    {
+        var id = nodeToGroupId(node);
+        return this._groupNodes.get(id) || this._buildGroupNode(id, node.event);
+    },
+}
+
 },{}],261:[function(require,module,exports){
-'use strict';
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
 
-exports.__esModule=true;
-exports.checkRevision=checkRevision;
-exports.template=template;
-exports.wrapProgram=wrapProgram;
-exports.resolvePartial=resolvePartial;
-exports.invokePartial=invokePartial;
-exports.noop=noop;
-
-
-function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{'default':obj};}
-
-
-
-function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj['default']=obj;return newObj;}}
-
-var _utils=require('./utils');
-
-var Utils=_interopRequireWildcard(_utils);
-
-var _exception=require('./exception');
-
-var _exception2=_interopRequireDefault(_exception);
-
-var _base=require('./base');
-
-function checkRevision(compilerInfo){
-var compilerRevision=compilerInfo&&compilerInfo[0]||1,
-currentRevision=_base.COMPILER_REVISION;
-
-if(compilerRevision!==currentRevision){
-if(compilerRevision<currentRevision){
-var runtimeVersions=_base.REVISION_CHANGES[currentRevision],
-compilerVersions=_base.REVISION_CHANGES[compilerRevision];
-throw new _exception2['default']('Template was precompiled with an older version of Handlebars than the current runtime. '+'Please update your precompiler to a newer version ('+runtimeVersions+') or downgrade your runtime to an older version ('+compilerVersions+').');
-}else{
-
-throw new _exception2['default']('Template was precompiled with a newer version of Handlebars than the current runtime. '+'Please update your runtime to a newer version ('+compilerInfo[1]+').');
-}
-}
-}
-
-function template(templateSpec,env){
-
-if(!env){
-throw new _exception2['default']('No environment passed to template');
-}
-if(!templateSpec||!templateSpec.main){
-throw new _exception2['default']('Unknown template object: '+typeof templateSpec);
-}
-
-templateSpec.main.decorator=templateSpec.main_d;
-
-
-
-env.VM.checkRevision(templateSpec.compiler);
-
-function invokePartialWrapper(partial,context,options){
-if(options.hash){
-context=Utils.extend({},context,options.hash);
-if(options.ids){
-options.ids[0]=true;
-}
-}
-
-partial=env.VM.resolvePartial.call(this,partial,context,options);
-var result=env.VM.invokePartial.call(this,partial,context,options);
-
-if(result==null&&env.compile){
-options.partials[options.name]=env.compile(partial,templateSpec.compilerOptions,env);
-result=options.partials[options.name](context,options);
-}
-if(result!=null){
-if(options.indent){
-var lines=result.split('\n');
-for(var i=0,l=lines.length;i<l;i++){
-if(!lines[i]&&i+1===l){
-break;
-}
-
-lines[i]=options.indent+lines[i];
-}
-result=lines.join('\n');
-}
-return result;
-}else{
-throw new _exception2['default']('The partial '+options.name+' could not be compiled when running in runtime-only mode');
-}
-}
-
-
-var container={
-strict:function strict(obj,name){
-if(!(name in obj)){
-throw new _exception2['default']('"'+name+'" not defined in '+obj);
-}
-return obj[name];
-},
-lookup:function lookup(depths,name){
-var len=depths.length;
-for(var i=0;i<len;i++){
-if(depths[i]&&depths[i][name]!=null){
-return depths[i][name];
-}
-}
-},
-lambda:function lambda(current,context){
-return typeof current==='function'?current.call(context):current;
-},
-
-escapeExpression:Utils.escapeExpression,
-invokePartial:invokePartialWrapper,
-
-fn:function fn(i){
-var ret=templateSpec[i];
-ret.decorator=templateSpec[i+'_d'];
-return ret;
-},
-
-programs:[],
-program:function program(i,data,declaredBlockParams,blockParams,depths){
-var programWrapper=this.programs[i],
-fn=this.fn(i);
-if(data||depths||blockParams||declaredBlockParams){
-programWrapper=wrapProgram(this,i,fn,data,declaredBlockParams,blockParams,depths);
-}else if(!programWrapper){
-programWrapper=this.programs[i]=wrapProgram(this,i,fn);
-}
-return programWrapper;
-},
-
-data:function data(value,depth){
-while(value&&depth--){
-value=value._parent;
-}
-return value;
-},
-merge:function merge(param,common){
-var obj=param||common;
-
-if(param&&common&&param!==common){
-obj=Utils.extend({},common,param);
-}
-
-return obj;
-},
-
-noop:env.VM.noop,
-compilerInfo:templateSpec.compiler};
-
-
-function ret(context){
-var options=arguments.length<=1||arguments[1]===undefined?{}:arguments[1];
-
-var data=options.data;
-
-ret._setup(options);
-if(!options.partial&&templateSpec.useData){
-data=initData(context,data);
-}
-var depths=undefined,
-blockParams=templateSpec.useBlockParams?[]:undefined;
-if(templateSpec.useDepths){
-if(options.depths){
-depths=context!==options.depths[0]?[context].concat(options.depths):options.depths;
-}else{
-depths=[context];
-}
-}
-
-function main(context){
-return''+templateSpec.main(container,context,container.helpers,container.partials,data,blockParams,depths);
-}
-main=executeDecorators(templateSpec.main,main,container,options.depths||[],data,blockParams);
-return main(context,options);
-}
-ret.isTop=true;
-
-ret._setup=function(options){
-if(!options.partial){
-container.helpers=container.merge(options.helpers,env.helpers);
-
-if(templateSpec.usePartial){
-container.partials=container.merge(options.partials,env.partials);
-}
-if(templateSpec.usePartial||templateSpec.useDecorators){
-container.decorators=container.merge(options.decorators,env.decorators);
-}
-}else{
-container.helpers=options.helpers;
-container.partials=options.partials;
-container.decorators=options.decorators;
-}
-};
-
-ret._child=function(i,data,blockParams,depths){
-if(templateSpec.useBlockParams&&!blockParams){
-throw new _exception2['default']('must pass block params');
-}
-if(templateSpec.useDepths&&!depths){
-throw new _exception2['default']('must pass parent depths');
-}
-
-return wrapProgram(container,i,templateSpec[i],data,0,blockParams,depths);
-};
-return ret;
-}
-
-function wrapProgram(container,i,fn,data,declaredBlockParams,blockParams,depths){
-function prog(context){
-var options=arguments.length<=1||arguments[1]===undefined?{}:arguments[1];
-
-var currentDepths=depths;
-if(depths&&context!==depths[0]){
-currentDepths=[context].concat(depths);
-}
-
-return fn(container,context,container.helpers,container.partials,options.data||data,blockParams&&[options.blockParams].concat(blockParams),currentDepths);
-}
-
-prog=executeDecorators(fn,prog,container,depths,data,blockParams);
-
-prog.program=i;
-prog.depth=depths?depths.length:0;
-prog.blockParams=declaredBlockParams||0;
-return prog;
-}
-
-function resolvePartial(partial,context,options){
-if(!partial){
-if(options.name==='@partial-block'){
-partial=options.data['partial-block'];
-}else{
-partial=options.partials[options.name];
-}
-}else if(!partial.call&&!options.name){
-
-options.name=partial;
-partial=options.partials[partial];
-}
-return partial;
-}
-
-function invokePartial(partial,context,options){
-options.partial=true;
-if(options.ids){
-options.data.contextPath=options.ids[0]||options.data.contextPath;
-}
-
-var partialBlock=undefined;
-if(options.fn&&options.fn!==noop){
-options.data=_base.createFrame(options.data);
-partialBlock=options.data['partial-block']=options.fn;
-
-if(partialBlock.partials){
-options.partials=Utils.extend({},options.partials,partialBlock.partials);
-}
-}
-
-if(partial===undefined&&partialBlock){
-partial=partialBlock;
-}
-
-if(partial===undefined){
-throw new _exception2['default']('The partial '+options.name+' could not be found');
-}else if(partial instanceof Function){
-return partial(context,options);
-}
-}
-
-function noop(){
-return'';
-}
-
-function initData(context,data){
-if(!data||!('root'in data)){
-data=data?_base.createFrame(data):{};
-data.root=context;
-}
-return data;
-}
-
-function executeDecorators(fn,prog,container,depths,data,blockParams){
-if(fn.decorator){
-var props={};
-prog=fn.decorator(prog,props,container,depths&&depths[0],data,blockParams,depths);
-Utils.extend(prog,props);
-}
-return prog;
-}
-
-
-},{"./base":247,"./exception":250,"./utils":263}],262:[function(require,module,exports){
-
-'use strict';
-
-exports.__esModule=true;
-function SafeString(string){
-this.string=string;
-}
-
-SafeString.prototype.toString=SafeString.prototype.toHTML=function(){
-return''+this.string;
-};
-
-exports['default']=SafeString;
-module.exports=exports['default'];
-
-
-},{}],263:[function(require,module,exports){
-'use strict';
-
-exports.__esModule=true;
-exports.extend=extend;
-exports.indexOf=indexOf;
-exports.escapeExpression=escapeExpression;
-exports.isEmpty=isEmpty;
-exports.createFrame=createFrame;
-exports.blockParams=blockParams;
-exports.appendContextPath=appendContextPath;
-var escape={
-'&':'&amp;',
-'<':'&lt;',
-'>':'&gt;',
-'"':'&quot;',
-"'":'&#x27;',
-'`':'&#x60;',
-'=':'&#x3D;'};
-
-
-var badChars=/[&<>"'`=]/g,
-possible=/[&<>"'`=]/;
-
-function escapeChar(chr){
-return escape[chr];
-}
-
-function extend(obj){
-for(var i=1;i<arguments.length;i++){
-for(var key in arguments[i]){
-if(Object.prototype.hasOwnProperty.call(arguments[i],key)){
-obj[key]=arguments[i][key];
-}
-}
-}
-
-return obj;
-}
-
-var toString=Object.prototype.toString;
-
-exports.toString=toString;
-
-
-
-var isFunction=function isFunction(value){
-return typeof value==='function';
-};
-
-
-if(isFunction(/x/)){
-exports.isFunction=isFunction=function(value){
-return typeof value==='function'&&toString.call(value)==='[object Function]';
-};
-}
-exports.isFunction=isFunction;
-
-
-
-
-var isArray=Array.isArray||function(value){
-return value&&typeof value==='object'?toString.call(value)==='[object Array]':false;
-};
-
-exports.isArray=isArray;
-
-
-function indexOf(array,value){
-for(var i=0,len=array.length;i<len;i++){
-if(array[i]===value){
-return i;
-}
-}
-return-1;
-}
-
-function escapeExpression(string){
-if(typeof string!=='string'){
-
-if(string&&string.toHTML){
-return string.toHTML();
-}else if(string==null){
-return'';
-}else if(!string){
-return string+'';
-}
-
-
-
-
-string=''+string;
-}
-
-if(!possible.test(string)){
-return string;
-}
-return string.replace(badChars,escapeChar);
-}
-
-function isEmpty(value){
-if(!value&&value!==0){
-return true;
-}else if(isArray(value)&&value.length===0){
-return true;
-}else{
-return false;
-}
-}
-
-function createFrame(object){
-var frame=extend({},object);
-frame._parent=object;
-return frame;
-}
-
-function blockParams(params,ids){
-params.path=ids;
-return params;
-}
-
-function appendContextPath(contextPath,id){
-return(contextPath?contextPath+'.':'')+id;
-}
-
-
-},{}],264:[function(require,module,exports){
-
-
-module.exports=require('./dist/cjs/handlebars.runtime')['default'];
-
-},{"./dist/cjs/handlebars.runtime":246}],265:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-var ImageSSIM;
-(function(ImageSSIM){
-'use strict';
-
-
-
-(function(Channels){
-Channels[Channels["Grey"]=1]="Grey";
-Channels[Channels["GreyAlpha"]=2]="GreyAlpha";
-Channels[Channels["RGB"]=3]="RGB";
-Channels[Channels["RGBAlpha"]=4]="RGBAlpha";
-})(ImageSSIM.Channels||(ImageSSIM.Channels={}));
-var Channels=ImageSSIM.Channels;
-
-
-
-
-function compare(image1,image2,windowSize,K1,K2,luminance,bitsPerComponent){
-if(windowSize===void 0){windowSize=8;}
-if(K1===void 0){K1=0.01;}
-if(K2===void 0){K2=0.03;}
-if(luminance===void 0){luminance=true;}
-if(bitsPerComponent===void 0){bitsPerComponent=8;}
-if(image1.width!==image2.width||image1.height!==image2.height){
-throw new Error('Images have different sizes!');
-}
-
-var L=(1<<bitsPerComponent)-1;
-
-var c1=Math.pow(K1*L,2),c2=Math.pow(K2*L,2),numWindows=0,mssim=0.0;
-var mcs=0.0;
-function iteration(lumaValues1,lumaValues2,averageLumaValue1,averageLumaValue2){
-
-var sigxy,sigsqx,sigsqy;
-sigxy=sigsqx=sigsqy=0.0;
-for(var i=0;i<lumaValues1.length;i++){
-sigsqx+=Math.pow(lumaValues1[i]-averageLumaValue1,2);
-sigsqy+=Math.pow(lumaValues2[i]-averageLumaValue2,2);
-sigxy+=(lumaValues1[i]-averageLumaValue1)*(lumaValues2[i]-averageLumaValue2);
-}
-var numPixelsInWin=lumaValues1.length-1;
-sigsqx/=numPixelsInWin;
-sigsqy/=numPixelsInWin;
-sigxy/=numPixelsInWin;
-
-var numerator=(2*averageLumaValue1*averageLumaValue2+c1)*(2*sigxy+c2);
-var denominator=(Math.pow(averageLumaValue1,2)+Math.pow(averageLumaValue2,2)+c1)*(sigsqx+sigsqy+c2);
-mssim+=numerator/denominator;
-mcs+=(2*sigxy+c2)/(sigsqx+sigsqy+c2);
-numWindows++;
-}
-
-Internals._iterate(image1,image2,windowSize,luminance,iteration);
-return{ssim:mssim/numWindows,mcs:mcs/numWindows};
-}
-ImageSSIM.compare=compare;
-
-
-
-var Internals;
-(function(Internals){
-function _iterate(image1,image2,windowSize,luminance,callback){
-var width=image1.width,height=image1.height;
-for(var y=0;y<height;y+=windowSize){
-for(var x=0;x<width;x+=windowSize){
-
-var windowWidth=Math.min(windowSize,width-x),windowHeight=Math.min(windowSize,height-y);
-var lumaValues1=_lumaValuesForWindow(image1,x,y,windowWidth,windowHeight,luminance),lumaValues2=_lumaValuesForWindow(image2,x,y,windowWidth,windowHeight,luminance),averageLuma1=_averageLuma(lumaValues1),averageLuma2=_averageLuma(lumaValues2);
-callback(lumaValues1,lumaValues2,averageLuma1,averageLuma2);
-}
-}
-}
-Internals._iterate=_iterate;
-function _lumaValuesForWindow(image,x,y,width,height,luminance){
-var array=image.data,lumaValues=new Float32Array(new ArrayBuffer(width*height*4)),counter=0;
-var maxj=y+height;
-for(var j=y;j<maxj;j++){
-var offset=j*image.width;
-var i=(offset+x)*image.channels;
-var maxi=(offset+x+width)*image.channels;
-switch(image.channels){
-case 1:
-while(i<maxi){
-
-lumaValues[counter++]=array[i++];
-}
-break;
-case 2:
-while(i<maxi){
-lumaValues[counter++]=array[i++]*(array[i++]/255);
-}
-break;
-case 3:
-if(luminance){
-while(i<maxi){
-lumaValues[counter++]=array[i++]*0.212655+array[i++]*0.715158+array[i++]*0.072187;
-}
-}else
+/**
+ * @constructor
+ * @extends {WebInspector.ViewportDataGrid}
+ * @param {!Array.<!WebInspector.DataGrid.ColumnDescriptor>} columnsArray
+ * @param {function(!WebInspector.DataGridNode, string, string, string)=} editCallback
+ * @param {function(!WebInspector.DataGridNode)=} deleteCallback
+ * @param {function()=} refreshCallback
+ * @param {function(!WebInspector.ContextMenu, !WebInspector.DataGridNode)=} contextMenuCallback
+ */
+WebInspector.SortableDataGrid = function(columnsArray, editCallback, deleteCallback, refreshCallback, contextMenuCallback)
 {
-while(i<maxi){
-lumaValues[counter++]=array[i++]+array[i++]+array[i++];
+    WebInspector.ViewportDataGrid.call(this, columnsArray, editCallback, deleteCallback, refreshCallback, contextMenuCallback);
+    /** @type {!WebInspector.SortableDataGrid.NodeComparator} */
+    this._sortingFunction = WebInspector.SortableDataGrid.TrivialComparator;
+    this.setRootNode(new WebInspector.SortableDataGridNode());
 }
-}
-break;
-case 4:
-if(luminance){
-while(i<maxi){
-lumaValues[counter++]=(array[i++]*0.212655+array[i++]*0.715158+array[i++]*0.072187)*(array[i++]/255);
-}
-}else
+
+/** @typedef {function(!WebInspector.DataGridNode, !WebInspector.DataGridNode):number} */
+WebInspector.SortableDataGrid.NodeComparator;
+
+/**
+ * @param {!WebInspector.DataGridNode} a
+ * @param {!WebInspector.DataGridNode} b
+ * @return {number}
+ */
+WebInspector.SortableDataGrid.TrivialComparator = function(a, b)
 {
-while(i<maxi){
-lumaValues[counter++]=(array[i++]+array[i++]+array[i++])*(array[i++]/255);
-}
-}
-break;}
-
-}
-return lumaValues;
-}
-function _averageLuma(lumaValues){
-var sumLuma=0.0;
-for(var i=0;i<lumaValues.length;i++){
-sumLuma+=lumaValues[i];
-}
-return sumLuma/lumaValues.length;
-}
-})(Internals||(Internals={}));
-})(ImageSSIM||(ImageSSIM={}));
-module.exports=ImageSSIM;
-
-},{}],266:[function(require,module,exports){
-var encode=require('./lib/encoder'),
-decode=require('./lib/decoder');
-
-module.exports={
-encode:encode,
-decode:decode};
-
-
-},{"./lib/decoder":267,"./lib/encoder":268}],267:[function(require,module,exports){
-(function(Buffer){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-var JpegImage=function jpegImage(){
-"use strict";
-var dctZigZag=new Int32Array([
-0,
-1,8,
-16,9,2,
-3,10,17,24,
-32,25,18,11,4,
-5,12,19,26,33,40,
-48,41,34,27,20,13,6,
-7,14,21,28,35,42,49,56,
-57,50,43,36,29,22,15,
-23,30,37,44,51,58,
-59,52,45,38,31,
-39,46,53,60,
-61,54,47,
-55,62,
-63]);
-
-
-var dctCos1=4017;
-var dctSin1=799;
-var dctCos3=3406;
-var dctSin3=2276;
-var dctCos6=1567;
-var dctSin6=3784;
-var dctSqrt2=5793;
-var dctSqrt1d2=2896;
-
-function constructor(){
+    return 0;
 }
 
-function buildHuffmanTable(codeLengths,values){
-var k=0,code=[],i,j,length=16;
-while(length>0&&!codeLengths[length-1])
-length--;
-code.push({children:[],index:0});
-var p=code[0],q;
-for(i=0;i<length;i++){
-for(j=0;j<codeLengths[i];j++){
-p=code.pop();
-p.children[p.index]=values[k];
-while(p.index>0){
-p=code.pop();
-}
-p.index++;
-code.push(p);
-while(code.length<=i){
-code.push(q={children:[],index:0});
-p.children[p.index]=q.children;
-p=q;
-}
-k++;
-}
-if(i+1<length){
-
-code.push(q={children:[],index:0});
-p.children[p.index]=q.children;
-p=q;
-}
-}
-return code[0].children;
-}
-
-function decodeScan(data,offset,
-frame,components,resetInterval,
-spectralStart,spectralEnd,
-successivePrev,successive){
-var precision=frame.precision;
-var samplesPerLine=frame.samplesPerLine;
-var scanLines=frame.scanLines;
-var mcusPerLine=frame.mcusPerLine;
-var progressive=frame.progressive;
-var maxH=frame.maxH,maxV=frame.maxV;
-
-var startOffset=offset,bitsData=0,bitsCount=0;
-function readBit(){
-if(bitsCount>0){
-bitsCount--;
-return bitsData>>bitsCount&1;
-}
-bitsData=data[offset++];
-if(bitsData==0xFF){
-var nextByte=data[offset++];
-if(nextByte){
-throw"unexpected marker: "+(bitsData<<8|nextByte).toString(16);
-}
-
-}
-bitsCount=7;
-return bitsData>>>7;
-}
-function decodeHuffman(tree){
-var node=tree,bit;
-while((bit=readBit())!==null){
-node=node[bit];
-if(typeof node==='number')
-return node;
-if(typeof node!=='object')
-throw"invalid huffman sequence";
-}
-return null;
-}
-function receive(length){
-var n=0;
-while(length>0){
-var bit=readBit();
-if(bit===null)return;
-n=n<<1|bit;
-length--;
-}
-return n;
-}
-function receiveAndExtend(length){
-var n=receive(length);
-if(n>=1<<length-1)
-return n;
-return n+(-1<<length)+1;
-}
-function decodeBaseline(component,zz){
-var t=decodeHuffman(component.huffmanTableDC);
-var diff=t===0?0:receiveAndExtend(t);
-zz[0]=component.pred+=diff;
-var k=1;
-while(k<64){
-var rs=decodeHuffman(component.huffmanTableAC);
-var s=rs&15,r=rs>>4;
-if(s===0){
-if(r<15)
-break;
-k+=16;
-continue;
-}
-k+=r;
-var z=dctZigZag[k];
-zz[z]=receiveAndExtend(s);
-k++;
-}
-}
-function decodeDCFirst(component,zz){
-var t=decodeHuffman(component.huffmanTableDC);
-var diff=t===0?0:receiveAndExtend(t)<<successive;
-zz[0]=component.pred+=diff;
-}
-function decodeDCSuccessive(component,zz){
-zz[0]|=readBit()<<successive;
-}
-var eobrun=0;
-function decodeACFirst(component,zz){
-if(eobrun>0){
-eobrun--;
-return;
-}
-var k=spectralStart,e=spectralEnd;
-while(k<=e){
-var rs=decodeHuffman(component.huffmanTableAC);
-var s=rs&15,r=rs>>4;
-if(s===0){
-if(r<15){
-eobrun=receive(r)+(1<<r)-1;
-break;
-}
-k+=16;
-continue;
-}
-k+=r;
-var z=dctZigZag[k];
-zz[z]=receiveAndExtend(s)*(1<<successive);
-k++;
-}
-}
-var successiveACState=0,successiveACNextValue;
-function decodeACSuccessive(component,zz){
-var k=spectralStart,e=spectralEnd,r=0;
-while(k<=e){
-var z=dctZigZag[k];
-switch(successiveACState){
-case 0:
-var rs=decodeHuffman(component.huffmanTableAC);
-var s=rs&15,r=rs>>4;
-if(s===0){
-if(r<15){
-eobrun=receive(r)+(1<<r);
-successiveACState=4;
-}else{
-r=16;
-successiveACState=1;
-}
-}else{
-if(s!==1)
-throw"invalid ACn encoding";
-successiveACNextValue=receiveAndExtend(s);
-successiveACState=r?2:3;
-}
-continue;
-case 1:
-case 2:
-if(zz[z])
-zz[z]+=readBit()<<successive;else
+/**
+ * @param {string} columnIdentifier
+ * @param {!WebInspector.DataGridNode} a
+ * @param {!WebInspector.DataGridNode} b
+ * @return {number}
+ */
+WebInspector.SortableDataGrid.NumericComparator = function(columnIdentifier, a, b)
 {
-r--;
-if(r===0)
-successiveACState=successiveACState==2?3:0;
+    var aValue = a.data[columnIdentifier];
+    var bValue = b.data[columnIdentifier];
+    var aNumber = Number(aValue instanceof Node ? aValue.textContent : aValue);
+    var bNumber = Number(bValue instanceof Node ? bValue.textContent : bValue);
+    return aNumber < bNumber ? -1 : (aNumber > bNumber ? 1 : 0);
 }
-break;
-case 3:
-if(zz[z])
-zz[z]+=readBit()<<successive;else
+
+/**
+ * @param {string} columnIdentifier
+ * @param {!WebInspector.DataGridNode} a
+ * @param {!WebInspector.DataGridNode} b
+ * @return {number}
+ */
+WebInspector.SortableDataGrid.StringComparator = function(columnIdentifier, a, b)
 {
-zz[z]=successiveACNextValue<<successive;
-successiveACState=0;
+    var aValue = a.data[columnIdentifier];
+    var bValue = b.data[columnIdentifier];
+    var aString = aValue instanceof Node ? aValue.textContent : String(aValue);
+    var bString = bValue instanceof Node ? bValue.textContent : String(bValue);
+    return aString < bString ? -1 : (aString > bString ? 1 : 0);
 }
-break;
-case 4:
-if(zz[z])
-zz[z]+=readBit()<<successive;
-break;}
 
-k++;
-}
-if(successiveACState===4){
-eobrun--;
-if(eobrun===0)
-successiveACState=0;
-}
-}
-function decodeMcu(component,decode,mcu,row,col){
-var mcuRow=mcu/mcusPerLine|0;
-var mcuCol=mcu%mcusPerLine;
-var blockRow=mcuRow*component.v+row;
-var blockCol=mcuCol*component.h+col;
-decode(component,component.blocks[blockRow][blockCol]);
-}
-function decodeBlock(component,decode,mcu){
-var blockRow=mcu/component.blocksPerLine|0;
-var blockCol=mcu%component.blocksPerLine;
-decode(component,component.blocks[blockRow][blockCol]);
-}
-
-var componentsLength=components.length;
-var component,i,j,k,n;
-var decodeFn;
-if(progressive){
-if(spectralStart===0)
-decodeFn=successivePrev===0?decodeDCFirst:decodeDCSuccessive;else
-
-decodeFn=successivePrev===0?decodeACFirst:decodeACSuccessive;
-}else{
-decodeFn=decodeBaseline;
-}
-
-var mcu=0,marker;
-var mcuExpected;
-if(componentsLength==1){
-mcuExpected=components[0].blocksPerLine*components[0].blocksPerColumn;
-}else{
-mcuExpected=mcusPerLine*frame.mcusPerColumn;
-}
-if(!resetInterval)resetInterval=mcuExpected;
-
-var h,v;
-while(mcu<mcuExpected){
-
-for(i=0;i<componentsLength;i++)
-components[i].pred=0;
-eobrun=0;
-
-if(componentsLength==1){
-component=components[0];
-for(n=0;n<resetInterval;n++){
-decodeBlock(component,decodeFn,mcu);
-mcu++;
-}
-}else{
-for(n=0;n<resetInterval;n++){
-for(i=0;i<componentsLength;i++){
-component=components[i];
-h=component.h;
-v=component.v;
-for(j=0;j<v;j++){
-for(k=0;k<h;k++){
-decodeMcu(component,decodeFn,mcu,j,k);
-}
-}
-}
-mcu++;
-
-
-if(mcu===mcuExpected)break;
-}
-}
-
-
-bitsCount=0;
-marker=data[offset]<<8|data[offset+1];
-if(marker<0xFF00){
-throw"marker was not found";
-}
-
-if(marker>=0xFFD0&&marker<=0xFFD7){
-offset+=2;
-}else
-
-break;
-}
-
-return offset-startOffset;
-}
-
-function buildComponentData(frame,component){
-var lines=[];
-var blocksPerLine=component.blocksPerLine;
-var blocksPerColumn=component.blocksPerColumn;
-var samplesPerLine=blocksPerLine<<3;
-var R=new Int32Array(64),r=new Uint8Array(64);
-
-
-
-
-
-
-function quantizeAndInverse(zz,dataOut,dataIn){
-var qt=component.quantizationTable;
-var v0,v1,v2,v3,v4,v5,v6,v7,t;
-var p=dataIn;
-var i;
-
-
-for(i=0;i<64;i++)
-p[i]=zz[i]*qt[i];
-
-
-for(i=0;i<8;++i){
-var row=8*i;
-
-
-if(p[1+row]==0&&p[2+row]==0&&p[3+row]==0&&
-p[4+row]==0&&p[5+row]==0&&p[6+row]==0&&
-p[7+row]==0){
-t=dctSqrt2*p[0+row]+512>>10;
-p[0+row]=t;
-p[1+row]=t;
-p[2+row]=t;
-p[3+row]=t;
-p[4+row]=t;
-p[5+row]=t;
-p[6+row]=t;
-p[7+row]=t;
-continue;
-}
-
-
-v0=dctSqrt2*p[0+row]+128>>8;
-v1=dctSqrt2*p[4+row]+128>>8;
-v2=p[2+row];
-v3=p[6+row];
-v4=dctSqrt1d2*(p[1+row]-p[7+row])+128>>8;
-v7=dctSqrt1d2*(p[1+row]+p[7+row])+128>>8;
-v5=p[3+row]<<4;
-v6=p[5+row]<<4;
-
-
-t=v0-v1+1>>1;
-v0=v0+v1+1>>1;
-v1=t;
-t=v2*dctSin6+v3*dctCos6+128>>8;
-v2=v2*dctCos6-v3*dctSin6+128>>8;
-v3=t;
-t=v4-v6+1>>1;
-v4=v4+v6+1>>1;
-v6=t;
-t=v7+v5+1>>1;
-v5=v7-v5+1>>1;
-v7=t;
-
-
-t=v0-v3+1>>1;
-v0=v0+v3+1>>1;
-v3=t;
-t=v1-v2+1>>1;
-v1=v1+v2+1>>1;
-v2=t;
-t=v4*dctSin3+v7*dctCos3+2048>>12;
-v4=v4*dctCos3-v7*dctSin3+2048>>12;
-v7=t;
-t=v5*dctSin1+v6*dctCos1+2048>>12;
-v5=v5*dctCos1-v6*dctSin1+2048>>12;
-v6=t;
-
-
-p[0+row]=v0+v7;
-p[7+row]=v0-v7;
-p[1+row]=v1+v6;
-p[6+row]=v1-v6;
-p[2+row]=v2+v5;
-p[5+row]=v2-v5;
-p[3+row]=v3+v4;
-p[4+row]=v3-v4;
-}
-
-
-for(i=0;i<8;++i){
-var col=i;
-
-
-if(p[1*8+col]==0&&p[2*8+col]==0&&p[3*8+col]==0&&
-p[4*8+col]==0&&p[5*8+col]==0&&p[6*8+col]==0&&
-p[7*8+col]==0){
-t=dctSqrt2*dataIn[i+0]+8192>>14;
-p[0*8+col]=t;
-p[1*8+col]=t;
-p[2*8+col]=t;
-p[3*8+col]=t;
-p[4*8+col]=t;
-p[5*8+col]=t;
-p[6*8+col]=t;
-p[7*8+col]=t;
-continue;
-}
-
-
-v0=dctSqrt2*p[0*8+col]+2048>>12;
-v1=dctSqrt2*p[4*8+col]+2048>>12;
-v2=p[2*8+col];
-v3=p[6*8+col];
-v4=dctSqrt1d2*(p[1*8+col]-p[7*8+col])+2048>>12;
-v7=dctSqrt1d2*(p[1*8+col]+p[7*8+col])+2048>>12;
-v5=p[3*8+col];
-v6=p[5*8+col];
-
-
-t=v0-v1+1>>1;
-v0=v0+v1+1>>1;
-v1=t;
-t=v2*dctSin6+v3*dctCos6+2048>>12;
-v2=v2*dctCos6-v3*dctSin6+2048>>12;
-v3=t;
-t=v4-v6+1>>1;
-v4=v4+v6+1>>1;
-v6=t;
-t=v7+v5+1>>1;
-v5=v7-v5+1>>1;
-v7=t;
-
-
-t=v0-v3+1>>1;
-v0=v0+v3+1>>1;
-v3=t;
-t=v1-v2+1>>1;
-v1=v1+v2+1>>1;
-v2=t;
-t=v4*dctSin3+v7*dctCos3+2048>>12;
-v4=v4*dctCos3-v7*dctSin3+2048>>12;
-v7=t;
-t=v5*dctSin1+v6*dctCos1+2048>>12;
-v5=v5*dctCos1-v6*dctSin1+2048>>12;
-v6=t;
-
-
-p[0*8+col]=v0+v7;
-p[7*8+col]=v0-v7;
-p[1*8+col]=v1+v6;
-p[6*8+col]=v1-v6;
-p[2*8+col]=v2+v5;
-p[5*8+col]=v2-v5;
-p[3*8+col]=v3+v4;
-p[4*8+col]=v3-v4;
-}
-
-
-for(i=0;i<64;++i){
-var sample=128+(p[i]+8>>4);
-dataOut[i]=sample<0?0:sample>0xFF?0xFF:sample;
-}
-}
-
-var i,j;
-for(var blockRow=0;blockRow<blocksPerColumn;blockRow++){
-var scanLine=blockRow<<3;
-for(i=0;i<8;i++)
-lines.push(new Uint8Array(samplesPerLine));
-for(var blockCol=0;blockCol<blocksPerLine;blockCol++){
-quantizeAndInverse(component.blocks[blockRow][blockCol],r,R);
-
-var offset=0,sample=blockCol<<3;
-for(j=0;j<8;j++){
-var line=lines[scanLine+j];
-for(i=0;i<8;i++)
-line[sample+i]=r[offset++];
-}
-}
-}
-return lines;
-}
-
-function clampTo8bit(a){
-return a<0?0:a>255?255:a;
-}
-
-constructor.prototype={
-load:function load(path){
-var xhr=new XMLHttpRequest();
-xhr.open("GET",path,true);
-xhr.responseType="arraybuffer";
-xhr.onload=function(){
-
-var data=new Uint8Array(xhr.response||xhr.mozResponseArrayBuffer);
-this.parse(data);
-if(this.onload)
-this.onload();
-}.bind(this);
-xhr.send(null);
-},
-parse:function parse(data){
-var offset=0,length=data.length;
-function readUint16(){
-var value=data[offset]<<8|data[offset+1];
-offset+=2;
-return value;
-}
-function readDataBlock(){
-var length=readUint16();
-var array=data.subarray(offset,offset+length-2);
-offset+=array.length;
-return array;
-}
-function prepareComponents(frame){
-var maxH=0,maxV=0;
-var component,componentId;
-for(componentId in frame.components){
-if(frame.components.hasOwnProperty(componentId)){
-component=frame.components[componentId];
-if(maxH<component.h)maxH=component.h;
-if(maxV<component.v)maxV=component.v;
-}
-}
-var mcusPerLine=Math.ceil(frame.samplesPerLine/8/maxH);
-var mcusPerColumn=Math.ceil(frame.scanLines/8/maxV);
-for(componentId in frame.components){
-if(frame.components.hasOwnProperty(componentId)){
-component=frame.components[componentId];
-var blocksPerLine=Math.ceil(Math.ceil(frame.samplesPerLine/8)*component.h/maxH);
-var blocksPerColumn=Math.ceil(Math.ceil(frame.scanLines/8)*component.v/maxV);
-var blocksPerLineForMcu=mcusPerLine*component.h;
-var blocksPerColumnForMcu=mcusPerColumn*component.v;
-var blocks=[];
-for(var i=0;i<blocksPerColumnForMcu;i++){
-var row=[];
-for(var j=0;j<blocksPerLineForMcu;j++)
-row.push(new Int32Array(64));
-blocks.push(row);
-}
-component.blocksPerLine=blocksPerLine;
-component.blocksPerColumn=blocksPerColumn;
-component.blocks=blocks;
-}
-}
-frame.maxH=maxH;
-frame.maxV=maxV;
-frame.mcusPerLine=mcusPerLine;
-frame.mcusPerColumn=mcusPerColumn;
-}
-var jfif=null;
-var adobe=null;
-var pixels=null;
-var frame,resetInterval;
-var quantizationTables=[],frames=[];
-var huffmanTablesAC=[],huffmanTablesDC=[];
-var fileMarker=readUint16();
-if(fileMarker!=0xFFD8){
-throw"SOI not found";
-}
-
-fileMarker=readUint16();
-while(fileMarker!=0xFFD9){
-var i,j,l;
-switch(fileMarker){
-case 0xFF00:break;
-case 0xFFE0:
-case 0xFFE1:
-case 0xFFE2:
-case 0xFFE3:
-case 0xFFE4:
-case 0xFFE5:
-case 0xFFE6:
-case 0xFFE7:
-case 0xFFE8:
-case 0xFFE9:
-case 0xFFEA:
-case 0xFFEB:
-case 0xFFEC:
-case 0xFFED:
-case 0xFFEE:
-case 0xFFEF:
-case 0xFFFE:
-var appData=readDataBlock();
-
-if(fileMarker===0xFFE0){
-if(appData[0]===0x4A&&appData[1]===0x46&&appData[2]===0x49&&
-appData[3]===0x46&&appData[4]===0){
-jfif={
-version:{major:appData[5],minor:appData[6]},
-densityUnits:appData[7],
-xDensity:appData[8]<<8|appData[9],
-yDensity:appData[10]<<8|appData[11],
-thumbWidth:appData[12],
-thumbHeight:appData[13],
-thumbData:appData.subarray(14,14+3*appData[12]*appData[13])};
-
-}
-}
-
-if(fileMarker===0xFFEE){
-if(appData[0]===0x41&&appData[1]===0x64&&appData[2]===0x6F&&
-appData[3]===0x62&&appData[4]===0x65&&appData[5]===0){
-adobe={
-version:appData[6],
-flags0:appData[7]<<8|appData[8],
-flags1:appData[9]<<8|appData[10],
-transformCode:appData[11]};
-
-}
-}
-break;
-
-case 0xFFDB:
-var quantizationTablesLength=readUint16();
-var quantizationTablesEnd=quantizationTablesLength+offset-2;
-while(offset<quantizationTablesEnd){
-var quantizationTableSpec=data[offset++];
-var tableData=new Int32Array(64);
-if(quantizationTableSpec>>4===0){
-for(j=0;j<64;j++){
-var z=dctZigZag[j];
-tableData[z]=data[offset++];
-}
-}else if(quantizationTableSpec>>4===1){
-for(j=0;j<64;j++){
-var z=dctZigZag[j];
-tableData[z]=readUint16();
-}
-}else
-throw"DQT: invalid table spec";
-quantizationTables[quantizationTableSpec&15]=tableData;
-}
-break;
-
-case 0xFFC0:
-case 0xFFC1:
-case 0xFFC2:
-readUint16();
-frame={};
-frame.extended=fileMarker===0xFFC1;
-frame.progressive=fileMarker===0xFFC2;
-frame.precision=data[offset++];
-frame.scanLines=readUint16();
-frame.samplesPerLine=readUint16();
-frame.components={};
-frame.componentsOrder=[];
-var componentsCount=data[offset++],componentId;
-var maxH=0,maxV=0;
-for(i=0;i<componentsCount;i++){
-componentId=data[offset];
-var h=data[offset+1]>>4;
-var v=data[offset+1]&15;
-var qId=data[offset+2];
-frame.componentsOrder.push(componentId);
-frame.components[componentId]={
-h:h,
-v:v,
-quantizationIdx:qId};
-
-offset+=3;
-}
-prepareComponents(frame);
-frames.push(frame);
-break;
-
-case 0xFFC4:
-var huffmanLength=readUint16();
-for(i=2;i<huffmanLength;){
-var huffmanTableSpec=data[offset++];
-var codeLengths=new Uint8Array(16);
-var codeLengthSum=0;
-for(j=0;j<16;j++,offset++)
-codeLengthSum+=codeLengths[j]=data[offset];
-var huffmanValues=new Uint8Array(codeLengthSum);
-for(j=0;j<codeLengthSum;j++,offset++)
-huffmanValues[j]=data[offset];
-i+=17+codeLengthSum;
-
-(huffmanTableSpec>>4===0?
-huffmanTablesDC:huffmanTablesAC)[huffmanTableSpec&15]=
-buildHuffmanTable(codeLengths,huffmanValues);
-}
-break;
-
-case 0xFFDD:
-readUint16();
-resetInterval=readUint16();
-break;
-
-case 0xFFDA:
-var scanLength=readUint16();
-var selectorsCount=data[offset++];
-var components=[],component;
-for(i=0;i<selectorsCount;i++){
-component=frame.components[data[offset++]];
-var tableSpec=data[offset++];
-component.huffmanTableDC=huffmanTablesDC[tableSpec>>4];
-component.huffmanTableAC=huffmanTablesAC[tableSpec&15];
-components.push(component);
-}
-var spectralStart=data[offset++];
-var spectralEnd=data[offset++];
-var successiveApproximation=data[offset++];
-var processed=decodeScan(data,offset,
-frame,components,resetInterval,
-spectralStart,spectralEnd,
-successiveApproximation>>4,successiveApproximation&15);
-offset+=processed;
-break;
-default:
-if(data[offset-3]==0xFF&&
-data[offset-2]>=0xC0&&data[offset-2]<=0xFE){
-
-
-offset-=3;
-break;
-}
-throw"unknown JPEG marker "+fileMarker.toString(16);}
-
-fileMarker=readUint16();
-}
-if(frames.length!=1)
-throw"only single frame JPEGs supported";
-
-
-for(var i=0;i<frames.length;i++){
-var cp=frames[i].components;
-for(var j in cp){
-cp[j].quantizationTable=quantizationTables[cp[j].quantizationIdx];
-delete cp[j].quantizationIdx;
-}
-}
-
-this.width=frame.samplesPerLine;
-this.height=frame.scanLines;
-this.jfif=jfif;
-this.adobe=adobe;
-this.components=[];
-for(var i=0;i<frame.componentsOrder.length;i++){
-var component=frame.components[frame.componentsOrder[i]];
-this.components.push({
-lines:buildComponentData(frame,component),
-scaleX:component.h/frame.maxH,
-scaleY:component.v/frame.maxV});
-
-}
-},
-getData:function getData(width,height){
-var scaleX=this.width/width,scaleY=this.height/height;
-
-var component1,component2,component3,component4;
-var component1Line,component2Line,component3Line,component4Line;
-var x,y;
-var offset=0;
-var Y,Cb,Cr,K,C,M,Ye,R,G,B;
-var colorTransform;
-var dataLength=width*height*this.components.length;
-var data=new Uint8Array(dataLength);
-switch(this.components.length){
-case 1:
-component1=this.components[0];
-for(y=0;y<height;y++){
-component1Line=component1.lines[0|y*component1.scaleY*scaleY];
-for(x=0;x<width;x++){
-Y=component1Line[0|x*component1.scaleX*scaleX];
-
-data[offset++]=Y;
-}
-}
-break;
-case 2:
-
-component1=this.components[0];
-component2=this.components[1];
-for(y=0;y<height;y++){
-component1Line=component1.lines[0|y*component1.scaleY*scaleY];
-component2Line=component2.lines[0|y*component2.scaleY*scaleY];
-for(x=0;x<width;x++){
-Y=component1Line[0|x*component1.scaleX*scaleX];
-data[offset++]=Y;
-Y=component2Line[0|x*component2.scaleX*scaleX];
-data[offset++]=Y;
-}
-}
-break;
-case 3:
-
-colorTransform=true;
-
-if(this.adobe&&this.adobe.transformCode)
-colorTransform=true;else
-if(typeof this.colorTransform!=='undefined')
-colorTransform=!!this.colorTransform;
-
-component1=this.components[0];
-component2=this.components[1];
-component3=this.components[2];
-for(y=0;y<height;y++){
-component1Line=component1.lines[0|y*component1.scaleY*scaleY];
-component2Line=component2.lines[0|y*component2.scaleY*scaleY];
-component3Line=component3.lines[0|y*component3.scaleY*scaleY];
-for(x=0;x<width;x++){
-if(!colorTransform){
-R=component1Line[0|x*component1.scaleX*scaleX];
-G=component2Line[0|x*component2.scaleX*scaleX];
-B=component3Line[0|x*component3.scaleX*scaleX];
-}else{
-Y=component1Line[0|x*component1.scaleX*scaleX];
-Cb=component2Line[0|x*component2.scaleX*scaleX];
-Cr=component3Line[0|x*component3.scaleX*scaleX];
-
-R=clampTo8bit(Y+1.402*(Cr-128));
-G=clampTo8bit(Y-0.3441363*(Cb-128)-0.71413636*(Cr-128));
-B=clampTo8bit(Y+1.772*(Cb-128));
-}
-
-data[offset++]=R;
-data[offset++]=G;
-data[offset++]=B;
-}
-}
-break;
-case 4:
-if(!this.adobe)
-throw'Unsupported color mode (4 components)';
-
-colorTransform=false;
-
-if(this.adobe&&this.adobe.transformCode)
-colorTransform=true;else
-if(typeof this.colorTransform!=='undefined')
-colorTransform=!!this.colorTransform;
-
-component1=this.components[0];
-component2=this.components[1];
-component3=this.components[2];
-component4=this.components[3];
-for(y=0;y<height;y++){
-component1Line=component1.lines[0|y*component1.scaleY*scaleY];
-component2Line=component2.lines[0|y*component2.scaleY*scaleY];
-component3Line=component3.lines[0|y*component3.scaleY*scaleY];
-component4Line=component4.lines[0|y*component4.scaleY*scaleY];
-for(x=0;x<width;x++){
-if(!colorTransform){
-C=component1Line[0|x*component1.scaleX*scaleX];
-M=component2Line[0|x*component2.scaleX*scaleX];
-Ye=component3Line[0|x*component3.scaleX*scaleX];
-K=component4Line[0|x*component4.scaleX*scaleX];
-}else{
-Y=component1Line[0|x*component1.scaleX*scaleX];
-Cb=component2Line[0|x*component2.scaleX*scaleX];
-Cr=component3Line[0|x*component3.scaleX*scaleX];
-K=component4Line[0|x*component4.scaleX*scaleX];
-
-C=255-clampTo8bit(Y+1.402*(Cr-128));
-M=255-clampTo8bit(Y-0.3441363*(Cb-128)-0.71413636*(Cr-128));
-Ye=255-clampTo8bit(Y+1.772*(Cb-128));
-}
-data[offset++]=255-C;
-data[offset++]=255-M;
-data[offset++]=255-Ye;
-data[offset++]=255-K;
-}
-}
-break;
-default:
-throw'Unsupported color mode';}
-
-return data;
-},
-copyToImageData:function copyToImageData(imageData){
-var width=imageData.width,height=imageData.height;
-var imageDataArray=imageData.data;
-var data=this.getData(width,height);
-var i=0,j=0,x,y;
-var Y,K,C,M,R,G,B;
-switch(this.components.length){
-case 1:
-for(y=0;y<height;y++){
-for(x=0;x<width;x++){
-Y=data[i++];
-
-imageDataArray[j++]=Y;
-imageDataArray[j++]=Y;
-imageDataArray[j++]=Y;
-imageDataArray[j++]=255;
-}
-}
-break;
-case 3:
-for(y=0;y<height;y++){
-for(x=0;x<width;x++){
-R=data[i++];
-G=data[i++];
-B=data[i++];
-
-imageDataArray[j++]=R;
-imageDataArray[j++]=G;
-imageDataArray[j++]=B;
-imageDataArray[j++]=255;
-}
-}
-break;
-case 4:
-for(y=0;y<height;y++){
-for(x=0;x<width;x++){
-C=data[i++];
-M=data[i++];
-Y=data[i++];
-K=data[i++];
-
-R=255-clampTo8bit(C*(1-K/255)+K);
-G=255-clampTo8bit(M*(1-K/255)+K);
-B=255-clampTo8bit(Y*(1-K/255)+K);
-
-imageDataArray[j++]=R;
-imageDataArray[j++]=G;
-imageDataArray[j++]=B;
-imageDataArray[j++]=255;
-}
-}
-break;
-default:
-throw'Unsupported color mode';}
-
-}};
-
-
-return constructor;
-}();
-module.exports=decode;
-
-function decode(jpegData){
-var arr=new Uint8Array(jpegData);
-var decoder=new JpegImage();
-decoder.parse(arr);
-
-var image={
-width:decoder.width,
-height:decoder.height,
-data:new Buffer(decoder.width*decoder.height*4)};
-
-
-decoder.copyToImageData(image);
-
-return image;
-}
-
-}).call(this,require("buffer").Buffer);
-},{"buffer":199}],268:[function(require,module,exports){
-(function(Buffer){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-var btoa=btoa||function(buf){
-return new Buffer(buf).toString('base64');
-};
-
-function JPEGEncoder(quality){
-var self=this;
-var fround=Math.round;
-var ffloor=Math.floor;
-var YTable=new Array(64);
-var UVTable=new Array(64);
-var fdtbl_Y=new Array(64);
-var fdtbl_UV=new Array(64);
-var YDC_HT;
-var UVDC_HT;
-var YAC_HT;
-var UVAC_HT;
-
-var bitcode=new Array(65535);
-var category=new Array(65535);
-var outputfDCTQuant=new Array(64);
-var DU=new Array(64);
-var byteout=[];
-var bytenew=0;
-var bytepos=7;
-
-var YDU=new Array(64);
-var UDU=new Array(64);
-var VDU=new Array(64);
-var clt=new Array(256);
-var RGB_YUV_TABLE=new Array(2048);
-var currentQuality;
-
-var ZigZag=[
-0,1,5,6,14,15,27,28,
-2,4,7,13,16,26,29,42,
-3,8,12,17,25,30,41,43,
-9,11,18,24,31,40,44,53,
-10,19,23,32,39,45,52,54,
-20,22,33,38,46,51,55,60,
-21,34,37,47,50,56,59,61,
-35,36,48,49,57,58,62,63];
-
-
-var std_dc_luminance_nrcodes=[0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];
-var std_dc_luminance_values=[0,1,2,3,4,5,6,7,8,9,10,11];
-var std_ac_luminance_nrcodes=[0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];
-var std_ac_luminance_values=[
-0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,
-0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,
-0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,
-0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,
-0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,
-0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,
-0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,
-0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,
-0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,
-0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,
-0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,
-0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
-0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,
-0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,
-0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,
-0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,
-0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,
-0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,
-0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,
-0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
-0xf9,0xfa];
-
-
-var std_dc_chrominance_nrcodes=[0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];
-var std_dc_chrominance_values=[0,1,2,3,4,5,6,7,8,9,10,11];
-var std_ac_chrominance_nrcodes=[0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];
-var std_ac_chrominance_values=[
-0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,
-0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,
-0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,
-0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,
-0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,
-0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,
-0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,
-0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,
-0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,
-0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,
-0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,
-0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,
-0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,
-0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,
-0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,
-0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,
-0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,
-0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,
-0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,
-0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
-0xf9,0xfa];
-
-
-function initQuantTables(sf){
-var YQT=[
-16,11,10,16,24,40,51,61,
-12,12,14,19,26,58,60,55,
-14,13,16,24,40,57,69,56,
-14,17,22,29,51,87,80,62,
-18,22,37,56,68,109,103,77,
-24,35,55,64,81,104,113,92,
-49,64,78,87,103,121,120,101,
-72,92,95,98,112,100,103,99];
-
-
-for(var i=0;i<64;i++){
-var t=ffloor((YQT[i]*sf+50)/100);
-if(t<1){
-t=1;
-}else if(t>255){
-t=255;
-}
-YTable[ZigZag[i]]=t;
-}
-var UVQT=[
-17,18,24,47,99,99,99,99,
-18,21,26,66,99,99,99,99,
-24,26,56,99,99,99,99,99,
-47,66,99,99,99,99,99,99,
-99,99,99,99,99,99,99,99,
-99,99,99,99,99,99,99,99,
-99,99,99,99,99,99,99,99,
-99,99,99,99,99,99,99,99];
-
-for(var j=0;j<64;j++){
-var u=ffloor((UVQT[j]*sf+50)/100);
-if(u<1){
-u=1;
-}else if(u>255){
-u=255;
-}
-UVTable[ZigZag[j]]=u;
-}
-var aasf=[
-1.0,1.387039845,1.306562965,1.175875602,
-1.0,0.785694958,0.541196100,0.275899379];
-
-var k=0;
-for(var row=0;row<8;row++)
+/**
+ * @param {!WebInspector.SortableDataGrid.NodeComparator} comparator
+ * @param {boolean} reverseMode
+ * @param {!WebInspector.DataGridNode} a
+ * @param {!WebInspector.DataGridNode} b
+ * @return {number}
+ */
+WebInspector.SortableDataGrid.Comparator = function(comparator, reverseMode, a, b)
 {
-for(var col=0;col<8;col++)
+    return reverseMode ? comparator(b, a) : comparator(a, b);
+}
+
+/**
+ * @param {!Array.<string>} columnNames
+ * @param {!Array.<string>} values
+ * @return {?WebInspector.SortableDataGrid}
+ */
+WebInspector.SortableDataGrid.create = function(columnNames, values)
 {
-fdtbl_Y[k]=1.0/(YTable[ZigZag[k]]*aasf[row]*aasf[col]*8.0);
-fdtbl_UV[k]=1.0/(UVTable[ZigZag[k]]*aasf[row]*aasf[col]*8.0);
-k++;
-}
-}
+    var numColumns = columnNames.length;
+    if (!numColumns)
+        return null;
+
+    var columns = [];
+    for (var i = 0; i < columnNames.length; ++i)
+        columns.push({ title: columnNames[i], width: columnNames[i].length, sortable: true });
+
+    var nodes = [];
+    for (var i = 0; i < values.length / numColumns; ++i) {
+        var data = {};
+        for (var j = 0; j < columnNames.length; ++j)
+            data[j] = values[numColumns * i + j];
+
+        var node = new WebInspector.SortableDataGridNode(data);
+        node.selectable = false;
+        nodes.push(node);
+    }
+
+    var dataGrid = new WebInspector.SortableDataGrid(columns);
+    var length = nodes.length;
+    var rootNode = dataGrid.rootNode();
+    for (var i = 0; i < length; ++i)
+        rootNode.appendChild(nodes[i]);
+
+    dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged, sortDataGrid);
+
+    function sortDataGrid()
+    {
+        var nodes = dataGrid.rootNode().children;
+        var sortColumnIdentifier = dataGrid.sortColumnIdentifier();
+        if (!sortColumnIdentifier)
+            return;
+
+        var columnIsNumeric = true;
+        for (var i = 0; i < nodes.length; i++) {
+            var value = nodes[i].data[sortColumnIdentifier];
+            if (isNaN(value instanceof Node ? value.textContent : value)) {
+                columnIsNumeric = false;
+                break;
+            }
+        }
+
+        var comparator = columnIsNumeric ? WebInspector.SortableDataGrid.NumericComparator : WebInspector.SortableDataGrid.StringComparator;
+        dataGrid.sortNodes(comparator.bind(null, sortColumnIdentifier), !dataGrid.isSortOrderAscending());
+    }
+    return dataGrid;
 }
 
-function computeHuffmanTbl(nrcodes,std_table){
-var codevalue=0;
-var pos_in_table=0;
-var HT=new Array();
-for(var k=1;k<=16;k++){
-for(var j=1;j<=nrcodes[k];j++){
-HT[std_table[pos_in_table]]=[];
-HT[std_table[pos_in_table]][0]=codevalue;
-HT[std_table[pos_in_table]][1]=k;
-pos_in_table++;
-codevalue++;
-}
-codevalue*=2;
-}
-return HT;
+WebInspector.SortableDataGrid.prototype = {
+    /**
+     * @param {!WebInspector.DataGridNode} node
+     */
+    insertChild: function(node)
+    {
+        var root = /** @type {!WebInspector.SortableDataGridNode} */ (this.rootNode());
+        root.insertChildOrdered(node);
+    },
+
+    /**
+     * @param {!WebInspector.SortableDataGrid.NodeComparator} comparator
+     * @param {boolean} reverseMode
+     */
+    sortNodes: function(comparator, reverseMode)
+    {
+        this._sortingFunction = WebInspector.SortableDataGrid.Comparator.bind(null, comparator, reverseMode);
+        this._rootNode._sortChildren(reverseMode);
+        this.scheduleUpdateStructure();
+    },
+
+    __proto__: WebInspector.ViewportDataGrid.prototype
 }
 
-function initHuffmanTbl()
+/**
+ * @constructor
+ * @extends {WebInspector.ViewportDataGridNode}
+ * @param {?Object.<string, *>=} data
+ * @param {boolean=} hasChildren
+ */
+WebInspector.SortableDataGridNode = function(data, hasChildren)
 {
-YDC_HT=computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values);
-UVDC_HT=computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values);
-YAC_HT=computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values);
-UVAC_HT=computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values);
+    WebInspector.ViewportDataGridNode.call(this, data, hasChildren);
 }
 
-function initCategoryNumber()
-{
-var nrlower=1;
-var nrupper=2;
-for(var cat=1;cat<=15;cat++){
+WebInspector.SortableDataGridNode.prototype = {
+    /**
+     * @param {!WebInspector.DataGridNode} node
+     */
+    insertChildOrdered: function(node)
+    {
+        this.insertChild(node, this.children.upperBound(node, this.dataGrid._sortingFunction));
+    },
 
-for(var nr=nrlower;nr<nrupper;nr++){
-category[32767+nr]=cat;
-bitcode[32767+nr]=[];
-bitcode[32767+nr][1]=cat;
-bitcode[32767+nr][0]=nr;
+    _sortChildren: function()
+    {
+        this.children.sort(this.dataGrid._sortingFunction);
+        for (var i = 0; i < this.children.length; ++i)
+            this.children[i].recalculateSiblings(i);
+        for (var child of this.children)
+            child._sortChildren();
+    },
+
+    __proto__: WebInspector.ViewportDataGridNode.prototype
 }
 
-for(var nrneg=-(nrupper-1);nrneg<=-nrlower;nrneg++){
-category[32767+nrneg]=cat;
-bitcode[32767+nrneg]=[];
-bitcode[32767+nrneg][1]=cat;
-bitcode[32767+nrneg][0]=nrupper-1+nrneg;
-}
-nrlower<<=1;
-nrupper<<=1;
-}
+},{}],262:[function(require,module,exports){
+
+/**
+ * This is the web browser implementation of `debug()`.
+ *
+ * Expose `debug()` as the module.
+ */
+
+exports = module.exports = require('./debug');
+exports.log = log;
+exports.formatArgs = formatArgs;
+exports.save = save;
+exports.load = load;
+exports.useColors = useColors;
+exports.storage = 'undefined' != typeof chrome
+               && 'undefined' != typeof chrome.storage
+                  ? chrome.storage.local
+                  : localstorage();
+
+/**
+ * Colors.
+ */
+
+exports.colors = [
+  'lightseagreen',
+  'forestgreen',
+  'goldenrod',
+  'dodgerblue',
+  'darkorchid',
+  'crimson'
+];
+
+/**
+ * Currently only WebKit-based Web Inspectors, Firefox >= v31,
+ * and the Firebug extension (any Firefox version) are known
+ * to support "%c" CSS customizations.
+ *
+ * TODO: add a `localStorage` variable to explicitly enable/disable colors
+ */
+
+function useColors() {
+  // is webkit? http://stackoverflow.com/a/16459606/376773
+  return ('WebkitAppearance' in document.documentElement.style) ||
+    // is firebug? http://stackoverflow.com/a/398120/376773
+    (window.console && (console.firebug || (console.exception && console.table))) ||
+    // is firefox >= v31?
+    // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
+    (navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31);
 }
 
-function initRGBYUVTable(){
-for(var i=0;i<256;i++){
-RGB_YUV_TABLE[i]=19595*i;
-RGB_YUV_TABLE[i+256>>0]=38470*i;
-RGB_YUV_TABLE[i+512>>0]=7471*i+0x8000;
-RGB_YUV_TABLE[i+768>>0]=-11059*i;
-RGB_YUV_TABLE[i+1024>>0]=-21709*i;
-RGB_YUV_TABLE[i+1280>>0]=32768*i+0x807FFF;
-RGB_YUV_TABLE[i+1536>>0]=-27439*i;
-RGB_YUV_TABLE[i+1792>>0]=-5329*i;
-}
-}
+/**
+ * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
+ */
 
-
-function writeBits(bs)
-{
-var value=bs[0];
-var posval=bs[1]-1;
-while(posval>=0){
-if(value&1<<posval){
-bytenew|=1<<bytepos;
-}
-posval--;
-bytepos--;
-if(bytepos<0){
-if(bytenew==0xFF){
-writeByte(0xFF);
-writeByte(0);
-}else
-{
-writeByte(bytenew);
-}
-bytepos=7;
-bytenew=0;
-}
-}
-}
-
-function writeByte(value)
-{
-
-byteout.push(value);
-}
-
-function writeWord(value)
-{
-writeByte(value>>8&0xFF);
-writeByte(value&0xFF);
-}
-
-
-function fDCTQuant(data,fdtbl)
-{
-var d0,d1,d2,d3,d4,d5,d6,d7;
-
-var dataOff=0;
-var i;
-const I8=8;
-const I64=64;
-for(i=0;i<I8;++i)
-{
-d0=data[dataOff];
-d1=data[dataOff+1];
-d2=data[dataOff+2];
-d3=data[dataOff+3];
-d4=data[dataOff+4];
-d5=data[dataOff+5];
-d6=data[dataOff+6];
-d7=data[dataOff+7];
-
-var tmp0=d0+d7;
-var tmp7=d0-d7;
-var tmp1=d1+d6;
-var tmp6=d1-d6;
-var tmp2=d2+d5;
-var tmp5=d2-d5;
-var tmp3=d3+d4;
-var tmp4=d3-d4;
-
-
-var tmp10=tmp0+tmp3;
-var tmp13=tmp0-tmp3;
-var tmp11=tmp1+tmp2;
-var tmp12=tmp1-tmp2;
-
-data[dataOff]=tmp10+tmp11;
-data[dataOff+4]=tmp10-tmp11;
-
-var z1=(tmp12+tmp13)*0.707106781;
-data[dataOff+2]=tmp13+z1;
-data[dataOff+6]=tmp13-z1;
-
-
-tmp10=tmp4+tmp5;
-tmp11=tmp5+tmp6;
-tmp12=tmp6+tmp7;
-
-
-var z5=(tmp10-tmp12)*0.382683433;
-var z2=0.541196100*tmp10+z5;
-var z4=1.306562965*tmp12+z5;
-var z3=tmp11*0.707106781;
-
-var z11=tmp7+z3;
-var z13=tmp7-z3;
-
-data[dataOff+5]=z13+z2;
-data[dataOff+3]=z13-z2;
-data[dataOff+1]=z11+z4;
-data[dataOff+7]=z11-z4;
-
-dataOff+=8;
-}
-
-
-dataOff=0;
-for(i=0;i<I8;++i)
-{
-d0=data[dataOff];
-d1=data[dataOff+8];
-d2=data[dataOff+16];
-d3=data[dataOff+24];
-d4=data[dataOff+32];
-d5=data[dataOff+40];
-d6=data[dataOff+48];
-d7=data[dataOff+56];
-
-var tmp0p2=d0+d7;
-var tmp7p2=d0-d7;
-var tmp1p2=d1+d6;
-var tmp6p2=d1-d6;
-var tmp2p2=d2+d5;
-var tmp5p2=d2-d5;
-var tmp3p2=d3+d4;
-var tmp4p2=d3-d4;
-
-
-var tmp10p2=tmp0p2+tmp3p2;
-var tmp13p2=tmp0p2-tmp3p2;
-var tmp11p2=tmp1p2+tmp2p2;
-var tmp12p2=tmp1p2-tmp2p2;
-
-data[dataOff]=tmp10p2+tmp11p2;
-data[dataOff+32]=tmp10p2-tmp11p2;
-
-var z1p2=(tmp12p2+tmp13p2)*0.707106781;
-data[dataOff+16]=tmp13p2+z1p2;
-data[dataOff+48]=tmp13p2-z1p2;
-
-
-tmp10p2=tmp4p2+tmp5p2;
-tmp11p2=tmp5p2+tmp6p2;
-tmp12p2=tmp6p2+tmp7p2;
-
-
-var z5p2=(tmp10p2-tmp12p2)*0.382683433;
-var z2p2=0.541196100*tmp10p2+z5p2;
-var z4p2=1.306562965*tmp12p2+z5p2;
-var z3p2=tmp11p2*0.707106781;
-
-var z11p2=tmp7p2+z3p2;
-var z13p2=tmp7p2-z3p2;
-
-data[dataOff+40]=z13p2+z2p2;
-data[dataOff+24]=z13p2-z2p2;
-data[dataOff+8]=z11p2+z4p2;
-data[dataOff+56]=z11p2-z4p2;
-
-dataOff++;
-}
-
-
-var fDCTQuant;
-for(i=0;i<I64;++i)
-{
-
-fDCTQuant=data[i]*fdtbl[i];
-outputfDCTQuant[i]=fDCTQuant>0.0?fDCTQuant+0.5|0:fDCTQuant-0.5|0;
-
-
-}
-return outputfDCTQuant;
-}
-
-function writeAPP0()
-{
-writeWord(0xFFE0);
-writeWord(16);
-writeByte(0x4A);
-writeByte(0x46);
-writeByte(0x49);
-writeByte(0x46);
-writeByte(0);
-writeByte(1);
-writeByte(1);
-writeByte(0);
-writeWord(1);
-writeWord(1);
-writeByte(0);
-writeByte(0);
-}
-
-function writeSOF0(width,height)
-{
-writeWord(0xFFC0);
-writeWord(17);
-writeByte(8);
-writeWord(height);
-writeWord(width);
-writeByte(3);
-writeByte(1);
-writeByte(0x11);
-writeByte(0);
-writeByte(2);
-writeByte(0x11);
-writeByte(1);
-writeByte(3);
-writeByte(0x11);
-writeByte(1);
-}
-
-function writeDQT()
-{
-writeWord(0xFFDB);
-writeWord(132);
-writeByte(0);
-for(var i=0;i<64;i++){
-writeByte(YTable[i]);
-}
-writeByte(1);
-for(var j=0;j<64;j++){
-writeByte(UVTable[j]);
-}
-}
-
-function writeDHT()
-{
-writeWord(0xFFC4);
-writeWord(0x01A2);
-
-writeByte(0);
-for(var i=0;i<16;i++){
-writeByte(std_dc_luminance_nrcodes[i+1]);
-}
-for(var j=0;j<=11;j++){
-writeByte(std_dc_luminance_values[j]);
-}
-
-writeByte(0x10);
-for(var k=0;k<16;k++){
-writeByte(std_ac_luminance_nrcodes[k+1]);
-}
-for(var l=0;l<=161;l++){
-writeByte(std_ac_luminance_values[l]);
-}
-
-writeByte(1);
-for(var m=0;m<16;m++){
-writeByte(std_dc_chrominance_nrcodes[m+1]);
-}
-for(var n=0;n<=11;n++){
-writeByte(std_dc_chrominance_values[n]);
-}
-
-writeByte(0x11);
-for(var o=0;o<16;o++){
-writeByte(std_ac_chrominance_nrcodes[o+1]);
-}
-for(var p=0;p<=161;p++){
-writeByte(std_ac_chrominance_values[p]);
-}
-}
-
-function writeSOS()
-{
-writeWord(0xFFDA);
-writeWord(12);
-writeByte(3);
-writeByte(1);
-writeByte(0);
-writeByte(2);
-writeByte(0x11);
-writeByte(3);
-writeByte(0x11);
-writeByte(0);
-writeByte(0x3f);
-writeByte(0);
-}
-
-function processDU(CDU,fdtbl,DC,HTDC,HTAC){
-var EOB=HTAC[0x00];
-var M16zeroes=HTAC[0xF0];
-var pos;
-const I16=16;
-const I63=63;
-const I64=64;
-var DU_DCT=fDCTQuant(CDU,fdtbl);
-
-for(var j=0;j<I64;++j){
-DU[ZigZag[j]]=DU_DCT[j];
-}
-var Diff=DU[0]-DC;DC=DU[0];
-
-if(Diff==0){
-writeBits(HTDC[0]);
-}else{
-pos=32767+Diff;
-writeBits(HTDC[category[pos]]);
-writeBits(bitcode[pos]);
-}
-
-var end0pos=63;
-for(;end0pos>0&&DU[end0pos]==0;end0pos--){};
-
-if(end0pos==0){
-writeBits(EOB);
-return DC;
-}
-var i=1;
-var lng;
-while(i<=end0pos){
-var startpos=i;
-for(;DU[i]==0&&i<=end0pos;++i){}
-var nrzeroes=i-startpos;
-if(nrzeroes>=I16){
-lng=nrzeroes>>4;
-for(var nrmarker=1;nrmarker<=lng;++nrmarker)
-writeBits(M16zeroes);
-nrzeroes=nrzeroes&0xF;
-}
-pos=32767+DU[i];
-writeBits(HTAC[(nrzeroes<<4)+category[pos]]);
-writeBits(bitcode[pos]);
-i++;
-}
-if(end0pos!=I63){
-writeBits(EOB);
-}
-return DC;
-}
-
-function initCharLookupTable(){
-var sfcc=String.fromCharCode;
-for(var i=0;i<256;i++){
-clt[i]=sfcc(i);
-}
-}
-
-this.encode=function(image,quality)
-{
-var time_start=new Date().getTime();
-
-if(quality)setQuality(quality);
-
-
-byteout=new Array();
-bytenew=0;
-bytepos=7;
-
-
-writeWord(0xFFD8);
-writeAPP0();
-writeDQT();
-writeSOF0(image.width,image.height);
-writeDHT();
-writeSOS();
-
-
-
-var DCY=0;
-var DCU=0;
-var DCV=0;
-
-bytenew=0;
-bytepos=7;
-
-
-this.encode.displayName="_encode_";
-
-var imageData=image.data;
-var width=image.width;
-var height=image.height;
-
-var quadWidth=width*4;
-var tripleWidth=width*3;
-
-var x,y=0;
-var r,g,b;
-var start,p,col,row,pos;
-while(y<height){
-x=0;
-while(x<quadWidth){
-start=quadWidth*y+x;
-p=start;
-col=-1;
-row=0;
-
-for(pos=0;pos<64;pos++){
-row=pos>>3;
-col=(pos&7)*4;
-p=start+row*quadWidth+col;
-
-if(y+row>=height){
-p-=quadWidth*(y+1+row-height);
-}
-
-if(x+col>=quadWidth){
-p-=x+col-quadWidth+4;
-}
-
-r=imageData[p++];
-g=imageData[p++];
-b=imageData[p++];
-
-
-
-
-
-
-
-
-
-YDU[pos]=(RGB_YUV_TABLE[r]+RGB_YUV_TABLE[g+256>>0]+RGB_YUV_TABLE[b+512>>0]>>16)-128;
-UDU[pos]=(RGB_YUV_TABLE[r+768>>0]+RGB_YUV_TABLE[g+1024>>0]+RGB_YUV_TABLE[b+1280>>0]>>16)-128;
-VDU[pos]=(RGB_YUV_TABLE[r+1280>>0]+RGB_YUV_TABLE[g+1536>>0]+RGB_YUV_TABLE[b+1792>>0]>>16)-128;
-
-}
-
-DCY=processDU(YDU,fdtbl_Y,DCY,YDC_HT,YAC_HT);
-DCU=processDU(UDU,fdtbl_UV,DCU,UVDC_HT,UVAC_HT);
-DCV=processDU(VDU,fdtbl_UV,DCV,UVDC_HT,UVAC_HT);
-x+=32;
-}
-y+=8;
-}
-
-
-
-
-
-if(bytepos>=0){
-var fillbits=[];
-fillbits[1]=bytepos+1;
-fillbits[0]=(1<<bytepos+1)-1;
-writeBits(fillbits);
-}
-
-writeWord(0xFFD9);
-
-
-return new Buffer(byteout);
-
-var jpegDataUri='data:image/jpeg;base64,'+btoa(byteout.join(''));
-
-byteout=[];
-
-
-var duration=new Date().getTime()-time_start;
-
-
-
-return jpegDataUri;
+exports.formatters.j = function(v) {
+  return JSON.stringify(v);
 };
 
-function setQuality(quality){
-if(quality<=0){
-quality=1;
-}
-if(quality>100){
-quality=100;
+
+/**
+ * Colorize log arguments if enabled.
+ *
+ * @api public
+ */
+
+function formatArgs() {
+  var args = arguments;
+  var useColors = this.useColors;
+
+  args[0] = (useColors ? '%c' : '')
+    + this.namespace
+    + (useColors ? ' %c' : ' ')
+    + args[0]
+    + (useColors ? '%c ' : ' ')
+    + '+' + exports.humanize(this.diff);
+
+  if (!useColors) return args;
+
+  var c = 'color: ' + this.color;
+  args = [args[0], c, 'color: inherit'].concat(Array.prototype.slice.call(args, 1));
+
+  // the final "%c" is somewhat tricky, because there could be other
+  // arguments passed either before or after the %c, so we need to
+  // figure out the correct index to insert the CSS into
+  var index = 0;
+  var lastC = 0;
+  args[0].replace(/%[a-z%]/g, function(match) {
+    if ('%%' === match) return;
+    index++;
+    if ('%c' === match) {
+      // we only are interested in the *last* %c
+      // (the user may have provided their own)
+      lastC = index;
+    }
+  });
+
+  args.splice(lastC, 0, c);
+  return args;
 }
 
-if(currentQuality==quality)return;
+/**
+ * Invokes `console.log()` when available.
+ * No-op when `console.log` is not a "function".
+ *
+ * @api public
+ */
 
-var sf=0;
-if(quality<50){
-sf=Math.floor(5000/quality);
-}else{
-sf=Math.floor(200-quality*2);
+function log() {
+  // this hackery is required for IE8/9, where
+  // the `console.log` function doesn't have 'apply'
+  return 'object' === typeof console
+    && console.log
+    && Function.prototype.apply.call(console.log, console, arguments);
 }
 
-initQuantTables(sf);
-currentQuality=quality;
+/**
+ * Save `namespaces`.
+ *
+ * @param {String} namespaces
+ * @api private
+ */
 
+function save(namespaces) {
+  try {
+    if (null == namespaces) {
+      exports.storage.removeItem('debug');
+    } else {
+      exports.storage.debug = namespaces;
+    }
+  } catch(e) {}
 }
 
-function init(){
-var time_start=new Date().getTime();
-if(!quality)quality=50;
+/**
+ * Load `namespaces`.
+ *
+ * @return {String} returns the previously persisted debug modes
+ * @api private
+ */
 
-initCharLookupTable();
-initHuffmanTbl();
-initCategoryNumber();
-initRGBYUVTable();
-
-setQuality(quality);
-var duration=new Date().getTime()-time_start;
-
+function load() {
+  var r;
+  try {
+    r = exports.storage.debug;
+  } catch(e) {}
+  return r;
 }
 
-init();
+/**
+ * Enable namespaces listed in `localStorage.debug` initially.
+ */
+
+exports.enable(load());
+
+/**
+ * Localstorage attempts to return the localstorage.
+ *
+ * This is necessary because safari throws
+ * when a user disables cookies/localstorage
+ * and you attempt to access it.
+ *
+ * @return {LocalStorage}
+ * @api private
+ */
+
+function localstorage(){
+  try {
+    return window.localStorage;
+  } catch (e) {}
+}
+
+},{"./debug":263}],263:[function(require,module,exports){
+
+/**
+ * This is the common logic for both the Node.js and web browser
+ * implementations of `debug()`.
+ *
+ * Expose `debug()` as the module.
+ */
+
+exports = module.exports = debug;
+exports.coerce = coerce;
+exports.disable = disable;
+exports.enable = enable;
+exports.enabled = enabled;
+exports.humanize = require('ms');
+
+/**
+ * The currently active debug mode names, and names to skip.
+ */
+
+exports.names = [];
+exports.skips = [];
+
+/**
+ * Map of special "%n" handling functions, for the debug "format" argument.
+ *
+ * Valid key names are a single, lowercased letter, i.e. "n".
+ */
+
+exports.formatters = {};
+
+/**
+ * Previously assigned color.
+ */
+
+var prevColor = 0;
+
+/**
+ * Previous log timestamp.
+ */
+
+var prevTime;
+
+/**
+ * Select a color.
+ *
+ * @return {Number}
+ * @api private
+ */
+
+function selectColor() {
+  return exports.colors[prevColor++ % exports.colors.length];
+}
+
+/**
+ * Create a debugger with the given `namespace`.
+ *
+ * @param {String} namespace
+ * @return {Function}
+ * @api public
+ */
+
+function debug(namespace) {
+
+  // define the `disabled` version
+  function disabled() {
+  }
+  disabled.enabled = false;
+
+  // define the `enabled` version
+  function enabled() {
+
+    var self = enabled;
+
+    // set `diff` timestamp
+    var curr = +new Date();
+    var ms = curr - (prevTime || curr);
+    self.diff = ms;
+    self.prev = prevTime;
+    self.curr = curr;
+    prevTime = curr;
+
+    // add the `color` if not set
+    if (null == self.useColors) self.useColors = exports.useColors();
+    if (null == self.color && self.useColors) self.color = selectColor();
+
+    var args = Array.prototype.slice.call(arguments);
+
+    args[0] = exports.coerce(args[0]);
+
+    if ('string' !== typeof args[0]) {
+      // anything else let's inspect with %o
+      args = ['%o'].concat(args);
+    }
+
+    // apply any `formatters` transformations
+    var index = 0;
+    args[0] = args[0].replace(/%([a-z%])/g, function(match, format) {
+      // if we encounter an escaped % then don't increase the array index
+      if (match === '%%') return match;
+      index++;
+      var formatter = exports.formatters[format];
+      if ('function' === typeof formatter) {
+        var val = args[index];
+        match = formatter.call(self, val);
+
+        // now we need to remove `args[index]` since it's inlined in the `format`
+        args.splice(index, 1);
+        index--;
+      }
+      return match;
+    });
+
+    if ('function' === typeof exports.formatArgs) {
+      args = exports.formatArgs.apply(self, args);
+    }
+    var logFn = enabled.log || exports.log || console.log.bind(console);
+    logFn.apply(self, args);
+  }
+  enabled.enabled = true;
+
+  var fn = exports.enabled(namespace) ? enabled : disabled;
+
+  fn.namespace = namespace;
+
+  return fn;
+}
+
+/**
+ * Enables a debug mode by namespaces. This can include modes
+ * separated by a colon and wildcards.
+ *
+ * @param {String} namespaces
+ * @api public
+ */
+
+function enable(namespaces) {
+  exports.save(namespaces);
+
+  var split = (namespaces || '').split(/[\s,]+/);
+  var len = split.length;
+
+  for (var i = 0; i < len; i++) {
+    if (!split[i]) continue; // ignore empty strings
+    namespaces = split[i].replace(/\*/g, '.*?');
+    if (namespaces[0] === '-') {
+      exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
+    } else {
+      exports.names.push(new RegExp('^' + namespaces + '$'));
+    }
+  }
+}
+
+/**
+ * Disable debug output.
+ *
+ * @api public
+ */
+
+function disable() {
+  exports.enable('');
+}
+
+/**
+ * Returns true if the given mode name is enabled, false otherwise.
+ *
+ * @param {String} name
+ * @return {Boolean}
+ * @api public
+ */
+
+function enabled(name) {
+  var i, len;
+  for (i = 0, len = exports.skips.length; i < len; i++) {
+    if (exports.skips[i].test(name)) {
+      return false;
+    }
+  }
+  for (i = 0, len = exports.names.length; i < len; i++) {
+    if (exports.names[i].test(name)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+/**
+ * Coerce `val`.
+ *
+ * @param {Mixed} val
+ * @return {Mixed}
+ * @api private
+ */
+
+function coerce(val) {
+  if (val instanceof Error) return val.stack || val.message;
+  return val;
+}
+
+},{"ms":300}],264:[function(require,module,exports){
+
+// this duplicates some work inside of TimelineTreeView, SortedDataGrid and beyond.
+// It's pretty difficult to extract, so we forked.
+
+module.exports = function(WebInspector) {
+
+  function TimelineModelTreeView(model) {
+    this._rootNode = model;
+  }
+
+  TimelineModelTreeView.prototype.sortingChanged = function(sortItem, sortOrder) {
+    if (!sortItem)
+      return;
+    var sortFunction;
+    switch (sortItem) {
+      case 'startTime':
+        sortFunction = compareStartTime;
+        break;
+      case 'self':
+        sortFunction = compareNumericField.bind(null, 'selfTime');
+        break;
+      case 'total':
+        sortFunction = compareNumericField.bind(null, 'totalTime');
+        break;
+      case 'activity':
+        sortFunction = compareName;
+        break;
+      default:
+        console.assert(false, 'Unknown sort field: ' + sortItem);
+        return;
+    }
+    return this.sortNodes(sortFunction, sortOrder !== 'asc');
+
+    function compareNumericField(field, a, b) {
+      var nodeA = (a[1]);
+      var nodeB = (b[1]);
+      return nodeA[field] - nodeB[field];
+    }
+
+    function compareStartTime(a, b) {
+      var nodeA = (a[1]);
+      var nodeB = (b[1]);
+      return nodeA.event.startTime - nodeB.event.startTime;
+    }
+
+    function compareName(a, b) {
+      var nodeA = (a[1]);
+      var nodeB = (b[1]);
+      var nameA = WebInspector.TimelineTreeView.eventNameForSorting(nodeA.event);
+      var nameB = WebInspector.TimelineTreeView.eventNameForSorting(nodeB.event);
+      return nameA.localeCompare(nameB);
+    }
+  };
+
+  TimelineModelTreeView.prototype.sortNodes = function(comparator, reverseMode) {
+    this._sortingFunction = WebInspector.SortableDataGrid.Comparator.bind(null, comparator, reverseMode);
+    sortChildren(this._rootNode, this._sortingFunction, reverseMode);
+  };
+
+  /**
+   * sortChildren has major changes, as it now works on Maps rather than Arrays
+   * @param  {WebInspector.TimelineProfileTree.Node} parent
+   * @param  {any} sortingFunction
+   */
+  function sortChildren(parent, sortingFunction) {
+    if (!parent.children) return;
+    parent.children = new Map([...parent.children.entries()].sort(sortingFunction));
+    for (var i = 0; i < parent.children.length; ++i)
+      recalculateSiblings(parent.children[i], i);
+    for (var child of parent.children.values())
+      sortChildren(child, sortingFunction);
+  }
+
+  /**
+   * @param  {WebInspector.TimelineProfileTree.Node} node
+   * @param  {any} myIndex
+   */
+  function recalculateSiblings(node, myIndex) {
+    if (!node.parent)
+      return;
+
+    var previousChild = node.parent.children[myIndex - 1] || null;
+    if (previousChild)
+      previousChild.nextSibling = node;
+    node.previousSibling = previousChild;
+
+    var nextChild = node.parent.children[myIndex + 1] || null;
+    if (nextChild)
+      nextChild.previousSibling = node;
+    node.nextSibling = nextChild;
+  }
+
+  return TimelineModelTreeView;
 
 };
-module.exports=encode;
 
-function encode(imgData,qu){
-if(typeof qu==='undefined')qu=50;
-var encoder=new JPEGEncoder(qu);
-var data=encoder.encode(imgData,qu);
-return{
-data:data,
-width:imgData.width,
-height:imgData.height};
+},{}],265:[function(require,module,exports){
+/**
+ * @fileoverview gl-matrix - High performance matrix and vector operations
+ * @author Brandon Jones
+ * @author Colin MacKenzie IV
+ * @version 2.3.2
+ */
 
+/* Copyright (c) 2015, Brandon Jones, Colin MacKenzie IV.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE. */
+// END HEADER
+
+exports.glMatrix = require("./gl-matrix/common.js");
+exports.mat2 = require("./gl-matrix/mat2.js");
+exports.mat2d = require("./gl-matrix/mat2d.js");
+exports.mat3 = require("./gl-matrix/mat3.js");
+exports.mat4 = require("./gl-matrix/mat4.js");
+exports.quat = require("./gl-matrix/quat.js");
+exports.vec2 = require("./gl-matrix/vec2.js");
+exports.vec3 = require("./gl-matrix/vec3.js");
+exports.vec4 = require("./gl-matrix/vec4.js");
+},{"./gl-matrix/common.js":266,"./gl-matrix/mat2.js":267,"./gl-matrix/mat2d.js":268,"./gl-matrix/mat3.js":269,"./gl-matrix/mat4.js":270,"./gl-matrix/quat.js":271,"./gl-matrix/vec2.js":272,"./gl-matrix/vec3.js":273,"./gl-matrix/vec4.js":274}],266:[function(require,module,exports){
+/* Copyright (c) 2015, Brandon Jones, Colin MacKenzie IV.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE. */
+
+/**
+ * @class Common utilities
+ * @name glMatrix
+ */
+var glMatrix = {};
+
+// Configuration Constants
+glMatrix.EPSILON = 0.000001;
+glMatrix.ARRAY_TYPE = (typeof Float32Array !== 'undefined') ? Float32Array : Array;
+glMatrix.RANDOM = Math.random;
+glMatrix.ENABLE_SIMD = false;
+
+// Capability detection
+glMatrix.SIMD_AVAILABLE = (glMatrix.ARRAY_TYPE === Float32Array) && ('SIMD' in this);
+glMatrix.USE_SIMD = glMatrix.ENABLE_SIMD && glMatrix.SIMD_AVAILABLE;
+
+/**
+ * Sets the type of array used when creating new vectors and matrices
+ *
+ * @param {Type} type Array type, such as Float32Array or Array
+ */
+glMatrix.setMatrixArrayType = function(type) {
+    glMatrix.ARRAY_TYPE = type;
 }
 
+var degree = Math.PI / 180;
 
-function getImageDataFromImage(idOrElement){
-var theImg=typeof idOrElement=='string'?document.getElementById(idOrElement):idOrElement;
-var cvs=document.createElement('canvas');
-cvs.width=theImg.width;
-cvs.height=theImg.height;
-var ctx=cvs.getContext("2d");
-ctx.drawImage(theImg,0,0);
-
-return ctx.getImageData(0,0,cvs.width,cvs.height);
+/**
+* Convert Degree To Radian
+*
+* @param {Number} Angle in Degrees
+*/
+glMatrix.toRadian = function(a){
+     return a * degree;
 }
 
-}).call(this,require("buffer").Buffer);
-},{"buffer":199}],269:[function(require,module,exports){
-(function(global){
-
-
-
-
-
-
-;(function(){
-
-
-
-
-
-var block={
-newline:/^\n+/,
-code:/^( {4}[^\n]+\n*)+/,
-fences:noop,
-hr:/^( *[-*_]){3,} *(?:\n+|$)/,
-heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
-nptable:noop,
-lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
-blockquote:/^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,
-list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
-html:/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,
-def:/^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
-table:noop,
-paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
-text:/^[^\n]+/};
-
-
-block.bullet=/(?:[*+-]|\d+\.)/;
-block.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
-block.item=replace(block.item,'gm')(
-/bull/g,block.bullet)();
-
-
-block.list=replace(block.list)(
-/bull/g,block.bullet)(
-'hr','\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))')(
-'def','\\n+(?='+block.def.source+')')();
-
-
-block.blockquote=replace(block.blockquote)(
-'def',block.def)();
-
-
-block._tag='(?!(?:'+
-'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'+
-'|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'+
-'|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b';
-
-block.html=replace(block.html)(
-'comment',/<!--[\s\S]*?-->/)(
-'closed',/<(tag)[\s\S]+?<\/\1>/)(
-'closing',/<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)(
-/tag/g,block._tag)();
-
-
-block.paragraph=replace(block.paragraph)(
-'hr',block.hr)(
-'heading',block.heading)(
-'lheading',block.lheading)(
-'blockquote',block.blockquote)(
-'tag','<'+block._tag)(
-'def',block.def)();
-
-
-
-
-
-
-block.normal=merge({},block);
-
-
-
-
-
-block.gfm=merge({},block.normal,{
-fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,
-paragraph:/^/,
-heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/});
-
-
-block.gfm.paragraph=replace(block.paragraph)(
-'(?!','(?!'+
-block.gfm.fences.source.replace('\\1','\\2')+'|'+
-block.list.source.replace('\\1','\\3')+'|')();
-
-
-
-
-
-
-block.tables=merge({},block.gfm,{
-nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
-table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/});
-
-
-
-
-
-
-function Lexer(options){
-this.tokens=[];
-this.tokens.links={};
-this.options=options||marked.defaults;
-this.rules=block.normal;
-
-if(this.options.gfm){
-if(this.options.tables){
-this.rules=block.tables;
-}else{
-this.rules=block.gfm;
-}
-}
+/**
+ * Tests whether or not the arguments have approximately the same value, within an absolute
+ * or relative tolerance of glMatrix.EPSILON (an absolute tolerance is used for values less 
+ * than or equal to 1.0, and a relative tolerance is used for larger values)
+ * 
+ * @param {Number} a The first number to test.
+ * @param {Number} b The second number to test.
+ * @returns {Boolean} True if the numbers are approximately equal, false otherwise.
+ */
+glMatrix.equals = function(a, b) {
+	return Math.abs(a - b) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a), Math.abs(b));
 }
 
+module.exports = glMatrix;
+
+},{}],267:[function(require,module,exports){
+/* Copyright (c) 2015, Brandon Jones, Colin MacKenzie IV.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE. */
+
+var glMatrix = require("./common.js");
+
+/**
+ * @class 2x2 Matrix
+ * @name mat2
+ */
+var mat2 = {};
+
+/**
+ * Creates a new identity mat2
+ *
+ * @returns {mat2} a new 2x2 matrix
+ */
+mat2.create = function() {
+    var out = new glMatrix.ARRAY_TYPE(4);
+    out[0] = 1;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 1;
+    return out;
+};
+
+/**
+ * Creates a new mat2 initialized with values from an existing matrix
+ *
+ * @param {mat2} a matrix to clone
+ * @returns {mat2} a new 2x2 matrix
+ */
+mat2.clone = function(a) {
+    var out = new glMatrix.ARRAY_TYPE(4);
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[3];
+    return out;
+};
+
+/**
+ * Copy the values from one mat2 to another
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {mat2} a the source matrix
+ * @returns {mat2} out
+ */
+mat2.copy = function(out, a) {
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[3];
+    return out;
+};
+
+/**
+ * Set a mat2 to the identity matrix
+ *
+ * @param {mat2} out the receiving matrix
+ * @returns {mat2} out
+ */
+mat2.identity = function(out) {
+    out[0] = 1;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 1;
+    return out;
+};
+
+/**
+ * Create a new mat2 with the given values
+ *
+ * @param {Number} m00 Component in column 0, row 0 position (index 0)
+ * @param {Number} m01 Component in column 0, row 1 position (index 1)
+ * @param {Number} m10 Component in column 1, row 0 position (index 2)
+ * @param {Number} m11 Component in column 1, row 1 position (index 3)
+ * @returns {mat2} out A new 2x2 matrix
+ */
+mat2.fromValues = function(m00, m01, m10, m11) {
+    var out = new glMatrix.ARRAY_TYPE(4);
+    out[0] = m00;
+    out[1] = m01;
+    out[2] = m10;
+    out[3] = m11;
+    return out;
+};
+
+/**
+ * Set the components of a mat2 to the given values
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {Number} m00 Component in column 0, row 0 position (index 0)
+ * @param {Number} m01 Component in column 0, row 1 position (index 1)
+ * @param {Number} m10 Component in column 1, row 0 position (index 2)
+ * @param {Number} m11 Component in column 1, row 1 position (index 3)
+ * @returns {mat2} out
+ */
+mat2.set = function(out, m00, m01, m10, m11) {
+    out[0] = m00;
+    out[1] = m01;
+    out[2] = m10;
+    out[3] = m11;
+    return out;
+};
 
 
+/**
+ * Transpose the values of a mat2
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {mat2} a the source matrix
+ * @returns {mat2} out
+ */
+mat2.transpose = function(out, a) {
+    // If we are transposing ourselves we can skip a few steps but have to cache some values
+    if (out === a) {
+        var a1 = a[1];
+        out[1] = a[2];
+        out[2] = a1;
+    } else {
+        out[0] = a[0];
+        out[1] = a[2];
+        out[2] = a[1];
+        out[3] = a[3];
+    }
+    
+    return out;
+};
+
+/**
+ * Inverts a mat2
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {mat2} a the source matrix
+ * @returns {mat2} out
+ */
+mat2.invert = function(out, a) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3],
+
+        // Calculate the determinant
+        det = a0 * a3 - a2 * a1;
+
+    if (!det) {
+        return null;
+    }
+    det = 1.0 / det;
+    
+    out[0] =  a3 * det;
+    out[1] = -a1 * det;
+    out[2] = -a2 * det;
+    out[3] =  a0 * det;
+
+    return out;
+};
+
+/**
+ * Calculates the adjugate of a mat2
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {mat2} a the source matrix
+ * @returns {mat2} out
+ */
+mat2.adjoint = function(out, a) {
+    // Caching this value is nessecary if out == a
+    var a0 = a[0];
+    out[0] =  a[3];
+    out[1] = -a[1];
+    out[2] = -a[2];
+    out[3] =  a0;
+
+    return out;
+};
+
+/**
+ * Calculates the determinant of a mat2
+ *
+ * @param {mat2} a the source matrix
+ * @returns {Number} determinant of a
+ */
+mat2.determinant = function (a) {
+    return a[0] * a[3] - a[2] * a[1];
+};
+
+/**
+ * Multiplies two mat2's
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {mat2} a the first operand
+ * @param {mat2} b the second operand
+ * @returns {mat2} out
+ */
+mat2.multiply = function (out, a, b) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3];
+    var b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
+    out[0] = a0 * b0 + a2 * b1;
+    out[1] = a1 * b0 + a3 * b1;
+    out[2] = a0 * b2 + a2 * b3;
+    out[3] = a1 * b2 + a3 * b3;
+    return out;
+};
+
+/**
+ * Alias for {@link mat2.multiply}
+ * @function
+ */
+mat2.mul = mat2.multiply;
+
+/**
+ * Rotates a mat2 by the given angle
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {mat2} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat2} out
+ */
+mat2.rotate = function (out, a, rad) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3],
+        s = Math.sin(rad),
+        c = Math.cos(rad);
+    out[0] = a0 *  c + a2 * s;
+    out[1] = a1 *  c + a3 * s;
+    out[2] = a0 * -s + a2 * c;
+    out[3] = a1 * -s + a3 * c;
+    return out;
+};
+
+/**
+ * Scales the mat2 by the dimensions in the given vec2
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {mat2} a the matrix to rotate
+ * @param {vec2} v the vec2 to scale the matrix by
+ * @returns {mat2} out
+ **/
+mat2.scale = function(out, a, v) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3],
+        v0 = v[0], v1 = v[1];
+    out[0] = a0 * v0;
+    out[1] = a1 * v0;
+    out[2] = a2 * v1;
+    out[3] = a3 * v1;
+    return out;
+};
+
+/**
+ * Creates a matrix from a given angle
+ * This is equivalent to (but much faster than):
+ *
+ *     mat2.identity(dest);
+ *     mat2.rotate(dest, dest, rad);
+ *
+ * @param {mat2} out mat2 receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat2} out
+ */
+mat2.fromRotation = function(out, rad) {
+    var s = Math.sin(rad),
+        c = Math.cos(rad);
+    out[0] = c;
+    out[1] = s;
+    out[2] = -s;
+    out[3] = c;
+    return out;
+}
+
+/**
+ * Creates a matrix from a vector scaling
+ * This is equivalent to (but much faster than):
+ *
+ *     mat2.identity(dest);
+ *     mat2.scale(dest, dest, vec);
+ *
+ * @param {mat2} out mat2 receiving operation result
+ * @param {vec2} v Scaling vector
+ * @returns {mat2} out
+ */
+mat2.fromScaling = function(out, v) {
+    out[0] = v[0];
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = v[1];
+    return out;
+}
+
+/**
+ * Returns a string representation of a mat2
+ *
+ * @param {mat2} mat matrix to represent as a string
+ * @returns {String} string representation of the matrix
+ */
+mat2.str = function (a) {
+    return 'mat2(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + a[3] + ')';
+};
+
+/**
+ * Returns Frobenius norm of a mat2
+ *
+ * @param {mat2} a the matrix to calculate Frobenius norm of
+ * @returns {Number} Frobenius norm
+ */
+mat2.frob = function (a) {
+    return(Math.sqrt(Math.pow(a[0], 2) + Math.pow(a[1], 2) + Math.pow(a[2], 2) + Math.pow(a[3], 2)))
+};
+
+/**
+ * Returns L, D and U matrices (Lower triangular, Diagonal and Upper triangular) by factorizing the input matrix
+ * @param {mat2} L the lower triangular matrix 
+ * @param {mat2} D the diagonal matrix 
+ * @param {mat2} U the upper triangular matrix 
+ * @param {mat2} a the input matrix to factorize
+ */
+
+mat2.LDU = function (L, D, U, a) { 
+    L[2] = a[2]/a[0]; 
+    U[0] = a[0]; 
+    U[1] = a[1]; 
+    U[3] = a[3] - L[2] * U[1]; 
+    return [L, D, U];       
+}; 
+
+/**
+ * Adds two mat2's
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {mat2} a the first operand
+ * @param {mat2} b the second operand
+ * @returns {mat2} out
+ */
+mat2.add = function(out, a, b) {
+    out[0] = a[0] + b[0];
+    out[1] = a[1] + b[1];
+    out[2] = a[2] + b[2];
+    out[3] = a[3] + b[3];
+    return out;
+};
+
+/**
+ * Subtracts matrix b from matrix a
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {mat2} a the first operand
+ * @param {mat2} b the second operand
+ * @returns {mat2} out
+ */
+mat2.subtract = function(out, a, b) {
+    out[0] = a[0] - b[0];
+    out[1] = a[1] - b[1];
+    out[2] = a[2] - b[2];
+    out[3] = a[3] - b[3];
+    return out;
+};
+
+/**
+ * Alias for {@link mat2.subtract}
+ * @function
+ */
+mat2.sub = mat2.subtract;
+
+/**
+ * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {mat2} a The first matrix.
+ * @param {mat2} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+mat2.exactEquals = function (a, b) {
+    return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3];
+};
+
+/**
+ * Returns whether or not the matrices have approximately the same elements in the same position.
+ *
+ * @param {mat2} a The first matrix.
+ * @param {mat2} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+mat2.equals = function (a, b) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3];
+    var b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
+    return (Math.abs(a0 - b0) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a0), Math.abs(b0)) &&
+            Math.abs(a1 - b1) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a1), Math.abs(b1)) &&
+            Math.abs(a2 - b2) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a2), Math.abs(b2)) &&
+            Math.abs(a3 - b3) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a3), Math.abs(b3)));
+};
+
+/**
+ * Multiply each element of the matrix by a scalar.
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {mat2} a the matrix to scale
+ * @param {Number} b amount to scale the matrix's elements by
+ * @returns {mat2} out
+ */
+mat2.multiplyScalar = function(out, a, b) {
+    out[0] = a[0] * b;
+    out[1] = a[1] * b;
+    out[2] = a[2] * b;
+    out[3] = a[3] * b;
+    return out;
+};
+
+/**
+ * Adds two mat2's after multiplying each element of the second operand by a scalar value.
+ *
+ * @param {mat2} out the receiving vector
+ * @param {mat2} a the first operand
+ * @param {mat2} b the second operand
+ * @param {Number} scale the amount to scale b's elements by before adding
+ * @returns {mat2} out
+ */
+mat2.multiplyScalarAndAdd = function(out, a, b, scale) {
+    out[0] = a[0] + (b[0] * scale);
+    out[1] = a[1] + (b[1] * scale);
+    out[2] = a[2] + (b[2] * scale);
+    out[3] = a[3] + (b[3] * scale);
+    return out;
+};
+
+module.exports = mat2;
+
+},{"./common.js":266}],268:[function(require,module,exports){
+/* Copyright (c) 2015, Brandon Jones, Colin MacKenzie IV.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE. */
+
+var glMatrix = require("./common.js");
+
+/**
+ * @class 2x3 Matrix
+ * @name mat2d
+ * 
+ * @description 
+ * A mat2d contains six elements defined as:
+ * <pre>
+ * [a, c, tx,
+ *  b, d, ty]
+ * </pre>
+ * This is a short form for the 3x3 matrix:
+ * <pre>
+ * [a, c, tx,
+ *  b, d, ty,
+ *  0, 0, 1]
+ * </pre>
+ * The last row is ignored so the array is shorter and operations are faster.
+ */
+var mat2d = {};
+
+/**
+ * Creates a new identity mat2d
+ *
+ * @returns {mat2d} a new 2x3 matrix
+ */
+mat2d.create = function() {
+    var out = new glMatrix.ARRAY_TYPE(6);
+    out[0] = 1;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 1;
+    out[4] = 0;
+    out[5] = 0;
+    return out;
+};
+
+/**
+ * Creates a new mat2d initialized with values from an existing matrix
+ *
+ * @param {mat2d} a matrix to clone
+ * @returns {mat2d} a new 2x3 matrix
+ */
+mat2d.clone = function(a) {
+    var out = new glMatrix.ARRAY_TYPE(6);
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[3];
+    out[4] = a[4];
+    out[5] = a[5];
+    return out;
+};
+
+/**
+ * Copy the values from one mat2d to another
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {mat2d} a the source matrix
+ * @returns {mat2d} out
+ */
+mat2d.copy = function(out, a) {
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[3];
+    out[4] = a[4];
+    out[5] = a[5];
+    return out;
+};
+
+/**
+ * Set a mat2d to the identity matrix
+ *
+ * @param {mat2d} out the receiving matrix
+ * @returns {mat2d} out
+ */
+mat2d.identity = function(out) {
+    out[0] = 1;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 1;
+    out[4] = 0;
+    out[5] = 0;
+    return out;
+};
+
+/**
+ * Create a new mat2d with the given values
+ *
+ * @param {Number} a Component A (index 0)
+ * @param {Number} b Component B (index 1)
+ * @param {Number} c Component C (index 2)
+ * @param {Number} d Component D (index 3)
+ * @param {Number} tx Component TX (index 4)
+ * @param {Number} ty Component TY (index 5)
+ * @returns {mat2d} A new mat2d
+ */
+mat2d.fromValues = function(a, b, c, d, tx, ty) {
+    var out = new glMatrix.ARRAY_TYPE(6);
+    out[0] = a;
+    out[1] = b;
+    out[2] = c;
+    out[3] = d;
+    out[4] = tx;
+    out[5] = ty;
+    return out;
+};
+
+/**
+ * Set the components of a mat2d to the given values
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {Number} a Component A (index 0)
+ * @param {Number} b Component B (index 1)
+ * @param {Number} c Component C (index 2)
+ * @param {Number} d Component D (index 3)
+ * @param {Number} tx Component TX (index 4)
+ * @param {Number} ty Component TY (index 5)
+ * @returns {mat2d} out
+ */
+mat2d.set = function(out, a, b, c, d, tx, ty) {
+    out[0] = a;
+    out[1] = b;
+    out[2] = c;
+    out[3] = d;
+    out[4] = tx;
+    out[5] = ty;
+    return out;
+};
+
+/**
+ * Inverts a mat2d
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {mat2d} a the source matrix
+ * @returns {mat2d} out
+ */
+mat2d.invert = function(out, a) {
+    var aa = a[0], ab = a[1], ac = a[2], ad = a[3],
+        atx = a[4], aty = a[5];
+
+    var det = aa * ad - ab * ac;
+    if(!det){
+        return null;
+    }
+    det = 1.0 / det;
+
+    out[0] = ad * det;
+    out[1] = -ab * det;
+    out[2] = -ac * det;
+    out[3] = aa * det;
+    out[4] = (ac * aty - ad * atx) * det;
+    out[5] = (ab * atx - aa * aty) * det;
+    return out;
+};
+
+/**
+ * Calculates the determinant of a mat2d
+ *
+ * @param {mat2d} a the source matrix
+ * @returns {Number} determinant of a
+ */
+mat2d.determinant = function (a) {
+    return a[0] * a[3] - a[1] * a[2];
+};
+
+/**
+ * Multiplies two mat2d's
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {mat2d} a the first operand
+ * @param {mat2d} b the second operand
+ * @returns {mat2d} out
+ */
+mat2d.multiply = function (out, a, b) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5],
+        b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3], b4 = b[4], b5 = b[5];
+    out[0] = a0 * b0 + a2 * b1;
+    out[1] = a1 * b0 + a3 * b1;
+    out[2] = a0 * b2 + a2 * b3;
+    out[3] = a1 * b2 + a3 * b3;
+    out[4] = a0 * b4 + a2 * b5 + a4;
+    out[5] = a1 * b4 + a3 * b5 + a5;
+    return out;
+};
+
+/**
+ * Alias for {@link mat2d.multiply}
+ * @function
+ */
+mat2d.mul = mat2d.multiply;
+
+/**
+ * Rotates a mat2d by the given angle
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {mat2d} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat2d} out
+ */
+mat2d.rotate = function (out, a, rad) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5],
+        s = Math.sin(rad),
+        c = Math.cos(rad);
+    out[0] = a0 *  c + a2 * s;
+    out[1] = a1 *  c + a3 * s;
+    out[2] = a0 * -s + a2 * c;
+    out[3] = a1 * -s + a3 * c;
+    out[4] = a4;
+    out[5] = a5;
+    return out;
+};
+
+/**
+ * Scales the mat2d by the dimensions in the given vec2
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {mat2d} a the matrix to translate
+ * @param {vec2} v the vec2 to scale the matrix by
+ * @returns {mat2d} out
+ **/
+mat2d.scale = function(out, a, v) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5],
+        v0 = v[0], v1 = v[1];
+    out[0] = a0 * v0;
+    out[1] = a1 * v0;
+    out[2] = a2 * v1;
+    out[3] = a3 * v1;
+    out[4] = a4;
+    out[5] = a5;
+    return out;
+};
+
+/**
+ * Translates the mat2d by the dimensions in the given vec2
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {mat2d} a the matrix to translate
+ * @param {vec2} v the vec2 to translate the matrix by
+ * @returns {mat2d} out
+ **/
+mat2d.translate = function(out, a, v) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5],
+        v0 = v[0], v1 = v[1];
+    out[0] = a0;
+    out[1] = a1;
+    out[2] = a2;
+    out[3] = a3;
+    out[4] = a0 * v0 + a2 * v1 + a4;
+    out[5] = a1 * v0 + a3 * v1 + a5;
+    return out;
+};
+
+/**
+ * Creates a matrix from a given angle
+ * This is equivalent to (but much faster than):
+ *
+ *     mat2d.identity(dest);
+ *     mat2d.rotate(dest, dest, rad);
+ *
+ * @param {mat2d} out mat2d receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat2d} out
+ */
+mat2d.fromRotation = function(out, rad) {
+    var s = Math.sin(rad), c = Math.cos(rad);
+    out[0] = c;
+    out[1] = s;
+    out[2] = -s;
+    out[3] = c;
+    out[4] = 0;
+    out[5] = 0;
+    return out;
+}
+
+/**
+ * Creates a matrix from a vector scaling
+ * This is equivalent to (but much faster than):
+ *
+ *     mat2d.identity(dest);
+ *     mat2d.scale(dest, dest, vec);
+ *
+ * @param {mat2d} out mat2d receiving operation result
+ * @param {vec2} v Scaling vector
+ * @returns {mat2d} out
+ */
+mat2d.fromScaling = function(out, v) {
+    out[0] = v[0];
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = v[1];
+    out[4] = 0;
+    out[5] = 0;
+    return out;
+}
+
+/**
+ * Creates a matrix from a vector translation
+ * This is equivalent to (but much faster than):
+ *
+ *     mat2d.identity(dest);
+ *     mat2d.translate(dest, dest, vec);
+ *
+ * @param {mat2d} out mat2d receiving operation result
+ * @param {vec2} v Translation vector
+ * @returns {mat2d} out
+ */
+mat2d.fromTranslation = function(out, v) {
+    out[0] = 1;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 1;
+    out[4] = v[0];
+    out[5] = v[1];
+    return out;
+}
+
+/**
+ * Returns a string representation of a mat2d
+ *
+ * @param {mat2d} a matrix to represent as a string
+ * @returns {String} string representation of the matrix
+ */
+mat2d.str = function (a) {
+    return 'mat2d(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + 
+                    a[3] + ', ' + a[4] + ', ' + a[5] + ')';
+};
+
+/**
+ * Returns Frobenius norm of a mat2d
+ *
+ * @param {mat2d} a the matrix to calculate Frobenius norm of
+ * @returns {Number} Frobenius norm
+ */
+mat2d.frob = function (a) { 
+    return(Math.sqrt(Math.pow(a[0], 2) + Math.pow(a[1], 2) + Math.pow(a[2], 2) + Math.pow(a[3], 2) + Math.pow(a[4], 2) + Math.pow(a[5], 2) + 1))
+}; 
+
+/**
+ * Adds two mat2d's
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {mat2d} a the first operand
+ * @param {mat2d} b the second operand
+ * @returns {mat2d} out
+ */
+mat2d.add = function(out, a, b) {
+    out[0] = a[0] + b[0];
+    out[1] = a[1] + b[1];
+    out[2] = a[2] + b[2];
+    out[3] = a[3] + b[3];
+    out[4] = a[4] + b[4];
+    out[5] = a[5] + b[5];
+    return out;
+};
+
+/**
+ * Subtracts matrix b from matrix a
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {mat2d} a the first operand
+ * @param {mat2d} b the second operand
+ * @returns {mat2d} out
+ */
+mat2d.subtract = function(out, a, b) {
+    out[0] = a[0] - b[0];
+    out[1] = a[1] - b[1];
+    out[2] = a[2] - b[2];
+    out[3] = a[3] - b[3];
+    out[4] = a[4] - b[4];
+    out[5] = a[5] - b[5];
+    return out;
+};
+
+/**
+ * Alias for {@link mat2d.subtract}
+ * @function
+ */
+mat2d.sub = mat2d.subtract;
+
+/**
+ * Multiply each element of the matrix by a scalar.
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {mat2d} a the matrix to scale
+ * @param {Number} b amount to scale the matrix's elements by
+ * @returns {mat2d} out
+ */
+mat2d.multiplyScalar = function(out, a, b) {
+    out[0] = a[0] * b;
+    out[1] = a[1] * b;
+    out[2] = a[2] * b;
+    out[3] = a[3] * b;
+    out[4] = a[4] * b;
+    out[5] = a[5] * b;
+    return out;
+};
+
+/**
+ * Adds two mat2d's after multiplying each element of the second operand by a scalar value.
+ *
+ * @param {mat2d} out the receiving vector
+ * @param {mat2d} a the first operand
+ * @param {mat2d} b the second operand
+ * @param {Number} scale the amount to scale b's elements by before adding
+ * @returns {mat2d} out
+ */
+mat2d.multiplyScalarAndAdd = function(out, a, b, scale) {
+    out[0] = a[0] + (b[0] * scale);
+    out[1] = a[1] + (b[1] * scale);
+    out[2] = a[2] + (b[2] * scale);
+    out[3] = a[3] + (b[3] * scale);
+    out[4] = a[4] + (b[4] * scale);
+    out[5] = a[5] + (b[5] * scale);
+    return out;
+};
+
+/**
+ * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {mat2d} a The first matrix.
+ * @param {mat2d} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+mat2d.exactEquals = function (a, b) {
+    return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5];
+};
+
+/**
+ * Returns whether or not the matrices have approximately the same elements in the same position.
+ *
+ * @param {mat2d} a The first matrix.
+ * @param {mat2d} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+mat2d.equals = function (a, b) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5];
+    var b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3], b4 = b[4], b5 = b[5];
+    return (Math.abs(a0 - b0) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a0), Math.abs(b0)) &&
+            Math.abs(a1 - b1) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a1), Math.abs(b1)) &&
+            Math.abs(a2 - b2) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a2), Math.abs(b2)) &&
+            Math.abs(a3 - b3) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a3), Math.abs(b3)) &&
+            Math.abs(a4 - b4) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a4), Math.abs(b4)) &&
+            Math.abs(a5 - b5) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a5), Math.abs(b5)));
+};
+
+module.exports = mat2d;
+
+},{"./common.js":266}],269:[function(require,module,exports){
+/* Copyright (c) 2015, Brandon Jones, Colin MacKenzie IV.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE. */
+
+var glMatrix = require("./common.js");
+
+/**
+ * @class 3x3 Matrix
+ * @name mat3
+ */
+var mat3 = {};
+
+/**
+ * Creates a new identity mat3
+ *
+ * @returns {mat3} a new 3x3 matrix
+ */
+mat3.create = function() {
+    var out = new glMatrix.ARRAY_TYPE(9);
+    out[0] = 1;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 0;
+    out[4] = 1;
+    out[5] = 0;
+    out[6] = 0;
+    out[7] = 0;
+    out[8] = 1;
+    return out;
+};
+
+/**
+ * Copies the upper-left 3x3 values into the given mat3.
+ *
+ * @param {mat3} out the receiving 3x3 matrix
+ * @param {mat4} a   the source 4x4 matrix
+ * @returns {mat3} out
+ */
+mat3.fromMat4 = function(out, a) {
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[4];
+    out[4] = a[5];
+    out[5] = a[6];
+    out[6] = a[8];
+    out[7] = a[9];
+    out[8] = a[10];
+    return out;
+};
+
+/**
+ * Creates a new mat3 initialized with values from an existing matrix
+ *
+ * @param {mat3} a matrix to clone
+ * @returns {mat3} a new 3x3 matrix
+ */
+mat3.clone = function(a) {
+    var out = new glMatrix.ARRAY_TYPE(9);
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[3];
+    out[4] = a[4];
+    out[5] = a[5];
+    out[6] = a[6];
+    out[7] = a[7];
+    out[8] = a[8];
+    return out;
+};
+
+/**
+ * Copy the values from one mat3 to another
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat3} a the source matrix
+ * @returns {mat3} out
+ */
+mat3.copy = function(out, a) {
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[3];
+    out[4] = a[4];
+    out[5] = a[5];
+    out[6] = a[6];
+    out[7] = a[7];
+    out[8] = a[8];
+    return out;
+};
+
+/**
+ * Create a new mat3 with the given values
+ *
+ * @param {Number} m00 Component in column 0, row 0 position (index 0)
+ * @param {Number} m01 Component in column 0, row 1 position (index 1)
+ * @param {Number} m02 Component in column 0, row 2 position (index 2)
+ * @param {Number} m10 Component in column 1, row 0 position (index 3)
+ * @param {Number} m11 Component in column 1, row 1 position (index 4)
+ * @param {Number} m12 Component in column 1, row 2 position (index 5)
+ * @param {Number} m20 Component in column 2, row 0 position (index 6)
+ * @param {Number} m21 Component in column 2, row 1 position (index 7)
+ * @param {Number} m22 Component in column 2, row 2 position (index 8)
+ * @returns {mat3} A new mat3
+ */
+mat3.fromValues = function(m00, m01, m02, m10, m11, m12, m20, m21, m22) {
+    var out = new glMatrix.ARRAY_TYPE(9);
+    out[0] = m00;
+    out[1] = m01;
+    out[2] = m02;
+    out[3] = m10;
+    out[4] = m11;
+    out[5] = m12;
+    out[6] = m20;
+    out[7] = m21;
+    out[8] = m22;
+    return out;
+};
+
+/**
+ * Set the components of a mat3 to the given values
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {Number} m00 Component in column 0, row 0 position (index 0)
+ * @param {Number} m01 Component in column 0, row 1 position (index 1)
+ * @param {Number} m02 Component in column 0, row 2 position (index 2)
+ * @param {Number} m10 Component in column 1, row 0 position (index 3)
+ * @param {Number} m11 Component in column 1, row 1 position (index 4)
+ * @param {Number} m12 Component in column 1, row 2 position (index 5)
+ * @param {Number} m20 Component in column 2, row 0 position (index 6)
+ * @param {Number} m21 Component in column 2, row 1 position (index 7)
+ * @param {Number} m22 Component in column 2, row 2 position (index 8)
+ * @returns {mat3} out
+ */
+mat3.set = function(out, m00, m01, m02, m10, m11, m12, m20, m21, m22) {
+    out[0] = m00;
+    out[1] = m01;
+    out[2] = m02;
+    out[3] = m10;
+    out[4] = m11;
+    out[5] = m12;
+    out[6] = m20;
+    out[7] = m21;
+    out[8] = m22;
+    return out;
+};
+
+/**
+ * Set a mat3 to the identity matrix
+ *
+ * @param {mat3} out the receiving matrix
+ * @returns {mat3} out
+ */
+mat3.identity = function(out) {
+    out[0] = 1;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 0;
+    out[4] = 1;
+    out[5] = 0;
+    out[6] = 0;
+    out[7] = 0;
+    out[8] = 1;
+    return out;
+};
+
+/**
+ * Transpose the values of a mat3
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat3} a the source matrix
+ * @returns {mat3} out
+ */
+mat3.transpose = function(out, a) {
+    // If we are transposing ourselves we can skip a few steps but have to cache some values
+    if (out === a) {
+        var a01 = a[1], a02 = a[2], a12 = a[5];
+        out[1] = a[3];
+        out[2] = a[6];
+        out[3] = a01;
+        out[5] = a[7];
+        out[6] = a02;
+        out[7] = a12;
+    } else {
+        out[0] = a[0];
+        out[1] = a[3];
+        out[2] = a[6];
+        out[3] = a[1];
+        out[4] = a[4];
+        out[5] = a[7];
+        out[6] = a[2];
+        out[7] = a[5];
+        out[8] = a[8];
+    }
+    
+    return out;
+};
+
+/**
+ * Inverts a mat3
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat3} a the source matrix
+ * @returns {mat3} out
+ */
+mat3.invert = function(out, a) {
+    var a00 = a[0], a01 = a[1], a02 = a[2],
+        a10 = a[3], a11 = a[4], a12 = a[5],
+        a20 = a[6], a21 = a[7], a22 = a[8],
+
+        b01 = a22 * a11 - a12 * a21,
+        b11 = -a22 * a10 + a12 * a20,
+        b21 = a21 * a10 - a11 * a20,
+
+        // Calculate the determinant
+        det = a00 * b01 + a01 * b11 + a02 * b21;
+
+    if (!det) { 
+        return null; 
+    }
+    det = 1.0 / det;
+
+    out[0] = b01 * det;
+    out[1] = (-a22 * a01 + a02 * a21) * det;
+    out[2] = (a12 * a01 - a02 * a11) * det;
+    out[3] = b11 * det;
+    out[4] = (a22 * a00 - a02 * a20) * det;
+    out[5] = (-a12 * a00 + a02 * a10) * det;
+    out[6] = b21 * det;
+    out[7] = (-a21 * a00 + a01 * a20) * det;
+    out[8] = (a11 * a00 - a01 * a10) * det;
+    return out;
+};
+
+/**
+ * Calculates the adjugate of a mat3
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat3} a the source matrix
+ * @returns {mat3} out
+ */
+mat3.adjoint = function(out, a) {
+    var a00 = a[0], a01 = a[1], a02 = a[2],
+        a10 = a[3], a11 = a[4], a12 = a[5],
+        a20 = a[6], a21 = a[7], a22 = a[8];
+
+    out[0] = (a11 * a22 - a12 * a21);
+    out[1] = (a02 * a21 - a01 * a22);
+    out[2] = (a01 * a12 - a02 * a11);
+    out[3] = (a12 * a20 - a10 * a22);
+    out[4] = (a00 * a22 - a02 * a20);
+    out[5] = (a02 * a10 - a00 * a12);
+    out[6] = (a10 * a21 - a11 * a20);
+    out[7] = (a01 * a20 - a00 * a21);
+    out[8] = (a00 * a11 - a01 * a10);
+    return out;
+};
+
+/**
+ * Calculates the determinant of a mat3
+ *
+ * @param {mat3} a the source matrix
+ * @returns {Number} determinant of a
+ */
+mat3.determinant = function (a) {
+    var a00 = a[0], a01 = a[1], a02 = a[2],
+        a10 = a[3], a11 = a[4], a12 = a[5],
+        a20 = a[6], a21 = a[7], a22 = a[8];
+
+    return a00 * (a22 * a11 - a12 * a21) + a01 * (-a22 * a10 + a12 * a20) + a02 * (a21 * a10 - a11 * a20);
+};
+
+/**
+ * Multiplies two mat3's
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat3} a the first operand
+ * @param {mat3} b the second operand
+ * @returns {mat3} out
+ */
+mat3.multiply = function (out, a, b) {
+    var a00 = a[0], a01 = a[1], a02 = a[2],
+        a10 = a[3], a11 = a[4], a12 = a[5],
+        a20 = a[6], a21 = a[7], a22 = a[8],
+
+        b00 = b[0], b01 = b[1], b02 = b[2],
+        b10 = b[3], b11 = b[4], b12 = b[5],
+        b20 = b[6], b21 = b[7], b22 = b[8];
+
+    out[0] = b00 * a00 + b01 * a10 + b02 * a20;
+    out[1] = b00 * a01 + b01 * a11 + b02 * a21;
+    out[2] = b00 * a02 + b01 * a12 + b02 * a22;
+
+    out[3] = b10 * a00 + b11 * a10 + b12 * a20;
+    out[4] = b10 * a01 + b11 * a11 + b12 * a21;
+    out[5] = b10 * a02 + b11 * a12 + b12 * a22;
+
+    out[6] = b20 * a00 + b21 * a10 + b22 * a20;
+    out[7] = b20 * a01 + b21 * a11 + b22 * a21;
+    out[8] = b20 * a02 + b21 * a12 + b22 * a22;
+    return out;
+};
+
+/**
+ * Alias for {@link mat3.multiply}
+ * @function
+ */
+mat3.mul = mat3.multiply;
+
+/**
+ * Translate a mat3 by the given vector
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat3} a the matrix to translate
+ * @param {vec2} v vector to translate by
+ * @returns {mat3} out
+ */
+mat3.translate = function(out, a, v) {
+    var a00 = a[0], a01 = a[1], a02 = a[2],
+        a10 = a[3], a11 = a[4], a12 = a[5],
+        a20 = a[6], a21 = a[7], a22 = a[8],
+        x = v[0], y = v[1];
+
+    out[0] = a00;
+    out[1] = a01;
+    out[2] = a02;
+
+    out[3] = a10;
+    out[4] = a11;
+    out[5] = a12;
+
+    out[6] = x * a00 + y * a10 + a20;
+    out[7] = x * a01 + y * a11 + a21;
+    out[8] = x * a02 + y * a12 + a22;
+    return out;
+};
+
+/**
+ * Rotates a mat3 by the given angle
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat3} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat3} out
+ */
+mat3.rotate = function (out, a, rad) {
+    var a00 = a[0], a01 = a[1], a02 = a[2],
+        a10 = a[3], a11 = a[4], a12 = a[5],
+        a20 = a[6], a21 = a[7], a22 = a[8],
+
+        s = Math.sin(rad),
+        c = Math.cos(rad);
+
+    out[0] = c * a00 + s * a10;
+    out[1] = c * a01 + s * a11;
+    out[2] = c * a02 + s * a12;
+
+    out[3] = c * a10 - s * a00;
+    out[4] = c * a11 - s * a01;
+    out[5] = c * a12 - s * a02;
+
+    out[6] = a20;
+    out[7] = a21;
+    out[8] = a22;
+    return out;
+};
+
+/**
+ * Scales the mat3 by the dimensions in the given vec2
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat3} a the matrix to rotate
+ * @param {vec2} v the vec2 to scale the matrix by
+ * @returns {mat3} out
+ **/
+mat3.scale = function(out, a, v) {
+    var x = v[0], y = v[1];
+
+    out[0] = x * a[0];
+    out[1] = x * a[1];
+    out[2] = x * a[2];
+
+    out[3] = y * a[3];
+    out[4] = y * a[4];
+    out[5] = y * a[5];
+
+    out[6] = a[6];
+    out[7] = a[7];
+    out[8] = a[8];
+    return out;
+};
+
+/**
+ * Creates a matrix from a vector translation
+ * This is equivalent to (but much faster than):
+ *
+ *     mat3.identity(dest);
+ *     mat3.translate(dest, dest, vec);
+ *
+ * @param {mat3} out mat3 receiving operation result
+ * @param {vec2} v Translation vector
+ * @returns {mat3} out
+ */
+mat3.fromTranslation = function(out, v) {
+    out[0] = 1;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 0;
+    out[4] = 1;
+    out[5] = 0;
+    out[6] = v[0];
+    out[7] = v[1];
+    out[8] = 1;
+    return out;
+}
+
+/**
+ * Creates a matrix from a given angle
+ * This is equivalent to (but much faster than):
+ *
+ *     mat3.identity(dest);
+ *     mat3.rotate(dest, dest, rad);
+ *
+ * @param {mat3} out mat3 receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat3} out
+ */
+mat3.fromRotation = function(out, rad) {
+    var s = Math.sin(rad), c = Math.cos(rad);
+
+    out[0] = c;
+    out[1] = s;
+    out[2] = 0;
+
+    out[3] = -s;
+    out[4] = c;
+    out[5] = 0;
+
+    out[6] = 0;
+    out[7] = 0;
+    out[8] = 1;
+    return out;
+}
+
+/**
+ * Creates a matrix from a vector scaling
+ * This is equivalent to (but much faster than):
+ *
+ *     mat3.identity(dest);
+ *     mat3.scale(dest, dest, vec);
+ *
+ * @param {mat3} out mat3 receiving operation result
+ * @param {vec2} v Scaling vector
+ * @returns {mat3} out
+ */
+mat3.fromScaling = function(out, v) {
+    out[0] = v[0];
+    out[1] = 0;
+    out[2] = 0;
+
+    out[3] = 0;
+    out[4] = v[1];
+    out[5] = 0;
+
+    out[6] = 0;
+    out[7] = 0;
+    out[8] = 1;
+    return out;
+}
+
+/**
+ * Copies the values from a mat2d into a mat3
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat2d} a the matrix to copy
+ * @returns {mat3} out
+ **/
+mat3.fromMat2d = function(out, a) {
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = 0;
+
+    out[3] = a[2];
+    out[4] = a[3];
+    out[5] = 0;
+
+    out[6] = a[4];
+    out[7] = a[5];
+    out[8] = 1;
+    return out;
+};
+
+/**
+* Calculates a 3x3 matrix from the given quaternion
+*
+* @param {mat3} out mat3 receiving operation result
+* @param {quat} q Quaternion to create matrix from
+*
+* @returns {mat3} out
+*/
+mat3.fromQuat = function (out, q) {
+    var x = q[0], y = q[1], z = q[2], w = q[3],
+        x2 = x + x,
+        y2 = y + y,
+        z2 = z + z,
+
+        xx = x * x2,
+        yx = y * x2,
+        yy = y * y2,
+        zx = z * x2,
+        zy = z * y2,
+        zz = z * z2,
+        wx = w * x2,
+        wy = w * y2,
+        wz = w * z2;
+
+    out[0] = 1 - yy - zz;
+    out[3] = yx - wz;
+    out[6] = zx + wy;
+
+    out[1] = yx + wz;
+    out[4] = 1 - xx - zz;
+    out[7] = zy - wx;
+
+    out[2] = zx - wy;
+    out[5] = zy + wx;
+    out[8] = 1 - xx - yy;
+
+    return out;
+};
+
+/**
+* Calculates a 3x3 normal matrix (transpose inverse) from the 4x4 matrix
+*
+* @param {mat3} out mat3 receiving operation result
+* @param {mat4} a Mat4 to derive the normal matrix from
+*
+* @returns {mat3} out
+*/
+mat3.normalFromMat4 = function (out, a) {
+    var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
+        a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
+        a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
+        a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15],
+
+        b00 = a00 * a11 - a01 * a10,
+        b01 = a00 * a12 - a02 * a10,
+        b02 = a00 * a13 - a03 * a10,
+        b03 = a01 * a12 - a02 * a11,
+        b04 = a01 * a13 - a03 * a11,
+        b05 = a02 * a13 - a03 * a12,
+        b06 = a20 * a31 - a21 * a30,
+        b07 = a20 * a32 - a22 * a30,
+        b08 = a20 * a33 - a23 * a30,
+        b09 = a21 * a32 - a22 * a31,
+        b10 = a21 * a33 - a23 * a31,
+        b11 = a22 * a33 - a23 * a32,
+
+        // Calculate the determinant
+        det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
+
+    if (!det) { 
+        return null; 
+    }
+    det = 1.0 / det;
+
+    out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
+    out[1] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
+    out[2] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
+
+    out[3] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
+    out[4] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
+    out[5] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
+
+    out[6] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
+    out[7] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
+    out[8] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
+
+    return out;
+};
+
+/**
+ * Returns a string representation of a mat3
+ *
+ * @param {mat3} mat matrix to represent as a string
+ * @returns {String} string representation of the matrix
+ */
+mat3.str = function (a) {
+    return 'mat3(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + 
+                    a[3] + ', ' + a[4] + ', ' + a[5] + ', ' + 
+                    a[6] + ', ' + a[7] + ', ' + a[8] + ')';
+};
+
+/**
+ * Returns Frobenius norm of a mat3
+ *
+ * @param {mat3} a the matrix to calculate Frobenius norm of
+ * @returns {Number} Frobenius norm
+ */
+mat3.frob = function (a) {
+    return(Math.sqrt(Math.pow(a[0], 2) + Math.pow(a[1], 2) + Math.pow(a[2], 2) + Math.pow(a[3], 2) + Math.pow(a[4], 2) + Math.pow(a[5], 2) + Math.pow(a[6], 2) + Math.pow(a[7], 2) + Math.pow(a[8], 2)))
+};
+
+/**
+ * Adds two mat3's
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat3} a the first operand
+ * @param {mat3} b the second operand
+ * @returns {mat3} out
+ */
+mat3.add = function(out, a, b) {
+    out[0] = a[0] + b[0];
+    out[1] = a[1] + b[1];
+    out[2] = a[2] + b[2];
+    out[3] = a[3] + b[3];
+    out[4] = a[4] + b[4];
+    out[5] = a[5] + b[5];
+    out[6] = a[6] + b[6];
+    out[7] = a[7] + b[7];
+    out[8] = a[8] + b[8];
+    return out;
+};
+
+/**
+ * Subtracts matrix b from matrix a
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat3} a the first operand
+ * @param {mat3} b the second operand
+ * @returns {mat3} out
+ */
+mat3.subtract = function(out, a, b) {
+    out[0] = a[0] - b[0];
+    out[1] = a[1] - b[1];
+    out[2] = a[2] - b[2];
+    out[3] = a[3] - b[3];
+    out[4] = a[4] - b[4];
+    out[5] = a[5] - b[5];
+    out[6] = a[6] - b[6];
+    out[7] = a[7] - b[7];
+    out[8] = a[8] - b[8];
+    return out;
+};
+
+/**
+ * Alias for {@link mat3.subtract}
+ * @function
+ */
+mat3.sub = mat3.subtract;
+
+/**
+ * Multiply each element of the matrix by a scalar.
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat3} a the matrix to scale
+ * @param {Number} b amount to scale the matrix's elements by
+ * @returns {mat3} out
+ */
+mat3.multiplyScalar = function(out, a, b) {
+    out[0] = a[0] * b;
+    out[1] = a[1] * b;
+    out[2] = a[2] * b;
+    out[3] = a[3] * b;
+    out[4] = a[4] * b;
+    out[5] = a[5] * b;
+    out[6] = a[6] * b;
+    out[7] = a[7] * b;
+    out[8] = a[8] * b;
+    return out;
+};
+
+/**
+ * Adds two mat3's after multiplying each element of the second operand by a scalar value.
+ *
+ * @param {mat3} out the receiving vector
+ * @param {mat3} a the first operand
+ * @param {mat3} b the second operand
+ * @param {Number} scale the amount to scale b's elements by before adding
+ * @returns {mat3} out
+ */
+mat3.multiplyScalarAndAdd = function(out, a, b, scale) {
+    out[0] = a[0] + (b[0] * scale);
+    out[1] = a[1] + (b[1] * scale);
+    out[2] = a[2] + (b[2] * scale);
+    out[3] = a[3] + (b[3] * scale);
+    out[4] = a[4] + (b[4] * scale);
+    out[5] = a[5] + (b[5] * scale);
+    out[6] = a[6] + (b[6] * scale);
+    out[7] = a[7] + (b[7] * scale);
+    out[8] = a[8] + (b[8] * scale);
+    return out;
+};
+
+/*
+ * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {mat3} a The first matrix.
+ * @param {mat3} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+mat3.exactEquals = function (a, b) {
+    return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && 
+           a[3] === b[3] && a[4] === b[4] && a[5] === b[5] &&
+           a[6] === b[6] && a[7] === b[7] && a[8] === b[8];
+};
+
+/**
+ * Returns whether or not the matrices have approximately the same elements in the same position.
+ *
+ * @param {mat3} a The first matrix.
+ * @param {mat3} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+mat3.equals = function (a, b) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5], a6 = a[6], a7 = a[7], a8 = a[8];
+    var b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3], b4 = b[4], b5 = b[5], b6 = a[6], b7 = b[7], b8 = b[8];
+    return (Math.abs(a0 - b0) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a0), Math.abs(b0)) &&
+            Math.abs(a1 - b1) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a1), Math.abs(b1)) &&
+            Math.abs(a2 - b2) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a2), Math.abs(b2)) &&
+            Math.abs(a3 - b3) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a3), Math.abs(b3)) &&
+            Math.abs(a4 - b4) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a4), Math.abs(b4)) &&
+            Math.abs(a5 - b5) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a5), Math.abs(b5)) &&
+            Math.abs(a6 - b6) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a6), Math.abs(b6)) &&
+            Math.abs(a7 - b7) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a7), Math.abs(b7)) &&
+            Math.abs(a8 - b8) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a8), Math.abs(b8)));
+};
 
 
-Lexer.rules=block;
+module.exports = mat3;
+
+},{"./common.js":266}],270:[function(require,module,exports){
+/* Copyright (c) 2015, Brandon Jones, Colin MacKenzie IV.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE. */
+
+var glMatrix = require("./common.js");
+
+/**
+ * @class 4x4 Matrix
+ * @name mat4
+ */
+var mat4 = {
+  scalar: {},
+  SIMD: {},
+};
+
+/**
+ * Creates a new identity mat4
+ *
+ * @returns {mat4} a new 4x4 matrix
+ */
+mat4.create = function() {
+    var out = new glMatrix.ARRAY_TYPE(16);
+    out[0] = 1;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 0;
+    out[4] = 0;
+    out[5] = 1;
+    out[6] = 0;
+    out[7] = 0;
+    out[8] = 0;
+    out[9] = 0;
+    out[10] = 1;
+    out[11] = 0;
+    out[12] = 0;
+    out[13] = 0;
+    out[14] = 0;
+    out[15] = 1;
+    return out;
+};
+
+/**
+ * Creates a new mat4 initialized with values from an existing matrix
+ *
+ * @param {mat4} a matrix to clone
+ * @returns {mat4} a new 4x4 matrix
+ */
+mat4.clone = function(a) {
+    var out = new glMatrix.ARRAY_TYPE(16);
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[3];
+    out[4] = a[4];
+    out[5] = a[5];
+    out[6] = a[6];
+    out[7] = a[7];
+    out[8] = a[8];
+    out[9] = a[9];
+    out[10] = a[10];
+    out[11] = a[11];
+    out[12] = a[12];
+    out[13] = a[13];
+    out[14] = a[14];
+    out[15] = a[15];
+    return out;
+};
+
+/**
+ * Copy the values from one mat4 to another
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the source matrix
+ * @returns {mat4} out
+ */
+mat4.copy = function(out, a) {
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[3];
+    out[4] = a[4];
+    out[5] = a[5];
+    out[6] = a[6];
+    out[7] = a[7];
+    out[8] = a[8];
+    out[9] = a[9];
+    out[10] = a[10];
+    out[11] = a[11];
+    out[12] = a[12];
+    out[13] = a[13];
+    out[14] = a[14];
+    out[15] = a[15];
+    return out;
+};
+
+/**
+ * Create a new mat4 with the given values
+ *
+ * @param {Number} m00 Component in column 0, row 0 position (index 0)
+ * @param {Number} m01 Component in column 0, row 1 position (index 1)
+ * @param {Number} m02 Component in column 0, row 2 position (index 2)
+ * @param {Number} m03 Component in column 0, row 3 position (index 3)
+ * @param {Number} m10 Component in column 1, row 0 position (index 4)
+ * @param {Number} m11 Component in column 1, row 1 position (index 5)
+ * @param {Number} m12 Component in column 1, row 2 position (index 6)
+ * @param {Number} m13 Component in column 1, row 3 position (index 7)
+ * @param {Number} m20 Component in column 2, row 0 position (index 8)
+ * @param {Number} m21 Component in column 2, row 1 position (index 9)
+ * @param {Number} m22 Component in column 2, row 2 position (index 10)
+ * @param {Number} m23 Component in column 2, row 3 position (index 11)
+ * @param {Number} m30 Component in column 3, row 0 position (index 12)
+ * @param {Number} m31 Component in column 3, row 1 position (index 13)
+ * @param {Number} m32 Component in column 3, row 2 position (index 14)
+ * @param {Number} m33 Component in column 3, row 3 position (index 15)
+ * @returns {mat4} A new mat4
+ */
+mat4.fromValues = function(m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33) {
+    var out = new glMatrix.ARRAY_TYPE(16);
+    out[0] = m00;
+    out[1] = m01;
+    out[2] = m02;
+    out[3] = m03;
+    out[4] = m10;
+    out[5] = m11;
+    out[6] = m12;
+    out[7] = m13;
+    out[8] = m20;
+    out[9] = m21;
+    out[10] = m22;
+    out[11] = m23;
+    out[12] = m30;
+    out[13] = m31;
+    out[14] = m32;
+    out[15] = m33;
+    return out;
+};
+
+/**
+ * Set the components of a mat4 to the given values
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {Number} m00 Component in column 0, row 0 position (index 0)
+ * @param {Number} m01 Component in column 0, row 1 position (index 1)
+ * @param {Number} m02 Component in column 0, row 2 position (index 2)
+ * @param {Number} m03 Component in column 0, row 3 position (index 3)
+ * @param {Number} m10 Component in column 1, row 0 position (index 4)
+ * @param {Number} m11 Component in column 1, row 1 position (index 5)
+ * @param {Number} m12 Component in column 1, row 2 position (index 6)
+ * @param {Number} m13 Component in column 1, row 3 position (index 7)
+ * @param {Number} m20 Component in column 2, row 0 position (index 8)
+ * @param {Number} m21 Component in column 2, row 1 position (index 9)
+ * @param {Number} m22 Component in column 2, row 2 position (index 10)
+ * @param {Number} m23 Component in column 2, row 3 position (index 11)
+ * @param {Number} m30 Component in column 3, row 0 position (index 12)
+ * @param {Number} m31 Component in column 3, row 1 position (index 13)
+ * @param {Number} m32 Component in column 3, row 2 position (index 14)
+ * @param {Number} m33 Component in column 3, row 3 position (index 15)
+ * @returns {mat4} out
+ */
+mat4.set = function(out, m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33) {
+    out[0] = m00;
+    out[1] = m01;
+    out[2] = m02;
+    out[3] = m03;
+    out[4] = m10;
+    out[5] = m11;
+    out[6] = m12;
+    out[7] = m13;
+    out[8] = m20;
+    out[9] = m21;
+    out[10] = m22;
+    out[11] = m23;
+    out[12] = m30;
+    out[13] = m31;
+    out[14] = m32;
+    out[15] = m33;
+    return out;
+};
 
 
+/**
+ * Set a mat4 to the identity matrix
+ *
+ * @param {mat4} out the receiving matrix
+ * @returns {mat4} out
+ */
+mat4.identity = function(out) {
+    out[0] = 1;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 0;
+    out[4] = 0;
+    out[5] = 1;
+    out[6] = 0;
+    out[7] = 0;
+    out[8] = 0;
+    out[9] = 0;
+    out[10] = 1;
+    out[11] = 0;
+    out[12] = 0;
+    out[13] = 0;
+    out[14] = 0;
+    out[15] = 1;
+    return out;
+};
 
+/**
+ * Transpose the values of a mat4 not using SIMD
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the source matrix
+ * @returns {mat4} out
+ */
+mat4.scalar.transpose = function(out, a) {
+    // If we are transposing ourselves we can skip a few steps but have to cache some values
+    if (out === a) {
+        var a01 = a[1], a02 = a[2], a03 = a[3],
+            a12 = a[6], a13 = a[7],
+            a23 = a[11];
 
+        out[1] = a[4];
+        out[2] = a[8];
+        out[3] = a[12];
+        out[4] = a01;
+        out[6] = a[9];
+        out[7] = a[13];
+        out[8] = a02;
+        out[9] = a12;
+        out[11] = a[14];
+        out[12] = a03;
+        out[13] = a13;
+        out[14] = a23;
+    } else {
+        out[0] = a[0];
+        out[1] = a[4];
+        out[2] = a[8];
+        out[3] = a[12];
+        out[4] = a[1];
+        out[5] = a[5];
+        out[6] = a[9];
+        out[7] = a[13];
+        out[8] = a[2];
+        out[9] = a[6];
+        out[10] = a[10];
+        out[11] = a[14];
+        out[12] = a[3];
+        out[13] = a[7];
+        out[14] = a[11];
+        out[15] = a[15];
+    }
 
-Lexer.lex=function(src,options){
-var lexer=new Lexer(options);
-return lexer.lex(src);
+    return out;
+};
+
+/**
+ * Transpose the values of a mat4 using SIMD
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the source matrix
+ * @returns {mat4} out
+ */
+mat4.SIMD.transpose = function(out, a) {
+    var a0, a1, a2, a3,
+        tmp01, tmp23,
+        out0, out1, out2, out3;
+
+    a0 = SIMD.Float32x4.load(a, 0);
+    a1 = SIMD.Float32x4.load(a, 4);
+    a2 = SIMD.Float32x4.load(a, 8);
+    a3 = SIMD.Float32x4.load(a, 12);
+
+    tmp01 = SIMD.Float32x4.shuffle(a0, a1, 0, 1, 4, 5);
+    tmp23 = SIMD.Float32x4.shuffle(a2, a3, 0, 1, 4, 5);
+    out0  = SIMD.Float32x4.shuffle(tmp01, tmp23, 0, 2, 4, 6);
+    out1  = SIMD.Float32x4.shuffle(tmp01, tmp23, 1, 3, 5, 7);
+    SIMD.Float32x4.store(out, 0,  out0);
+    SIMD.Float32x4.store(out, 4,  out1);
+
+    tmp01 = SIMD.Float32x4.shuffle(a0, a1, 2, 3, 6, 7);
+    tmp23 = SIMD.Float32x4.shuffle(a2, a3, 2, 3, 6, 7);
+    out2  = SIMD.Float32x4.shuffle(tmp01, tmp23, 0, 2, 4, 6);
+    out3  = SIMD.Float32x4.shuffle(tmp01, tmp23, 1, 3, 5, 7);
+    SIMD.Float32x4.store(out, 8,  out2);
+    SIMD.Float32x4.store(out, 12, out3);
+
+    return out;
+};
+
+/**
+ * Transpse a mat4 using SIMD if available and enabled
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the source matrix
+ * @returns {mat4} out
+ */
+mat4.transpose = glMatrix.USE_SIMD ? mat4.SIMD.transpose : mat4.scalar.transpose;
+
+/**
+ * Inverts a mat4 not using SIMD
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the source matrix
+ * @returns {mat4} out
+ */
+mat4.scalar.invert = function(out, a) {
+    var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
+        a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
+        a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
+        a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15],
+
+        b00 = a00 * a11 - a01 * a10,
+        b01 = a00 * a12 - a02 * a10,
+        b02 = a00 * a13 - a03 * a10,
+        b03 = a01 * a12 - a02 * a11,
+        b04 = a01 * a13 - a03 * a11,
+        b05 = a02 * a13 - a03 * a12,
+        b06 = a20 * a31 - a21 * a30,
+        b07 = a20 * a32 - a22 * a30,
+        b08 = a20 * a33 - a23 * a30,
+        b09 = a21 * a32 - a22 * a31,
+        b10 = a21 * a33 - a23 * a31,
+        b11 = a22 * a33 - a23 * a32,
+
+        // Calculate the determinant
+        det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
+
+    if (!det) {
+        return null;
+    }
+    det = 1.0 / det;
+
+    out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
+    out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
+    out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
+    out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
+    out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
+    out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
+    out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
+    out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
+    out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
+    out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
+    out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
+    out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
+    out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
+    out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
+    out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
+    out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
+
+    return out;
+};
+
+/**
+ * Inverts a mat4 using SIMD
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the source matrix
+ * @returns {mat4} out
+ */
+mat4.SIMD.invert = function(out, a) {
+  var row0, row1, row2, row3,
+      tmp1,
+      minor0, minor1, minor2, minor3,
+      det,
+      a0 = SIMD.Float32x4.load(a, 0),
+      a1 = SIMD.Float32x4.load(a, 4),
+      a2 = SIMD.Float32x4.load(a, 8),
+      a3 = SIMD.Float32x4.load(a, 12);
+
+  // Compute matrix adjugate
+  tmp1 = SIMD.Float32x4.shuffle(a0, a1, 0, 1, 4, 5);
+  row1 = SIMD.Float32x4.shuffle(a2, a3, 0, 1, 4, 5);
+  row0 = SIMD.Float32x4.shuffle(tmp1, row1, 0, 2, 4, 6);
+  row1 = SIMD.Float32x4.shuffle(row1, tmp1, 1, 3, 5, 7);
+  tmp1 = SIMD.Float32x4.shuffle(a0, a1, 2, 3, 6, 7);
+  row3 = SIMD.Float32x4.shuffle(a2, a3, 2, 3, 6, 7);
+  row2 = SIMD.Float32x4.shuffle(tmp1, row3, 0, 2, 4, 6);
+  row3 = SIMD.Float32x4.shuffle(row3, tmp1, 1, 3, 5, 7);
+
+  tmp1   = SIMD.Float32x4.mul(row2, row3);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 1, 0, 3, 2);
+  minor0 = SIMD.Float32x4.mul(row1, tmp1);
+  minor1 = SIMD.Float32x4.mul(row0, tmp1);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 2, 3, 0, 1);
+  minor0 = SIMD.Float32x4.sub(SIMD.Float32x4.mul(row1, tmp1), minor0);
+  minor1 = SIMD.Float32x4.sub(SIMD.Float32x4.mul(row0, tmp1), minor1);
+  minor1 = SIMD.Float32x4.swizzle(minor1, 2, 3, 0, 1);
+
+  tmp1   = SIMD.Float32x4.mul(row1, row2);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 1, 0, 3, 2);
+  minor0 = SIMD.Float32x4.add(SIMD.Float32x4.mul(row3, tmp1), minor0);
+  minor3 = SIMD.Float32x4.mul(row0, tmp1);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 2, 3, 0, 1);
+  minor0 = SIMD.Float32x4.sub(minor0, SIMD.Float32x4.mul(row3, tmp1));
+  minor3 = SIMD.Float32x4.sub(SIMD.Float32x4.mul(row0, tmp1), minor3);
+  minor3 = SIMD.Float32x4.swizzle(minor3, 2, 3, 0, 1);
+
+  tmp1   = SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(row1, 2, 3, 0, 1), row3);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 1, 0, 3, 2);
+  row2   = SIMD.Float32x4.swizzle(row2, 2, 3, 0, 1);
+  minor0 = SIMD.Float32x4.add(SIMD.Float32x4.mul(row2, tmp1), minor0);
+  minor2 = SIMD.Float32x4.mul(row0, tmp1);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 2, 3, 0, 1);
+  minor0 = SIMD.Float32x4.sub(minor0, SIMD.Float32x4.mul(row2, tmp1));
+  minor2 = SIMD.Float32x4.sub(SIMD.Float32x4.mul(row0, tmp1), minor2);
+  minor2 = SIMD.Float32x4.swizzle(minor2, 2, 3, 0, 1);
+
+  tmp1   = SIMD.Float32x4.mul(row0, row1);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 1, 0, 3, 2);
+  minor2 = SIMD.Float32x4.add(SIMD.Float32x4.mul(row3, tmp1), minor2);
+  minor3 = SIMD.Float32x4.sub(SIMD.Float32x4.mul(row2, tmp1), minor3);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 2, 3, 0, 1);
+  minor2 = SIMD.Float32x4.sub(SIMD.Float32x4.mul(row3, tmp1), minor2);
+  minor3 = SIMD.Float32x4.sub(minor3, SIMD.Float32x4.mul(row2, tmp1));
+
+  tmp1   = SIMD.Float32x4.mul(row0, row3);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 1, 0, 3, 2);
+  minor1 = SIMD.Float32x4.sub(minor1, SIMD.Float32x4.mul(row2, tmp1));
+  minor2 = SIMD.Float32x4.add(SIMD.Float32x4.mul(row1, tmp1), minor2);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 2, 3, 0, 1);
+  minor1 = SIMD.Float32x4.add(SIMD.Float32x4.mul(row2, tmp1), minor1);
+  minor2 = SIMD.Float32x4.sub(minor2, SIMD.Float32x4.mul(row1, tmp1));
+
+  tmp1   = SIMD.Float32x4.mul(row0, row2);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 1, 0, 3, 2);
+  minor1 = SIMD.Float32x4.add(SIMD.Float32x4.mul(row3, tmp1), minor1);
+  minor3 = SIMD.Float32x4.sub(minor3, SIMD.Float32x4.mul(row1, tmp1));
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 2, 3, 0, 1);
+  minor1 = SIMD.Float32x4.sub(minor1, SIMD.Float32x4.mul(row3, tmp1));
+  minor3 = SIMD.Float32x4.add(SIMD.Float32x4.mul(row1, tmp1), minor3);
+
+  // Compute matrix determinant
+  det   = SIMD.Float32x4.mul(row0, minor0);
+  det   = SIMD.Float32x4.add(SIMD.Float32x4.swizzle(det, 2, 3, 0, 1), det);
+  det   = SIMD.Float32x4.add(SIMD.Float32x4.swizzle(det, 1, 0, 3, 2), det);
+  tmp1  = SIMD.Float32x4.reciprocalApproximation(det);
+  det   = SIMD.Float32x4.sub(
+               SIMD.Float32x4.add(tmp1, tmp1),
+               SIMD.Float32x4.mul(det, SIMD.Float32x4.mul(tmp1, tmp1)));
+  det   = SIMD.Float32x4.swizzle(det, 0, 0, 0, 0);
+  if (!det) {
+      return null;
+  }
+
+  // Compute matrix inverse
+  SIMD.Float32x4.store(out, 0,  SIMD.Float32x4.mul(det, minor0));
+  SIMD.Float32x4.store(out, 4,  SIMD.Float32x4.mul(det, minor1));
+  SIMD.Float32x4.store(out, 8,  SIMD.Float32x4.mul(det, minor2));
+  SIMD.Float32x4.store(out, 12, SIMD.Float32x4.mul(det, minor3));
+  return out;
+}
+
+/**
+ * Inverts a mat4 using SIMD if available and enabled
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the source matrix
+ * @returns {mat4} out
+ */
+mat4.invert = glMatrix.USE_SIMD ? mat4.SIMD.invert : mat4.scalar.invert;
+
+/**
+ * Calculates the adjugate of a mat4 not using SIMD
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the source matrix
+ * @returns {mat4} out
+ */
+mat4.scalar.adjoint = function(out, a) {
+    var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
+        a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
+        a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
+        a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
+
+    out[0]  =  (a11 * (a22 * a33 - a23 * a32) - a21 * (a12 * a33 - a13 * a32) + a31 * (a12 * a23 - a13 * a22));
+    out[1]  = -(a01 * (a22 * a33 - a23 * a32) - a21 * (a02 * a33 - a03 * a32) + a31 * (a02 * a23 - a03 * a22));
+    out[2]  =  (a01 * (a12 * a33 - a13 * a32) - a11 * (a02 * a33 - a03 * a32) + a31 * (a02 * a13 - a03 * a12));
+    out[3]  = -(a01 * (a12 * a23 - a13 * a22) - a11 * (a02 * a23 - a03 * a22) + a21 * (a02 * a13 - a03 * a12));
+    out[4]  = -(a10 * (a22 * a33 - a23 * a32) - a20 * (a12 * a33 - a13 * a32) + a30 * (a12 * a23 - a13 * a22));
+    out[5]  =  (a00 * (a22 * a33 - a23 * a32) - a20 * (a02 * a33 - a03 * a32) + a30 * (a02 * a23 - a03 * a22));
+    out[6]  = -(a00 * (a12 * a33 - a13 * a32) - a10 * (a02 * a33 - a03 * a32) + a30 * (a02 * a13 - a03 * a12));
+    out[7]  =  (a00 * (a12 * a23 - a13 * a22) - a10 * (a02 * a23 - a03 * a22) + a20 * (a02 * a13 - a03 * a12));
+    out[8]  =  (a10 * (a21 * a33 - a23 * a31) - a20 * (a11 * a33 - a13 * a31) + a30 * (a11 * a23 - a13 * a21));
+    out[9]  = -(a00 * (a21 * a33 - a23 * a31) - a20 * (a01 * a33 - a03 * a31) + a30 * (a01 * a23 - a03 * a21));
+    out[10] =  (a00 * (a11 * a33 - a13 * a31) - a10 * (a01 * a33 - a03 * a31) + a30 * (a01 * a13 - a03 * a11));
+    out[11] = -(a00 * (a11 * a23 - a13 * a21) - a10 * (a01 * a23 - a03 * a21) + a20 * (a01 * a13 - a03 * a11));
+    out[12] = -(a10 * (a21 * a32 - a22 * a31) - a20 * (a11 * a32 - a12 * a31) + a30 * (a11 * a22 - a12 * a21));
+    out[13] =  (a00 * (a21 * a32 - a22 * a31) - a20 * (a01 * a32 - a02 * a31) + a30 * (a01 * a22 - a02 * a21));
+    out[14] = -(a00 * (a11 * a32 - a12 * a31) - a10 * (a01 * a32 - a02 * a31) + a30 * (a01 * a12 - a02 * a11));
+    out[15] =  (a00 * (a11 * a22 - a12 * a21) - a10 * (a01 * a22 - a02 * a21) + a20 * (a01 * a12 - a02 * a11));
+    return out;
+};
+
+/**
+ * Calculates the adjugate of a mat4 using SIMD
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the source matrix
+ * @returns {mat4} out
+ */
+mat4.SIMD.adjoint = function(out, a) {
+  var a0, a1, a2, a3;
+  var row0, row1, row2, row3;
+  var tmp1;
+  var minor0, minor1, minor2, minor3;
+
+  var a0 = SIMD.Float32x4.load(a, 0);
+  var a1 = SIMD.Float32x4.load(a, 4);
+  var a2 = SIMD.Float32x4.load(a, 8);
+  var a3 = SIMD.Float32x4.load(a, 12);
+
+  // Transpose the source matrix.  Sort of.  Not a true transpose operation
+  tmp1 = SIMD.Float32x4.shuffle(a0, a1, 0, 1, 4, 5);
+  row1 = SIMD.Float32x4.shuffle(a2, a3, 0, 1, 4, 5);
+  row0 = SIMD.Float32x4.shuffle(tmp1, row1, 0, 2, 4, 6);
+  row1 = SIMD.Float32x4.shuffle(row1, tmp1, 1, 3, 5, 7);
+
+  tmp1 = SIMD.Float32x4.shuffle(a0, a1, 2, 3, 6, 7);
+  row3 = SIMD.Float32x4.shuffle(a2, a3, 2, 3, 6, 7);
+  row2 = SIMD.Float32x4.shuffle(tmp1, row3, 0, 2, 4, 6);
+  row3 = SIMD.Float32x4.shuffle(row3, tmp1, 1, 3, 5, 7);
+
+  tmp1   = SIMD.Float32x4.mul(row2, row3);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 1, 0, 3, 2);
+  minor0 = SIMD.Float32x4.mul(row1, tmp1);
+  minor1 = SIMD.Float32x4.mul(row0, tmp1);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 2, 3, 0, 1);
+  minor0 = SIMD.Float32x4.sub(SIMD.Float32x4.mul(row1, tmp1), minor0);
+  minor1 = SIMD.Float32x4.sub(SIMD.Float32x4.mul(row0, tmp1), minor1);
+  minor1 = SIMD.Float32x4.swizzle(minor1, 2, 3, 0, 1);
+
+  tmp1   = SIMD.Float32x4.mul(row1, row2);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 1, 0, 3, 2);
+  minor0 = SIMD.Float32x4.add(SIMD.Float32x4.mul(row3, tmp1), minor0);
+  minor3 = SIMD.Float32x4.mul(row0, tmp1);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 2, 3, 0, 1);
+  minor0 = SIMD.Float32x4.sub(minor0, SIMD.Float32x4.mul(row3, tmp1));
+  minor3 = SIMD.Float32x4.sub(SIMD.Float32x4.mul(row0, tmp1), minor3);
+  minor3 = SIMD.Float32x4.swizzle(minor3, 2, 3, 0, 1);
+
+  tmp1   = SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(row1, 2, 3, 0, 1), row3);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 1, 0, 3, 2);
+  row2   = SIMD.Float32x4.swizzle(row2, 2, 3, 0, 1);
+  minor0 = SIMD.Float32x4.add(SIMD.Float32x4.mul(row2, tmp1), minor0);
+  minor2 = SIMD.Float32x4.mul(row0, tmp1);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 2, 3, 0, 1);
+  minor0 = SIMD.Float32x4.sub(minor0, SIMD.Float32x4.mul(row2, tmp1));
+  minor2 = SIMD.Float32x4.sub(SIMD.Float32x4.mul(row0, tmp1), minor2);
+  minor2 = SIMD.Float32x4.swizzle(minor2, 2, 3, 0, 1);
+
+  tmp1   = SIMD.Float32x4.mul(row0, row1);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 1, 0, 3, 2);
+  minor2 = SIMD.Float32x4.add(SIMD.Float32x4.mul(row3, tmp1), minor2);
+  minor3 = SIMD.Float32x4.sub(SIMD.Float32x4.mul(row2, tmp1), minor3);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 2, 3, 0, 1);
+  minor2 = SIMD.Float32x4.sub(SIMD.Float32x4.mul(row3, tmp1), minor2);
+  minor3 = SIMD.Float32x4.sub(minor3, SIMD.Float32x4.mul(row2, tmp1));
+
+  tmp1   = SIMD.Float32x4.mul(row0, row3);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 1, 0, 3, 2);
+  minor1 = SIMD.Float32x4.sub(minor1, SIMD.Float32x4.mul(row2, tmp1));
+  minor2 = SIMD.Float32x4.add(SIMD.Float32x4.mul(row1, tmp1), minor2);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 2, 3, 0, 1);
+  minor1 = SIMD.Float32x4.add(SIMD.Float32x4.mul(row2, tmp1), minor1);
+  minor2 = SIMD.Float32x4.sub(minor2, SIMD.Float32x4.mul(row1, tmp1));
+
+  tmp1   = SIMD.Float32x4.mul(row0, row2);
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 1, 0, 3, 2);
+  minor1 = SIMD.Float32x4.add(SIMD.Float32x4.mul(row3, tmp1), minor1);
+  minor3 = SIMD.Float32x4.sub(minor3, SIMD.Float32x4.mul(row1, tmp1));
+  tmp1   = SIMD.Float32x4.swizzle(tmp1, 2, 3, 0, 1);
+  minor1 = SIMD.Float32x4.sub(minor1, SIMD.Float32x4.mul(row3, tmp1));
+  minor3 = SIMD.Float32x4.add(SIMD.Float32x4.mul(row1, tmp1), minor3);
+
+  SIMD.Float32x4.store(out, 0,  minor0);
+  SIMD.Float32x4.store(out, 4,  minor1);
+  SIMD.Float32x4.store(out, 8,  minor2);
+  SIMD.Float32x4.store(out, 12, minor3);
+  return out;
+};
+
+/**
+ * Calculates the adjugate of a mat4 using SIMD if available and enabled
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the source matrix
+ * @returns {mat4} out
+ */
+ mat4.adjoint = glMatrix.USE_SIMD ? mat4.SIMD.adjoint : mat4.scalar.adjoint;
+
+/**
+ * Calculates the determinant of a mat4
+ *
+ * @param {mat4} a the source matrix
+ * @returns {Number} determinant of a
+ */
+mat4.determinant = function (a) {
+    var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
+        a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
+        a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
+        a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15],
+
+        b00 = a00 * a11 - a01 * a10,
+        b01 = a00 * a12 - a02 * a10,
+        b02 = a00 * a13 - a03 * a10,
+        b03 = a01 * a12 - a02 * a11,
+        b04 = a01 * a13 - a03 * a11,
+        b05 = a02 * a13 - a03 * a12,
+        b06 = a20 * a31 - a21 * a30,
+        b07 = a20 * a32 - a22 * a30,
+        b08 = a20 * a33 - a23 * a30,
+        b09 = a21 * a32 - a22 * a31,
+        b10 = a21 * a33 - a23 * a31,
+        b11 = a22 * a33 - a23 * a32;
+
+    // Calculate the determinant
+    return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
+};
+
+/**
+ * Multiplies two mat4's explicitly using SIMD
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the first operand, must be a Float32Array
+ * @param {mat4} b the second operand, must be a Float32Array
+ * @returns {mat4} out
+ */
+mat4.SIMD.multiply = function (out, a, b) {
+    var a0 = SIMD.Float32x4.load(a, 0);
+    var a1 = SIMD.Float32x4.load(a, 4);
+    var a2 = SIMD.Float32x4.load(a, 8);
+    var a3 = SIMD.Float32x4.load(a, 12);
+
+    var b0 = SIMD.Float32x4.load(b, 0);
+    var out0 = SIMD.Float32x4.add(
+                   SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b0, 0, 0, 0, 0), a0),
+                   SIMD.Float32x4.add(
+                       SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b0, 1, 1, 1, 1), a1),
+                       SIMD.Float32x4.add(
+                           SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b0, 2, 2, 2, 2), a2),
+                           SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b0, 3, 3, 3, 3), a3))));
+    SIMD.Float32x4.store(out, 0, out0);
+
+    var b1 = SIMD.Float32x4.load(b, 4);
+    var out1 = SIMD.Float32x4.add(
+                   SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b1, 0, 0, 0, 0), a0),
+                   SIMD.Float32x4.add(
+                       SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b1, 1, 1, 1, 1), a1),
+                       SIMD.Float32x4.add(
+                           SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b1, 2, 2, 2, 2), a2),
+                           SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b1, 3, 3, 3, 3), a3))));
+    SIMD.Float32x4.store(out, 4, out1);
+
+    var b2 = SIMD.Float32x4.load(b, 8);
+    var out2 = SIMD.Float32x4.add(
+                   SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b2, 0, 0, 0, 0), a0),
+                   SIMD.Float32x4.add(
+                       SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b2, 1, 1, 1, 1), a1),
+                       SIMD.Float32x4.add(
+                               SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b2, 2, 2, 2, 2), a2),
+                               SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b2, 3, 3, 3, 3), a3))));
+    SIMD.Float32x4.store(out, 8, out2);
+
+    var b3 = SIMD.Float32x4.load(b, 12);
+    var out3 = SIMD.Float32x4.add(
+                   SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b3, 0, 0, 0, 0), a0),
+                   SIMD.Float32x4.add(
+                        SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b3, 1, 1, 1, 1), a1),
+                        SIMD.Float32x4.add(
+                            SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b3, 2, 2, 2, 2), a2),
+                            SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(b3, 3, 3, 3, 3), a3))));
+    SIMD.Float32x4.store(out, 12, out3);
+
+    return out;
+};
+
+/**
+ * Multiplies two mat4's explicitly not using SIMD
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the first operand
+ * @param {mat4} b the second operand
+ * @returns {mat4} out
+ */
+mat4.scalar.multiply = function (out, a, b) {
+    var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
+        a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
+        a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
+        a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
+
+    // Cache only the current line of the second matrix
+    var b0  = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
+    out[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
+    out[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
+    out[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
+    out[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
+
+    b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7];
+    out[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
+    out[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
+    out[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
+    out[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
+
+    b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11];
+    out[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
+    out[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
+    out[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
+    out[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
+
+    b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15];
+    out[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
+    out[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
+    out[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
+    out[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
+    return out;
+};
+
+/**
+ * Multiplies two mat4's using SIMD if available and enabled
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the first operand
+ * @param {mat4} b the second operand
+ * @returns {mat4} out
+ */
+mat4.multiply = glMatrix.USE_SIMD ? mat4.SIMD.multiply : mat4.scalar.multiply;
+
+/**
+ * Alias for {@link mat4.multiply}
+ * @function
+ */
+mat4.mul = mat4.multiply;
+
+/**
+ * Translate a mat4 by the given vector not using SIMD
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to translate
+ * @param {vec3} v vector to translate by
+ * @returns {mat4} out
+ */
+mat4.scalar.translate = function (out, a, v) {
+    var x = v[0], y = v[1], z = v[2],
+        a00, a01, a02, a03,
+        a10, a11, a12, a13,
+        a20, a21, a22, a23;
+
+    if (a === out) {
+        out[12] = a[0] * x + a[4] * y + a[8] * z + a[12];
+        out[13] = a[1] * x + a[5] * y + a[9] * z + a[13];
+        out[14] = a[2] * x + a[6] * y + a[10] * z + a[14];
+        out[15] = a[3] * x + a[7] * y + a[11] * z + a[15];
+    } else {
+        a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3];
+        a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7];
+        a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11];
+
+        out[0] = a00; out[1] = a01; out[2] = a02; out[3] = a03;
+        out[4] = a10; out[5] = a11; out[6] = a12; out[7] = a13;
+        out[8] = a20; out[9] = a21; out[10] = a22; out[11] = a23;
+
+        out[12] = a00 * x + a10 * y + a20 * z + a[12];
+        out[13] = a01 * x + a11 * y + a21 * z + a[13];
+        out[14] = a02 * x + a12 * y + a22 * z + a[14];
+        out[15] = a03 * x + a13 * y + a23 * z + a[15];
+    }
+
+    return out;
+};
+
+/**
+ * Translates a mat4 by the given vector using SIMD
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to translate
+ * @param {vec3} v vector to translate by
+ * @returns {mat4} out
+ */
+mat4.SIMD.translate = function (out, a, v) {
+    var a0 = SIMD.Float32x4.load(a, 0),
+        a1 = SIMD.Float32x4.load(a, 4),
+        a2 = SIMD.Float32x4.load(a, 8),
+        a3 = SIMD.Float32x4.load(a, 12),
+        vec = SIMD.Float32x4(v[0], v[1], v[2] , 0);
+
+    if (a !== out) {
+        out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3];
+        out[4] = a[4]; out[5] = a[5]; out[6] = a[6]; out[7] = a[7];
+        out[8] = a[8]; out[9] = a[9]; out[10] = a[10]; out[11] = a[11];
+    }
+
+    a0 = SIMD.Float32x4.mul(a0, SIMD.Float32x4.swizzle(vec, 0, 0, 0, 0));
+    a1 = SIMD.Float32x4.mul(a1, SIMD.Float32x4.swizzle(vec, 1, 1, 1, 1));
+    a2 = SIMD.Float32x4.mul(a2, SIMD.Float32x4.swizzle(vec, 2, 2, 2, 2));
+
+    var t0 = SIMD.Float32x4.add(a0, SIMD.Float32x4.add(a1, SIMD.Float32x4.add(a2, a3)));
+    SIMD.Float32x4.store(out, 12, t0);
+
+    return out;
+};
+
+/**
+ * Translates a mat4 by the given vector using SIMD if available and enabled
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to translate
+ * @param {vec3} v vector to translate by
+ * @returns {mat4} out
+ */
+mat4.translate = glMatrix.USE_SIMD ? mat4.SIMD.translate : mat4.scalar.translate;
+
+/**
+ * Scales the mat4 by the dimensions in the given vec3 not using vectorization
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to scale
+ * @param {vec3} v the vec3 to scale the matrix by
+ * @returns {mat4} out
+ **/
+mat4.scalar.scale = function(out, a, v) {
+    var x = v[0], y = v[1], z = v[2];
+
+    out[0] = a[0] * x;
+    out[1] = a[1] * x;
+    out[2] = a[2] * x;
+    out[3] = a[3] * x;
+    out[4] = a[4] * y;
+    out[5] = a[5] * y;
+    out[6] = a[6] * y;
+    out[7] = a[7] * y;
+    out[8] = a[8] * z;
+    out[9] = a[9] * z;
+    out[10] = a[10] * z;
+    out[11] = a[11] * z;
+    out[12] = a[12];
+    out[13] = a[13];
+    out[14] = a[14];
+    out[15] = a[15];
+    return out;
+};
+
+/**
+ * Scales the mat4 by the dimensions in the given vec3 using vectorization
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to scale
+ * @param {vec3} v the vec3 to scale the matrix by
+ * @returns {mat4} out
+ **/
+mat4.SIMD.scale = function(out, a, v) {
+    var a0, a1, a2;
+    var vec = SIMD.Float32x4(v[0], v[1], v[2], 0);
+
+    a0 = SIMD.Float32x4.load(a, 0);
+    SIMD.Float32x4.store(
+        out, 0, SIMD.Float32x4.mul(a0, SIMD.Float32x4.swizzle(vec, 0, 0, 0, 0)));
+
+    a1 = SIMD.Float32x4.load(a, 4);
+    SIMD.Float32x4.store(
+        out, 4, SIMD.Float32x4.mul(a1, SIMD.Float32x4.swizzle(vec, 1, 1, 1, 1)));
+
+    a2 = SIMD.Float32x4.load(a, 8);
+    SIMD.Float32x4.store(
+        out, 8, SIMD.Float32x4.mul(a2, SIMD.Float32x4.swizzle(vec, 2, 2, 2, 2)));
+
+    out[12] = a[12];
+    out[13] = a[13];
+    out[14] = a[14];
+    out[15] = a[15];
+    return out;
+};
+
+/**
+ * Scales the mat4 by the dimensions in the given vec3 using SIMD if available and enabled
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to scale
+ * @param {vec3} v the vec3 to scale the matrix by
+ * @returns {mat4} out
+ */
+mat4.scale = glMatrix.USE_SIMD ? mat4.SIMD.scale : mat4.scalar.scale;
+
+/**
+ * Rotates a mat4 by the given angle around the given axis
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @param {vec3} axis the axis to rotate around
+ * @returns {mat4} out
+ */
+mat4.rotate = function (out, a, rad, axis) {
+    var x = axis[0], y = axis[1], z = axis[2],
+        len = Math.sqrt(x * x + y * y + z * z),
+        s, c, t,
+        a00, a01, a02, a03,
+        a10, a11, a12, a13,
+        a20, a21, a22, a23,
+        b00, b01, b02,
+        b10, b11, b12,
+        b20, b21, b22;
+
+    if (Math.abs(len) < glMatrix.EPSILON) { return null; }
+
+    len = 1 / len;
+    x *= len;
+    y *= len;
+    z *= len;
+
+    s = Math.sin(rad);
+    c = Math.cos(rad);
+    t = 1 - c;
+
+    a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3];
+    a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7];
+    a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11];
+
+    // Construct the elements of the rotation matrix
+    b00 = x * x * t + c; b01 = y * x * t + z * s; b02 = z * x * t - y * s;
+    b10 = x * y * t - z * s; b11 = y * y * t + c; b12 = z * y * t + x * s;
+    b20 = x * z * t + y * s; b21 = y * z * t - x * s; b22 = z * z * t + c;
+
+    // Perform rotation-specific matrix multiplication
+    out[0] = a00 * b00 + a10 * b01 + a20 * b02;
+    out[1] = a01 * b00 + a11 * b01 + a21 * b02;
+    out[2] = a02 * b00 + a12 * b01 + a22 * b02;
+    out[3] = a03 * b00 + a13 * b01 + a23 * b02;
+    out[4] = a00 * b10 + a10 * b11 + a20 * b12;
+    out[5] = a01 * b10 + a11 * b11 + a21 * b12;
+    out[6] = a02 * b10 + a12 * b11 + a22 * b12;
+    out[7] = a03 * b10 + a13 * b11 + a23 * b12;
+    out[8] = a00 * b20 + a10 * b21 + a20 * b22;
+    out[9] = a01 * b20 + a11 * b21 + a21 * b22;
+    out[10] = a02 * b20 + a12 * b21 + a22 * b22;
+    out[11] = a03 * b20 + a13 * b21 + a23 * b22;
+
+    if (a !== out) { // If the source and destination differ, copy the unchanged last row
+        out[12] = a[12];
+        out[13] = a[13];
+        out[14] = a[14];
+        out[15] = a[15];
+    }
+    return out;
+};
+
+/**
+ * Rotates a matrix by the given angle around the X axis not using SIMD
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+mat4.scalar.rotateX = function (out, a, rad) {
+    var s = Math.sin(rad),
+        c = Math.cos(rad),
+        a10 = a[4],
+        a11 = a[5],
+        a12 = a[6],
+        a13 = a[7],
+        a20 = a[8],
+        a21 = a[9],
+        a22 = a[10],
+        a23 = a[11];
+
+    if (a !== out) { // If the source and destination differ, copy the unchanged rows
+        out[0]  = a[0];
+        out[1]  = a[1];
+        out[2]  = a[2];
+        out[3]  = a[3];
+        out[12] = a[12];
+        out[13] = a[13];
+        out[14] = a[14];
+        out[15] = a[15];
+    }
+
+    // Perform axis-specific matrix multiplication
+    out[4] = a10 * c + a20 * s;
+    out[5] = a11 * c + a21 * s;
+    out[6] = a12 * c + a22 * s;
+    out[7] = a13 * c + a23 * s;
+    out[8] = a20 * c - a10 * s;
+    out[9] = a21 * c - a11 * s;
+    out[10] = a22 * c - a12 * s;
+    out[11] = a23 * c - a13 * s;
+    return out;
+};
+
+/**
+ * Rotates a matrix by the given angle around the X axis using SIMD
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+mat4.SIMD.rotateX = function (out, a, rad) {
+    var s = SIMD.Float32x4.splat(Math.sin(rad)),
+        c = SIMD.Float32x4.splat(Math.cos(rad));
+
+    if (a !== out) { // If the source and destination differ, copy the unchanged rows
+      out[0]  = a[0];
+      out[1]  = a[1];
+      out[2]  = a[2];
+      out[3]  = a[3];
+      out[12] = a[12];
+      out[13] = a[13];
+      out[14] = a[14];
+      out[15] = a[15];
+    }
+
+    // Perform axis-specific matrix multiplication
+    var a_1 = SIMD.Float32x4.load(a, 4);
+    var a_2 = SIMD.Float32x4.load(a, 8);
+    SIMD.Float32x4.store(out, 4,
+                         SIMD.Float32x4.add(SIMD.Float32x4.mul(a_1, c), SIMD.Float32x4.mul(a_2, s)));
+    SIMD.Float32x4.store(out, 8,
+                         SIMD.Float32x4.sub(SIMD.Float32x4.mul(a_2, c), SIMD.Float32x4.mul(a_1, s)));
+    return out;
+};
+
+/**
+ * Rotates a matrix by the given angle around the X axis using SIMD if availabe and enabled
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+mat4.rotateX = glMatrix.USE_SIMD ? mat4.SIMD.rotateX : mat4.scalar.rotateX;
+
+/**
+ * Rotates a matrix by the given angle around the Y axis not using SIMD
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+mat4.scalar.rotateY = function (out, a, rad) {
+    var s = Math.sin(rad),
+        c = Math.cos(rad),
+        a00 = a[0],
+        a01 = a[1],
+        a02 = a[2],
+        a03 = a[3],
+        a20 = a[8],
+        a21 = a[9],
+        a22 = a[10],
+        a23 = a[11];
+
+    if (a !== out) { // If the source and destination differ, copy the unchanged rows
+        out[4]  = a[4];
+        out[5]  = a[5];
+        out[6]  = a[6];
+        out[7]  = a[7];
+        out[12] = a[12];
+        out[13] = a[13];
+        out[14] = a[14];
+        out[15] = a[15];
+    }
+
+    // Perform axis-specific matrix multiplication
+    out[0] = a00 * c - a20 * s;
+    out[1] = a01 * c - a21 * s;
+    out[2] = a02 * c - a22 * s;
+    out[3] = a03 * c - a23 * s;
+    out[8] = a00 * s + a20 * c;
+    out[9] = a01 * s + a21 * c;
+    out[10] = a02 * s + a22 * c;
+    out[11] = a03 * s + a23 * c;
+    return out;
+};
+
+/**
+ * Rotates a matrix by the given angle around the Y axis using SIMD
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+mat4.SIMD.rotateY = function (out, a, rad) {
+    var s = SIMD.Float32x4.splat(Math.sin(rad)),
+        c = SIMD.Float32x4.splat(Math.cos(rad));
+
+    if (a !== out) { // If the source and destination differ, copy the unchanged rows
+        out[4]  = a[4];
+        out[5]  = a[5];
+        out[6]  = a[6];
+        out[7]  = a[7];
+        out[12] = a[12];
+        out[13] = a[13];
+        out[14] = a[14];
+        out[15] = a[15];
+    }
+
+    // Perform axis-specific matrix multiplication
+    var a_0 = SIMD.Float32x4.load(a, 0);
+    var a_2 = SIMD.Float32x4.load(a, 8);
+    SIMD.Float32x4.store(out, 0,
+                         SIMD.Float32x4.sub(SIMD.Float32x4.mul(a_0, c), SIMD.Float32x4.mul(a_2, s)));
+    SIMD.Float32x4.store(out, 8,
+                         SIMD.Float32x4.add(SIMD.Float32x4.mul(a_0, s), SIMD.Float32x4.mul(a_2, c)));
+    return out;
+};
+
+/**
+ * Rotates a matrix by the given angle around the Y axis if SIMD available and enabled
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+ mat4.rotateY = glMatrix.USE_SIMD ? mat4.SIMD.rotateY : mat4.scalar.rotateY;
+
+/**
+ * Rotates a matrix by the given angle around the Z axis not using SIMD
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+mat4.scalar.rotateZ = function (out, a, rad) {
+    var s = Math.sin(rad),
+        c = Math.cos(rad),
+        a00 = a[0],
+        a01 = a[1],
+        a02 = a[2],
+        a03 = a[3],
+        a10 = a[4],
+        a11 = a[5],
+        a12 = a[6],
+        a13 = a[7];
+
+    if (a !== out) { // If the source and destination differ, copy the unchanged last row
+        out[8]  = a[8];
+        out[9]  = a[9];
+        out[10] = a[10];
+        out[11] = a[11];
+        out[12] = a[12];
+        out[13] = a[13];
+        out[14] = a[14];
+        out[15] = a[15];
+    }
+
+    // Perform axis-specific matrix multiplication
+    out[0] = a00 * c + a10 * s;
+    out[1] = a01 * c + a11 * s;
+    out[2] = a02 * c + a12 * s;
+    out[3] = a03 * c + a13 * s;
+    out[4] = a10 * c - a00 * s;
+    out[5] = a11 * c - a01 * s;
+    out[6] = a12 * c - a02 * s;
+    out[7] = a13 * c - a03 * s;
+    return out;
+};
+
+/**
+ * Rotates a matrix by the given angle around the Z axis using SIMD
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+mat4.SIMD.rotateZ = function (out, a, rad) {
+    var s = SIMD.Float32x4.splat(Math.sin(rad)),
+        c = SIMD.Float32x4.splat(Math.cos(rad));
+
+    if (a !== out) { // If the source and destination differ, copy the unchanged last row
+        out[8]  = a[8];
+        out[9]  = a[9];
+        out[10] = a[10];
+        out[11] = a[11];
+        out[12] = a[12];
+        out[13] = a[13];
+        out[14] = a[14];
+        out[15] = a[15];
+    }
+
+    // Perform axis-specific matrix multiplication
+    var a_0 = SIMD.Float32x4.load(a, 0);
+    var a_1 = SIMD.Float32x4.load(a, 4);
+    SIMD.Float32x4.store(out, 0,
+                         SIMD.Float32x4.add(SIMD.Float32x4.mul(a_0, c), SIMD.Float32x4.mul(a_1, s)));
+    SIMD.Float32x4.store(out, 4,
+                         SIMD.Float32x4.sub(SIMD.Float32x4.mul(a_1, c), SIMD.Float32x4.mul(a_0, s)));
+    return out;
+};
+
+/**
+ * Rotates a matrix by the given angle around the Z axis if SIMD available and enabled
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+ mat4.rotateZ = glMatrix.USE_SIMD ? mat4.SIMD.rotateZ : mat4.scalar.rotateZ;
+
+/**
+ * Creates a matrix from a vector translation
+ * This is equivalent to (but much faster than):
+ *
+ *     mat4.identity(dest);
+ *     mat4.translate(dest, dest, vec);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {vec3} v Translation vector
+ * @returns {mat4} out
+ */
+mat4.fromTranslation = function(out, v) {
+    out[0] = 1;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 0;
+    out[4] = 0;
+    out[5] = 1;
+    out[6] = 0;
+    out[7] = 0;
+    out[8] = 0;
+    out[9] = 0;
+    out[10] = 1;
+    out[11] = 0;
+    out[12] = v[0];
+    out[13] = v[1];
+    out[14] = v[2];
+    out[15] = 1;
+    return out;
+}
+
+/**
+ * Creates a matrix from a vector scaling
+ * This is equivalent to (but much faster than):
+ *
+ *     mat4.identity(dest);
+ *     mat4.scale(dest, dest, vec);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {vec3} v Scaling vector
+ * @returns {mat4} out
+ */
+mat4.fromScaling = function(out, v) {
+    out[0] = v[0];
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 0;
+    out[4] = 0;
+    out[5] = v[1];
+    out[6] = 0;
+    out[7] = 0;
+    out[8] = 0;
+    out[9] = 0;
+    out[10] = v[2];
+    out[11] = 0;
+    out[12] = 0;
+    out[13] = 0;
+    out[14] = 0;
+    out[15] = 1;
+    return out;
+}
+
+/**
+ * Creates a matrix from a given angle around a given axis
+ * This is equivalent to (but much faster than):
+ *
+ *     mat4.identity(dest);
+ *     mat4.rotate(dest, dest, rad, axis);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @param {vec3} axis the axis to rotate around
+ * @returns {mat4} out
+ */
+mat4.fromRotation = function(out, rad, axis) {
+    var x = axis[0], y = axis[1], z = axis[2],
+        len = Math.sqrt(x * x + y * y + z * z),
+        s, c, t;
+
+    if (Math.abs(len) < glMatrix.EPSILON) { return null; }
+
+    len = 1 / len;
+    x *= len;
+    y *= len;
+    z *= len;
+
+    s = Math.sin(rad);
+    c = Math.cos(rad);
+    t = 1 - c;
+
+    // Perform rotation-specific matrix multiplication
+    out[0] = x * x * t + c;
+    out[1] = y * x * t + z * s;
+    out[2] = z * x * t - y * s;
+    out[3] = 0;
+    out[4] = x * y * t - z * s;
+    out[5] = y * y * t + c;
+    out[6] = z * y * t + x * s;
+    out[7] = 0;
+    out[8] = x * z * t + y * s;
+    out[9] = y * z * t - x * s;
+    out[10] = z * z * t + c;
+    out[11] = 0;
+    out[12] = 0;
+    out[13] = 0;
+    out[14] = 0;
+    out[15] = 1;
+    return out;
+}
+
+/**
+ * Creates a matrix from the given angle around the X axis
+ * This is equivalent to (but much faster than):
+ *
+ *     mat4.identity(dest);
+ *     mat4.rotateX(dest, dest, rad);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+mat4.fromXRotation = function(out, rad) {
+    var s = Math.sin(rad),
+        c = Math.cos(rad);
+
+    // Perform axis-specific matrix multiplication
+    out[0]  = 1;
+    out[1]  = 0;
+    out[2]  = 0;
+    out[3]  = 0;
+    out[4] = 0;
+    out[5] = c;
+    out[6] = s;
+    out[7] = 0;
+    out[8] = 0;
+    out[9] = -s;
+    out[10] = c;
+    out[11] = 0;
+    out[12] = 0;
+    out[13] = 0;
+    out[14] = 0;
+    out[15] = 1;
+    return out;
+}
+
+/**
+ * Creates a matrix from the given angle around the Y axis
+ * This is equivalent to (but much faster than):
+ *
+ *     mat4.identity(dest);
+ *     mat4.rotateY(dest, dest, rad);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+mat4.fromYRotation = function(out, rad) {
+    var s = Math.sin(rad),
+        c = Math.cos(rad);
+
+    // Perform axis-specific matrix multiplication
+    out[0]  = c;
+    out[1]  = 0;
+    out[2]  = -s;
+    out[3]  = 0;
+    out[4] = 0;
+    out[5] = 1;
+    out[6] = 0;
+    out[7] = 0;
+    out[8] = s;
+    out[9] = 0;
+    out[10] = c;
+    out[11] = 0;
+    out[12] = 0;
+    out[13] = 0;
+    out[14] = 0;
+    out[15] = 1;
+    return out;
+}
+
+/**
+ * Creates a matrix from the given angle around the Z axis
+ * This is equivalent to (but much faster than):
+ *
+ *     mat4.identity(dest);
+ *     mat4.rotateZ(dest, dest, rad);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+mat4.fromZRotation = function(out, rad) {
+    var s = Math.sin(rad),
+        c = Math.cos(rad);
+
+    // Perform axis-specific matrix multiplication
+    out[0]  = c;
+    out[1]  = s;
+    out[2]  = 0;
+    out[3]  = 0;
+    out[4] = -s;
+    out[5] = c;
+    out[6] = 0;
+    out[7] = 0;
+    out[8] = 0;
+    out[9] = 0;
+    out[10] = 1;
+    out[11] = 0;
+    out[12] = 0;
+    out[13] = 0;
+    out[14] = 0;
+    out[15] = 1;
+    return out;
+}
+
+/**
+ * Creates a matrix from a quaternion rotation and vector translation
+ * This is equivalent to (but much faster than):
+ *
+ *     mat4.identity(dest);
+ *     mat4.translate(dest, vec);
+ *     var quatMat = mat4.create();
+ *     quat4.toMat4(quat, quatMat);
+ *     mat4.multiply(dest, quatMat);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {quat4} q Rotation quaternion
+ * @param {vec3} v Translation vector
+ * @returns {mat4} out
+ */
+mat4.fromRotationTranslation = function (out, q, v) {
+    // Quaternion math
+    var x = q[0], y = q[1], z = q[2], w = q[3],
+        x2 = x + x,
+        y2 = y + y,
+        z2 = z + z,
+
+        xx = x * x2,
+        xy = x * y2,
+        xz = x * z2,
+        yy = y * y2,
+        yz = y * z2,
+        zz = z * z2,
+        wx = w * x2,
+        wy = w * y2,
+        wz = w * z2;
+
+    out[0] = 1 - (yy + zz);
+    out[1] = xy + wz;
+    out[2] = xz - wy;
+    out[3] = 0;
+    out[4] = xy - wz;
+    out[5] = 1 - (xx + zz);
+    out[6] = yz + wx;
+    out[7] = 0;
+    out[8] = xz + wy;
+    out[9] = yz - wx;
+    out[10] = 1 - (xx + yy);
+    out[11] = 0;
+    out[12] = v[0];
+    out[13] = v[1];
+    out[14] = v[2];
+    out[15] = 1;
+
+    return out;
+};
+
+/**
+ * Returns the translation vector component of a transformation
+ *  matrix. If a matrix is built with fromRotationTranslation,
+ *  the returned vector will be the same as the translation vector
+ *  originally supplied.
+ * @param  {vec3} out Vector to receive translation component
+ * @param  {mat4} mat Matrix to be decomposed (input)
+ * @return {vec3} out
+ */
+mat4.getTranslation = function (out, mat) {
+  out[0] = mat[12];
+  out[1] = mat[13];
+  out[2] = mat[14];
+
+  return out;
+};
+
+/**
+ * Returns a quaternion representing the rotational component
+ *  of a transformation matrix. If a matrix is built with
+ *  fromRotationTranslation, the returned quaternion will be the
+ *  same as the quaternion originally supplied.
+ * @param {quat} out Quaternion to receive the rotation component
+ * @param {mat4} mat Matrix to be decomposed (input)
+ * @return {quat} out
+ */
+mat4.getRotation = function (out, mat) {
+  // Algorithm taken from http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
+  var trace = mat[0] + mat[5] + mat[10];
+  var S = 0;
+
+  if (trace > 0) { 
+    S = Math.sqrt(trace + 1.0) * 2;
+    out[3] = 0.25 * S;
+    out[0] = (mat[6] - mat[9]) / S;
+    out[1] = (mat[8] - mat[2]) / S; 
+    out[2] = (mat[1] - mat[4]) / S; 
+  } else if ((mat[0] > mat[5])&(mat[0] > mat[10])) { 
+    S = Math.sqrt(1.0 + mat[0] - mat[5] - mat[10]) * 2;
+    out[3] = (mat[6] - mat[9]) / S;
+    out[0] = 0.25 * S;
+    out[1] = (mat[1] + mat[4]) / S; 
+    out[2] = (mat[8] + mat[2]) / S; 
+  } else if (mat[5] > mat[10]) { 
+    S = Math.sqrt(1.0 + mat[5] - mat[0] - mat[10]) * 2;
+    out[3] = (mat[8] - mat[2]) / S;
+    out[0] = (mat[1] + mat[4]) / S; 
+    out[1] = 0.25 * S;
+    out[2] = (mat[6] + mat[9]) / S; 
+  } else { 
+    S = Math.sqrt(1.0 + mat[10] - mat[0] - mat[5]) * 2;
+    out[3] = (mat[1] - mat[4]) / S;
+    out[0] = (mat[8] + mat[2]) / S;
+    out[1] = (mat[6] + mat[9]) / S;
+    out[2] = 0.25 * S;
+  }
+
+  return out;
+};
+
+/**
+ * Creates a matrix from a quaternion rotation, vector translation and vector scale
+ * This is equivalent to (but much faster than):
+ *
+ *     mat4.identity(dest);
+ *     mat4.translate(dest, vec);
+ *     var quatMat = mat4.create();
+ *     quat4.toMat4(quat, quatMat);
+ *     mat4.multiply(dest, quatMat);
+ *     mat4.scale(dest, scale)
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {quat4} q Rotation quaternion
+ * @param {vec3} v Translation vector
+ * @param {vec3} s Scaling vector
+ * @returns {mat4} out
+ */
+mat4.fromRotationTranslationScale = function (out, q, v, s) {
+    // Quaternion math
+    var x = q[0], y = q[1], z = q[2], w = q[3],
+        x2 = x + x,
+        y2 = y + y,
+        z2 = z + z,
+
+        xx = x * x2,
+        xy = x * y2,
+        xz = x * z2,
+        yy = y * y2,
+        yz = y * z2,
+        zz = z * z2,
+        wx = w * x2,
+        wy = w * y2,
+        wz = w * z2,
+        sx = s[0],
+        sy = s[1],
+        sz = s[2];
+
+    out[0] = (1 - (yy + zz)) * sx;
+    out[1] = (xy + wz) * sx;
+    out[2] = (xz - wy) * sx;
+    out[3] = 0;
+    out[4] = (xy - wz) * sy;
+    out[5] = (1 - (xx + zz)) * sy;
+    out[6] = (yz + wx) * sy;
+    out[7] = 0;
+    out[8] = (xz + wy) * sz;
+    out[9] = (yz - wx) * sz;
+    out[10] = (1 - (xx + yy)) * sz;
+    out[11] = 0;
+    out[12] = v[0];
+    out[13] = v[1];
+    out[14] = v[2];
+    out[15] = 1;
+
+    return out;
+};
+
+/**
+ * Creates a matrix from a quaternion rotation, vector translation and vector scale, rotating and scaling around the given origin
+ * This is equivalent to (but much faster than):
+ *
+ *     mat4.identity(dest);
+ *     mat4.translate(dest, vec);
+ *     mat4.translate(dest, origin);
+ *     var quatMat = mat4.create();
+ *     quat4.toMat4(quat, quatMat);
+ *     mat4.multiply(dest, quatMat);
+ *     mat4.scale(dest, scale)
+ *     mat4.translate(dest, negativeOrigin);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {quat4} q Rotation quaternion
+ * @param {vec3} v Translation vector
+ * @param {vec3} s Scaling vector
+ * @param {vec3} o The origin vector around which to scale and rotate
+ * @returns {mat4} out
+ */
+mat4.fromRotationTranslationScaleOrigin = function (out, q, v, s, o) {
+  // Quaternion math
+  var x = q[0], y = q[1], z = q[2], w = q[3],
+      x2 = x + x,
+      y2 = y + y,
+      z2 = z + z,
+
+      xx = x * x2,
+      xy = x * y2,
+      xz = x * z2,
+      yy = y * y2,
+      yz = y * z2,
+      zz = z * z2,
+      wx = w * x2,
+      wy = w * y2,
+      wz = w * z2,
+
+      sx = s[0],
+      sy = s[1],
+      sz = s[2],
+
+      ox = o[0],
+      oy = o[1],
+      oz = o[2];
+
+  out[0] = (1 - (yy + zz)) * sx;
+  out[1] = (xy + wz) * sx;
+  out[2] = (xz - wy) * sx;
+  out[3] = 0;
+  out[4] = (xy - wz) * sy;
+  out[5] = (1 - (xx + zz)) * sy;
+  out[6] = (yz + wx) * sy;
+  out[7] = 0;
+  out[8] = (xz + wy) * sz;
+  out[9] = (yz - wx) * sz;
+  out[10] = (1 - (xx + yy)) * sz;
+  out[11] = 0;
+  out[12] = v[0] + ox - (out[0] * ox + out[4] * oy + out[8] * oz);
+  out[13] = v[1] + oy - (out[1] * ox + out[5] * oy + out[9] * oz);
+  out[14] = v[2] + oz - (out[2] * ox + out[6] * oy + out[10] * oz);
+  out[15] = 1;
+
+  return out;
+};
+
+/**
+ * Calculates a 4x4 matrix from the given quaternion
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {quat} q Quaternion to create matrix from
+ *
+ * @returns {mat4} out
+ */
+mat4.fromQuat = function (out, q) {
+    var x = q[0], y = q[1], z = q[2], w = q[3],
+        x2 = x + x,
+        y2 = y + y,
+        z2 = z + z,
+
+        xx = x * x2,
+        yx = y * x2,
+        yy = y * y2,
+        zx = z * x2,
+        zy = z * y2,
+        zz = z * z2,
+        wx = w * x2,
+        wy = w * y2,
+        wz = w * z2;
+
+    out[0] = 1 - yy - zz;
+    out[1] = yx + wz;
+    out[2] = zx - wy;
+    out[3] = 0;
+
+    out[4] = yx - wz;
+    out[5] = 1 - xx - zz;
+    out[6] = zy + wx;
+    out[7] = 0;
+
+    out[8] = zx + wy;
+    out[9] = zy - wx;
+    out[10] = 1 - xx - yy;
+    out[11] = 0;
+
+    out[12] = 0;
+    out[13] = 0;
+    out[14] = 0;
+    out[15] = 1;
+
+    return out;
+};
+
+/**
+ * Generates a frustum matrix with the given bounds
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {Number} left Left bound of the frustum
+ * @param {Number} right Right bound of the frustum
+ * @param {Number} bottom Bottom bound of the frustum
+ * @param {Number} top Top bound of the frustum
+ * @param {Number} near Near bound of the frustum
+ * @param {Number} far Far bound of the frustum
+ * @returns {mat4} out
+ */
+mat4.frustum = function (out, left, right, bottom, top, near, far) {
+    var rl = 1 / (right - left),
+        tb = 1 / (top - bottom),
+        nf = 1 / (near - far);
+    out[0] = (near * 2) * rl;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 0;
+    out[4] = 0;
+    out[5] = (near * 2) * tb;
+    out[6] = 0;
+    out[7] = 0;
+    out[8] = (right + left) * rl;
+    out[9] = (top + bottom) * tb;
+    out[10] = (far + near) * nf;
+    out[11] = -1;
+    out[12] = 0;
+    out[13] = 0;
+    out[14] = (far * near * 2) * nf;
+    out[15] = 0;
+    return out;
+};
+
+/**
+ * Generates a perspective projection matrix with the given bounds
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {number} fovy Vertical field of view in radians
+ * @param {number} aspect Aspect ratio. typically viewport width/height
+ * @param {number} near Near bound of the frustum
+ * @param {number} far Far bound of the frustum
+ * @returns {mat4} out
+ */
+mat4.perspective = function (out, fovy, aspect, near, far) {
+    var f = 1.0 / Math.tan(fovy / 2),
+        nf = 1 / (near - far);
+    out[0] = f / aspect;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 0;
+    out[4] = 0;
+    out[5] = f;
+    out[6] = 0;
+    out[7] = 0;
+    out[8] = 0;
+    out[9] = 0;
+    out[10] = (far + near) * nf;
+    out[11] = -1;
+    out[12] = 0;
+    out[13] = 0;
+    out[14] = (2 * far * near) * nf;
+    out[15] = 0;
+    return out;
+};
+
+/**
+ * Generates a perspective projection matrix with the given field of view.
+ * This is primarily useful for generating projection matrices to be used
+ * with the still experiemental WebVR API.
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {Object} fov Object containing the following values: upDegrees, downDegrees, leftDegrees, rightDegrees
+ * @param {number} near Near bound of the frustum
+ * @param {number} far Far bound of the frustum
+ * @returns {mat4} out
+ */
+mat4.perspectiveFromFieldOfView = function (out, fov, near, far) {
+    var upTan = Math.tan(fov.upDegrees * Math.PI/180.0),
+        downTan = Math.tan(fov.downDegrees * Math.PI/180.0),
+        leftTan = Math.tan(fov.leftDegrees * Math.PI/180.0),
+        rightTan = Math.tan(fov.rightDegrees * Math.PI/180.0),
+        xScale = 2.0 / (leftTan + rightTan),
+        yScale = 2.0 / (upTan + downTan);
+
+    out[0] = xScale;
+    out[1] = 0.0;
+    out[2] = 0.0;
+    out[3] = 0.0;
+    out[4] = 0.0;
+    out[5] = yScale;
+    out[6] = 0.0;
+    out[7] = 0.0;
+    out[8] = -((leftTan - rightTan) * xScale * 0.5);
+    out[9] = ((upTan - downTan) * yScale * 0.5);
+    out[10] = far / (near - far);
+    out[11] = -1.0;
+    out[12] = 0.0;
+    out[13] = 0.0;
+    out[14] = (far * near) / (near - far);
+    out[15] = 0.0;
+    return out;
+}
+
+/**
+ * Generates a orthogonal projection matrix with the given bounds
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {number} left Left bound of the frustum
+ * @param {number} right Right bound of the frustum
+ * @param {number} bottom Bottom bound of the frustum
+ * @param {number} top Top bound of the frustum
+ * @param {number} near Near bound of the frustum
+ * @param {number} far Far bound of the frustum
+ * @returns {mat4} out
+ */
+mat4.ortho = function (out, left, right, bottom, top, near, far) {
+    var lr = 1 / (left - right),
+        bt = 1 / (bottom - top),
+        nf = 1 / (near - far);
+    out[0] = -2 * lr;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 0;
+    out[4] = 0;
+    out[5] = -2 * bt;
+    out[6] = 0;
+    out[7] = 0;
+    out[8] = 0;
+    out[9] = 0;
+    out[10] = 2 * nf;
+    out[11] = 0;
+    out[12] = (left + right) * lr;
+    out[13] = (top + bottom) * bt;
+    out[14] = (far + near) * nf;
+    out[15] = 1;
+    return out;
+};
+
+/**
+ * Generates a look-at matrix with the given eye position, focal point, and up axis
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {vec3} eye Position of the viewer
+ * @param {vec3} center Point the viewer is looking at
+ * @param {vec3} up vec3 pointing up
+ * @returns {mat4} out
+ */
+mat4.lookAt = function (out, eye, center, up) {
+    var x0, x1, x2, y0, y1, y2, z0, z1, z2, len,
+        eyex = eye[0],
+        eyey = eye[1],
+        eyez = eye[2],
+        upx = up[0],
+        upy = up[1],
+        upz = up[2],
+        centerx = center[0],
+        centery = center[1],
+        centerz = center[2];
+
+    if (Math.abs(eyex - centerx) < glMatrix.EPSILON &&
+        Math.abs(eyey - centery) < glMatrix.EPSILON &&
+        Math.abs(eyez - centerz) < glMatrix.EPSILON) {
+        return mat4.identity(out);
+    }
+
+    z0 = eyex - centerx;
+    z1 = eyey - centery;
+    z2 = eyez - centerz;
+
+    len = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2);
+    z0 *= len;
+    z1 *= len;
+    z2 *= len;
+
+    x0 = upy * z2 - upz * z1;
+    x1 = upz * z0 - upx * z2;
+    x2 = upx * z1 - upy * z0;
+    len = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2);
+    if (!len) {
+        x0 = 0;
+        x1 = 0;
+        x2 = 0;
+    } else {
+        len = 1 / len;
+        x0 *= len;
+        x1 *= len;
+        x2 *= len;
+    }
+
+    y0 = z1 * x2 - z2 * x1;
+    y1 = z2 * x0 - z0 * x2;
+    y2 = z0 * x1 - z1 * x0;
+
+    len = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2);
+    if (!len) {
+        y0 = 0;
+        y1 = 0;
+        y2 = 0;
+    } else {
+        len = 1 / len;
+        y0 *= len;
+        y1 *= len;
+        y2 *= len;
+    }
+
+    out[0] = x0;
+    out[1] = y0;
+    out[2] = z0;
+    out[3] = 0;
+    out[4] = x1;
+    out[5] = y1;
+    out[6] = z1;
+    out[7] = 0;
+    out[8] = x2;
+    out[9] = y2;
+    out[10] = z2;
+    out[11] = 0;
+    out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez);
+    out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez);
+    out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez);
+    out[15] = 1;
+
+    return out;
+};
+
+/**
+ * Returns a string representation of a mat4
+ *
+ * @param {mat4} mat matrix to represent as a string
+ * @returns {String} string representation of the matrix
+ */
+mat4.str = function (a) {
+    return 'mat4(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + a[3] + ', ' +
+                    a[4] + ', ' + a[5] + ', ' + a[6] + ', ' + a[7] + ', ' +
+                    a[8] + ', ' + a[9] + ', ' + a[10] + ', ' + a[11] + ', ' +
+                    a[12] + ', ' + a[13] + ', ' + a[14] + ', ' + a[15] + ')';
+};
+
+/**
+ * Returns Frobenius norm of a mat4
+ *
+ * @param {mat4} a the matrix to calculate Frobenius norm of
+ * @returns {Number} Frobenius norm
+ */
+mat4.frob = function (a) {
+    return(Math.sqrt(Math.pow(a[0], 2) + Math.pow(a[1], 2) + Math.pow(a[2], 2) + Math.pow(a[3], 2) + Math.pow(a[4], 2) + Math.pow(a[5], 2) + Math.pow(a[6], 2) + Math.pow(a[7], 2) + Math.pow(a[8], 2) + Math.pow(a[9], 2) + Math.pow(a[10], 2) + Math.pow(a[11], 2) + Math.pow(a[12], 2) + Math.pow(a[13], 2) + Math.pow(a[14], 2) + Math.pow(a[15], 2) ))
+};
+
+/**
+ * Adds two mat4's
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the first operand
+ * @param {mat4} b the second operand
+ * @returns {mat4} out
+ */
+mat4.add = function(out, a, b) {
+    out[0] = a[0] + b[0];
+    out[1] = a[1] + b[1];
+    out[2] = a[2] + b[2];
+    out[3] = a[3] + b[3];
+    out[4] = a[4] + b[4];
+    out[5] = a[5] + b[5];
+    out[6] = a[6] + b[6];
+    out[7] = a[7] + b[7];
+    out[8] = a[8] + b[8];
+    out[9] = a[9] + b[9];
+    out[10] = a[10] + b[10];
+    out[11] = a[11] + b[11];
+    out[12] = a[12] + b[12];
+    out[13] = a[13] + b[13];
+    out[14] = a[14] + b[14];
+    out[15] = a[15] + b[15];
+    return out;
+};
+
+/**
+ * Subtracts matrix b from matrix a
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the first operand
+ * @param {mat4} b the second operand
+ * @returns {mat4} out
+ */
+mat4.subtract = function(out, a, b) {
+    out[0] = a[0] - b[0];
+    out[1] = a[1] - b[1];
+    out[2] = a[2] - b[2];
+    out[3] = a[3] - b[3];
+    out[4] = a[4] - b[4];
+    out[5] = a[5] - b[5];
+    out[6] = a[6] - b[6];
+    out[7] = a[7] - b[7];
+    out[8] = a[8] - b[8];
+    out[9] = a[9] - b[9];
+    out[10] = a[10] - b[10];
+    out[11] = a[11] - b[11];
+    out[12] = a[12] - b[12];
+    out[13] = a[13] - b[13];
+    out[14] = a[14] - b[14];
+    out[15] = a[15] - b[15];
+    return out;
+};
+
+/**
+ * Alias for {@link mat4.subtract}
+ * @function
+ */
+mat4.sub = mat4.subtract;
+
+/**
+ * Multiply each element of the matrix by a scalar.
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to scale
+ * @param {Number} b amount to scale the matrix's elements by
+ * @returns {mat4} out
+ */
+mat4.multiplyScalar = function(out, a, b) {
+    out[0] = a[0] * b;
+    out[1] = a[1] * b;
+    out[2] = a[2] * b;
+    out[3] = a[3] * b;
+    out[4] = a[4] * b;
+    out[5] = a[5] * b;
+    out[6] = a[6] * b;
+    out[7] = a[7] * b;
+    out[8] = a[8] * b;
+    out[9] = a[9] * b;
+    out[10] = a[10] * b;
+    out[11] = a[11] * b;
+    out[12] = a[12] * b;
+    out[13] = a[13] * b;
+    out[14] = a[14] * b;
+    out[15] = a[15] * b;
+    return out;
+};
+
+/**
+ * Adds two mat4's after multiplying each element of the second operand by a scalar value.
+ *
+ * @param {mat4} out the receiving vector
+ * @param {mat4} a the first operand
+ * @param {mat4} b the second operand
+ * @param {Number} scale the amount to scale b's elements by before adding
+ * @returns {mat4} out
+ */
+mat4.multiplyScalarAndAdd = function(out, a, b, scale) {
+    out[0] = a[0] + (b[0] * scale);
+    out[1] = a[1] + (b[1] * scale);
+    out[2] = a[2] + (b[2] * scale);
+    out[3] = a[3] + (b[3] * scale);
+    out[4] = a[4] + (b[4] * scale);
+    out[5] = a[5] + (b[5] * scale);
+    out[6] = a[6] + (b[6] * scale);
+    out[7] = a[7] + (b[7] * scale);
+    out[8] = a[8] + (b[8] * scale);
+    out[9] = a[9] + (b[9] * scale);
+    out[10] = a[10] + (b[10] * scale);
+    out[11] = a[11] + (b[11] * scale);
+    out[12] = a[12] + (b[12] * scale);
+    out[13] = a[13] + (b[13] * scale);
+    out[14] = a[14] + (b[14] * scale);
+    out[15] = a[15] + (b[15] * scale);
+    return out;
+};
+
+/**
+ * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {mat4} a The first matrix.
+ * @param {mat4} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+mat4.exactEquals = function (a, b) {
+    return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && 
+           a[4] === b[4] && a[5] === b[5] && a[6] === b[6] && a[7] === b[7] && 
+           a[8] === b[8] && a[9] === b[9] && a[10] === b[10] && a[11] === b[11] &&
+           a[12] === b[12] && a[13] === b[13] && a[14] === b[14] && a[15] === b[15];
+};
+
+/**
+ * Returns whether or not the matrices have approximately the same elements in the same position.
+ *
+ * @param {mat4} a The first matrix.
+ * @param {mat4} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+mat4.equals = function (a, b) {
+    var a0  = a[0],  a1  = a[1],  a2  = a[2],  a3  = a[3],
+        a4  = a[4],  a5  = a[5],  a6  = a[6],  a7  = a[7], 
+        a8  = a[8],  a9  = a[9],  a10 = a[10], a11 = a[11], 
+        a12 = a[12], a13 = a[13], a14 = a[14], a15 = a[15];
+
+    var b0  = b[0],  b1  = b[1],  b2  = b[2],  b3  = b[3],
+        b4  = b[4],  b5  = b[5],  b6  = b[6],  b7  = b[7], 
+        b8  = b[8],  b9  = b[9],  b10 = b[10], b11 = b[11], 
+        b12 = b[12], b13 = b[13], b14 = b[14], b15 = b[15];
+
+    return (Math.abs(a0 - b0) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a0), Math.abs(b0)) &&
+            Math.abs(a1 - b1) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a1), Math.abs(b1)) &&
+            Math.abs(a2 - b2) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a2), Math.abs(b2)) &&
+            Math.abs(a3 - b3) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a3), Math.abs(b3)) &&
+            Math.abs(a4 - b4) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a4), Math.abs(b4)) &&
+            Math.abs(a5 - b5) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a5), Math.abs(b5)) &&
+            Math.abs(a6 - b6) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a6), Math.abs(b6)) &&
+            Math.abs(a7 - b7) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a7), Math.abs(b7)) &&
+            Math.abs(a8 - b8) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a8), Math.abs(b8)) &&
+            Math.abs(a9 - b9) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a9), Math.abs(b9)) &&
+            Math.abs(a10 - b10) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a10), Math.abs(b10)) &&
+            Math.abs(a11 - b11) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a11), Math.abs(b11)) &&
+            Math.abs(a12 - b12) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a12), Math.abs(b12)) &&
+            Math.abs(a13 - b13) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a13), Math.abs(b13)) &&
+            Math.abs(a14 - b14) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a14), Math.abs(b14)) &&
+            Math.abs(a15 - b15) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a15), Math.abs(b15)));
 };
 
 
 
+module.exports = mat4;
 
+},{"./common.js":266}],271:[function(require,module,exports){
+/* Copyright (c) 2015, Brandon Jones, Colin MacKenzie IV.
 
-Lexer.prototype.lex=function(src){
-src=src.
-replace(/\r\n|\r/g,'\n').
-replace(/\t/g,'    ').
-replace(/\u00a0/g,' ').
-replace(/\u2424/g,'\n');
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
 
-return this.token(src,true);
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE. */
+
+var glMatrix = require("./common.js");
+var mat3 = require("./mat3.js");
+var vec3 = require("./vec3.js");
+var vec4 = require("./vec4.js");
+
+/**
+ * @class Quaternion
+ * @name quat
+ */
+var quat = {};
+
+/**
+ * Creates a new identity quat
+ *
+ * @returns {quat} a new quaternion
+ */
+quat.create = function() {
+    var out = new glMatrix.ARRAY_TYPE(4);
+    out[0] = 0;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 1;
+    return out;
 };
 
+/**
+ * Sets a quaternion to represent the shortest rotation from one
+ * vector to another.
+ *
+ * Both vectors are assumed to be unit length.
+ *
+ * @param {quat} out the receiving quaternion.
+ * @param {vec3} a the initial vector
+ * @param {vec3} b the destination vector
+ * @returns {quat} out
+ */
+quat.rotationTo = (function() {
+    var tmpvec3 = vec3.create();
+    var xUnitVec3 = vec3.fromValues(1,0,0);
+    var yUnitVec3 = vec3.fromValues(0,1,0);
 
+    return function(out, a, b) {
+        var dot = vec3.dot(a, b);
+        if (dot < -0.999999) {
+            vec3.cross(tmpvec3, xUnitVec3, a);
+            if (vec3.length(tmpvec3) < 0.000001)
+                vec3.cross(tmpvec3, yUnitVec3, a);
+            vec3.normalize(tmpvec3, tmpvec3);
+            quat.setAxisAngle(out, tmpvec3, Math.PI);
+            return out;
+        } else if (dot > 0.999999) {
+            out[0] = 0;
+            out[1] = 0;
+            out[2] = 0;
+            out[3] = 1;
+            return out;
+        } else {
+            vec3.cross(tmpvec3, a, b);
+            out[0] = tmpvec3[0];
+            out[1] = tmpvec3[1];
+            out[2] = tmpvec3[2];
+            out[3] = 1 + dot;
+            return quat.normalize(out, out);
+        }
+    };
+})();
 
+/**
+ * Sets the specified quaternion with values corresponding to the given
+ * axes. Each axis is a vec3 and is expected to be unit length and
+ * perpendicular to all other specified axes.
+ *
+ * @param {vec3} view  the vector representing the viewing direction
+ * @param {vec3} right the vector representing the local "right" direction
+ * @param {vec3} up    the vector representing the local "up" direction
+ * @returns {quat} out
+ */
+quat.setAxes = (function() {
+    var matr = mat3.create();
 
+    return function(out, view, right, up) {
+        matr[0] = right[0];
+        matr[3] = right[1];
+        matr[6] = right[2];
 
-Lexer.prototype.token=function(src,top,bq){
-var src=src.replace(/^ +$/gm,''),
-next,
-loose,
-cap,
-bull,
-b,
-item,
-space,
-i,
-l;
+        matr[1] = up[0];
+        matr[4] = up[1];
+        matr[7] = up[2];
 
-while(src){
+        matr[2] = -view[0];
+        matr[5] = -view[1];
+        matr[8] = -view[2];
 
-if(cap=this.rules.newline.exec(src)){
-src=src.substring(cap[0].length);
-if(cap[0].length>1){
-this.tokens.push({
-type:'space'});
+        return quat.normalize(out, quat.fromMat3(out, matr));
+    };
+})();
 
-}
-}
+/**
+ * Creates a new quat initialized with values from an existing quaternion
+ *
+ * @param {quat} a quaternion to clone
+ * @returns {quat} a new quaternion
+ * @function
+ */
+quat.clone = vec4.clone;
 
+/**
+ * Creates a new quat initialized with the given values
+ *
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @param {Number} w W component
+ * @returns {quat} a new quaternion
+ * @function
+ */
+quat.fromValues = vec4.fromValues;
 
-if(cap=this.rules.code.exec(src)){
-src=src.substring(cap[0].length);
-cap=cap[0].replace(/^ {4}/gm,'');
-this.tokens.push({
-type:'code',
-text:!this.options.pedantic?
-cap.replace(/\n+$/,''):
-cap});
+/**
+ * Copy the values from one quat to another
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {quat} a the source quaternion
+ * @returns {quat} out
+ * @function
+ */
+quat.copy = vec4.copy;
 
-continue;
-}
+/**
+ * Set the components of a quat to the given values
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @param {Number} w W component
+ * @returns {quat} out
+ * @function
+ */
+quat.set = vec4.set;
 
-
-if(cap=this.rules.fences.exec(src)){
-src=src.substring(cap[0].length);
-this.tokens.push({
-type:'code',
-lang:cap[2],
-text:cap[3]||''});
-
-continue;
-}
-
-
-if(cap=this.rules.heading.exec(src)){
-src=src.substring(cap[0].length);
-this.tokens.push({
-type:'heading',
-depth:cap[1].length,
-text:cap[2]});
-
-continue;
-}
-
-
-if(top&&(cap=this.rules.nptable.exec(src))){
-src=src.substring(cap[0].length);
-
-item={
-type:'table',
-header:cap[1].replace(/^ *| *\| *$/g,'').split(/ *\| */),
-align:cap[2].replace(/^ *|\| *$/g,'').split(/ *\| */),
-cells:cap[3].replace(/\n$/,'').split('\n')};
-
-
-for(i=0;i<item.align.length;i++){
-if(/^ *-+: *$/.test(item.align[i])){
-item.align[i]='right';
-}else if(/^ *:-+: *$/.test(item.align[i])){
-item.align[i]='center';
-}else if(/^ *:-+ *$/.test(item.align[i])){
-item.align[i]='left';
-}else{
-item.align[i]=null;
-}
-}
-
-for(i=0;i<item.cells.length;i++){
-item.cells[i]=item.cells[i].split(/ *\| */);
-}
-
-this.tokens.push(item);
-
-continue;
-}
-
-
-if(cap=this.rules.lheading.exec(src)){
-src=src.substring(cap[0].length);
-this.tokens.push({
-type:'heading',
-depth:cap[2]==='='?1:2,
-text:cap[1]});
-
-continue;
-}
-
-
-if(cap=this.rules.hr.exec(src)){
-src=src.substring(cap[0].length);
-this.tokens.push({
-type:'hr'});
-
-continue;
-}
-
-
-if(cap=this.rules.blockquote.exec(src)){
-src=src.substring(cap[0].length);
-
-this.tokens.push({
-type:'blockquote_start'});
-
-
-cap=cap[0].replace(/^ *> ?/gm,'');
-
-
-
-
-this.token(cap,top,true);
-
-this.tokens.push({
-type:'blockquote_end'});
-
-
-continue;
-}
-
-
-if(cap=this.rules.list.exec(src)){
-src=src.substring(cap[0].length);
-bull=cap[2];
-
-this.tokens.push({
-type:'list_start',
-ordered:bull.length>1});
-
-
-
-cap=cap[0].match(this.rules.item);
-
-next=false;
-l=cap.length;
-i=0;
-
-for(;i<l;i++){
-item=cap[i];
-
-
-
-space=item.length;
-item=item.replace(/^ *([*+-]|\d+\.) +/,'');
-
-
-
-if(~item.indexOf('\n ')){
-space-=item.length;
-item=!this.options.pedantic?
-item.replace(new RegExp('^ {1,'+space+'}','gm'),''):
-item.replace(/^ {1,4}/gm,'');
-}
-
-
-
-if(this.options.smartLists&&i!==l-1){
-b=block.bullet.exec(cap[i+1])[0];
-if(bull!==b&&!(bull.length>1&&b.length>1)){
-src=cap.slice(i+1).join('\n')+src;
-i=l-1;
-}
-}
-
-
-
-
-loose=next||/\n\n(?!\s*$)/.test(item);
-if(i!==l-1){
-next=item.charAt(item.length-1)==='\n';
-if(!loose)loose=next;
-}
-
-this.tokens.push({
-type:loose?
-'loose_item_start':
-'list_item_start'});
-
-
-
-this.token(item,false,bq);
-
-this.tokens.push({
-type:'list_item_end'});
-
-}
-
-this.tokens.push({
-type:'list_end'});
-
-
-continue;
-}
-
-
-if(cap=this.rules.html.exec(src)){
-src=src.substring(cap[0].length);
-this.tokens.push({
-type:this.options.sanitize?
-'paragraph':
-'html',
-pre:!this.options.sanitizer&&(
-cap[1]==='pre'||cap[1]==='script'||cap[1]==='style'),
-text:cap[0]});
-
-continue;
-}
-
-
-if(!bq&&top&&(cap=this.rules.def.exec(src))){
-src=src.substring(cap[0].length);
-this.tokens.links[cap[1].toLowerCase()]={
-href:cap[2],
-title:cap[3]};
-
-continue;
-}
-
-
-if(top&&(cap=this.rules.table.exec(src))){
-src=src.substring(cap[0].length);
-
-item={
-type:'table',
-header:cap[1].replace(/^ *| *\| *$/g,'').split(/ *\| */),
-align:cap[2].replace(/^ *|\| *$/g,'').split(/ *\| */),
-cells:cap[3].replace(/(?: *\| *)?\n$/,'').split('\n')};
-
-
-for(i=0;i<item.align.length;i++){
-if(/^ *-+: *$/.test(item.align[i])){
-item.align[i]='right';
-}else if(/^ *:-+: *$/.test(item.align[i])){
-item.align[i]='center';
-}else if(/^ *:-+ *$/.test(item.align[i])){
-item.align[i]='left';
-}else{
-item.align[i]=null;
-}
-}
-
-for(i=0;i<item.cells.length;i++){
-item.cells[i]=item.cells[i].
-replace(/^ *\| *| *\| *$/g,'').
-split(/ *\| */);
-}
-
-this.tokens.push(item);
-
-continue;
-}
-
-
-if(top&&(cap=this.rules.paragraph.exec(src))){
-src=src.substring(cap[0].length);
-this.tokens.push({
-type:'paragraph',
-text:cap[1].charAt(cap[1].length-1)==='\n'?
-cap[1].slice(0,-1):
-cap[1]});
-
-continue;
-}
-
-
-if(cap=this.rules.text.exec(src)){
-
-src=src.substring(cap[0].length);
-this.tokens.push({
-type:'text',
-text:cap[0]});
-
-continue;
-}
-
-if(src){
-throw new
-Error('Infinite loop on byte: '+src.charCodeAt(0));
-}
-}
-
-return this.tokens;
+/**
+ * Set a quat to the identity quaternion
+ *
+ * @param {quat} out the receiving quaternion
+ * @returns {quat} out
+ */
+quat.identity = function(out) {
+    out[0] = 0;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 1;
+    return out;
 };
 
-
-
-
-
-var inline={
-escape:/^\\([\\`*{}\[\]()#+\-.!_>])/,
-autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,
-url:noop,
-tag:/^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
-link:/^!?\[(inside)\]\(href\)/,
-reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,
-nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
-strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
-em:/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
-code:/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,
-br:/^ {2,}\n(?!\s*$)/,
-del:noop,
-text:/^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/};
-
-
-inline._inside=/(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/;
-inline._href=/\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;
-
-inline.link=replace(inline.link)(
-'inside',inline._inside)(
-'href',inline._href)();
-
-
-inline.reflink=replace(inline.reflink)(
-'inside',inline._inside)();
-
-
-
-
-
-
-inline.normal=merge({},inline);
-
-
-
-
-
-inline.pedantic=merge({},inline.normal,{
-strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
-em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/});
-
-
-
-
-
-
-inline.gfm=merge({},inline.normal,{
-escape:replace(inline.escape)('])','~|])')(),
-url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
-del:/^~~(?=\S)([\s\S]*?\S)~~/,
-text:replace(inline.text)(
-']|','~]|')(
-'|','|https?://|')()});
-
-
-
-
-
-
-
-inline.breaks=merge({},inline.gfm,{
-br:replace(inline.br)('{2,}','*')(),
-text:replace(inline.gfm.text)('{2,}','*')()});
-
-
-
-
-
-
-function InlineLexer(links,options){
-this.options=options||marked.defaults;
-this.links=links;
-this.rules=inline.normal;
-this.renderer=this.options.renderer||new Renderer();
-this.renderer.options=this.options;
-
-if(!this.links){
-throw new
-Error('Tokens array requires a `links` property.');
-}
-
-if(this.options.gfm){
-if(this.options.breaks){
-this.rules=inline.breaks;
-}else{
-this.rules=inline.gfm;
-}
-}else if(this.options.pedantic){
-this.rules=inline.pedantic;
-}
-}
-
-
-
-
-
-InlineLexer.rules=inline;
-
-
-
-
-
-InlineLexer.output=function(src,links,options){
-var inline=new InlineLexer(links,options);
-return inline.output(src);
+/**
+ * Sets a quat from the given angle and rotation axis,
+ * then returns it.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {vec3} axis the axis around which to rotate
+ * @param {Number} rad the angle in radians
+ * @returns {quat} out
+ **/
+quat.setAxisAngle = function(out, axis, rad) {
+    rad = rad * 0.5;
+    var s = Math.sin(rad);
+    out[0] = s * axis[0];
+    out[1] = s * axis[1];
+    out[2] = s * axis[2];
+    out[3] = Math.cos(rad);
+    return out;
 };
 
-
-
-
-
-InlineLexer.prototype.output=function(src){
-var out='',
-link,
-text,
-href,
-cap;
-
-while(src){
-
-if(cap=this.rules.escape.exec(src)){
-src=src.substring(cap[0].length);
-out+=cap[1];
-continue;
-}
-
-
-if(cap=this.rules.autolink.exec(src)){
-src=src.substring(cap[0].length);
-if(cap[2]==='@'){
-text=cap[1].charAt(6)===':'?
-this.mangle(cap[1].substring(7)):
-this.mangle(cap[1]);
-href=this.mangle('mailto:')+text;
-}else{
-text=escape(cap[1]);
-href=text;
-}
-out+=this.renderer.link(href,null,text);
-continue;
-}
-
-
-if(!this.inLink&&(cap=this.rules.url.exec(src))){
-src=src.substring(cap[0].length);
-text=escape(cap[1]);
-href=text;
-out+=this.renderer.link(href,null,text);
-continue;
-}
-
-
-if(cap=this.rules.tag.exec(src)){
-if(!this.inLink&&/^<a /i.test(cap[0])){
-this.inLink=true;
-}else if(this.inLink&&/^<\/a>/i.test(cap[0])){
-this.inLink=false;
-}
-src=src.substring(cap[0].length);
-out+=this.options.sanitize?
-this.options.sanitizer?
-this.options.sanitizer(cap[0]):
-escape(cap[0]):
-cap[0];
-continue;
-}
-
-
-if(cap=this.rules.link.exec(src)){
-src=src.substring(cap[0].length);
-this.inLink=true;
-out+=this.outputLink(cap,{
-href:cap[2],
-title:cap[3]});
-
-this.inLink=false;
-continue;
-}
-
-
-if((cap=this.rules.reflink.exec(src))||(
-cap=this.rules.nolink.exec(src))){
-src=src.substring(cap[0].length);
-link=(cap[2]||cap[1]).replace(/\s+/g,' ');
-link=this.links[link.toLowerCase()];
-if(!link||!link.href){
-out+=cap[0].charAt(0);
-src=cap[0].substring(1)+src;
-continue;
-}
-this.inLink=true;
-out+=this.outputLink(cap,link);
-this.inLink=false;
-continue;
-}
-
-
-if(cap=this.rules.strong.exec(src)){
-src=src.substring(cap[0].length);
-out+=this.renderer.strong(this.output(cap[2]||cap[1]));
-continue;
-}
-
-
-if(cap=this.rules.em.exec(src)){
-src=src.substring(cap[0].length);
-out+=this.renderer.em(this.output(cap[2]||cap[1]));
-continue;
-}
-
-
-if(cap=this.rules.code.exec(src)){
-src=src.substring(cap[0].length);
-out+=this.renderer.codespan(escape(cap[2],true));
-continue;
-}
-
-
-if(cap=this.rules.br.exec(src)){
-src=src.substring(cap[0].length);
-out+=this.renderer.br();
-continue;
-}
-
-
-if(cap=this.rules.del.exec(src)){
-src=src.substring(cap[0].length);
-out+=this.renderer.del(this.output(cap[1]));
-continue;
-}
-
-
-if(cap=this.rules.text.exec(src)){
-src=src.substring(cap[0].length);
-out+=this.renderer.text(escape(this.smartypants(cap[0])));
-continue;
-}
-
-if(src){
-throw new
-Error('Infinite loop on byte: '+src.charCodeAt(0));
-}
-}
-
-return out;
+/**
+ * Gets the rotation axis and angle for a given
+ *  quaternion. If a quaternion is created with
+ *  setAxisAngle, this method will return the same
+ *  values as providied in the original parameter list
+ *  OR functionally equivalent values.
+ * Example: The quaternion formed by axis [0, 0, 1] and
+ *  angle -90 is the same as the quaternion formed by
+ *  [0, 0, 1] and 270. This method favors the latter.
+ * @param  {vec3} out_axis  Vector receiving the axis of rotation
+ * @param  {quat} q     Quaternion to be decomposed
+ * @return {Number}     Angle, in radians, of the rotation
+ */
+quat.getAxisAngle = function(out_axis, q) {
+    var rad = Math.acos(q[3]) * 2.0;
+    var s = Math.sin(rad / 2.0);
+    if (s != 0.0) {
+        out_axis[0] = q[0] / s;
+        out_axis[1] = q[1] / s;
+        out_axis[2] = q[2] / s;
+    } else {
+        // If s is zero, return any axis (no rotation - axis does not matter)
+        out_axis[0] = 1;
+        out_axis[1] = 0;
+        out_axis[2] = 0;
+    }
+    return rad;
 };
 
+/**
+ * Adds two quat's
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {quat} a the first operand
+ * @param {quat} b the second operand
+ * @returns {quat} out
+ * @function
+ */
+quat.add = vec4.add;
 
+/**
+ * Multiplies two quat's
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {quat} a the first operand
+ * @param {quat} b the second operand
+ * @returns {quat} out
+ */
+quat.multiply = function(out, a, b) {
+    var ax = a[0], ay = a[1], az = a[2], aw = a[3],
+        bx = b[0], by = b[1], bz = b[2], bw = b[3];
 
-
-
-InlineLexer.prototype.outputLink=function(cap,link){
-var href=escape(link.href),
-title=link.title?escape(link.title):null;
-
-return cap[0].charAt(0)!=='!'?
-this.renderer.link(href,title,this.output(cap[1])):
-this.renderer.image(href,title,escape(cap[1]));
+    out[0] = ax * bw + aw * bx + ay * bz - az * by;
+    out[1] = ay * bw + aw * by + az * bx - ax * bz;
+    out[2] = az * bw + aw * bz + ax * by - ay * bx;
+    out[3] = aw * bw - ax * bx - ay * by - az * bz;
+    return out;
 };
 
+/**
+ * Alias for {@link quat.multiply}
+ * @function
+ */
+quat.mul = quat.multiply;
 
+/**
+ * Scales a quat by a scalar number
+ *
+ * @param {quat} out the receiving vector
+ * @param {quat} a the vector to scale
+ * @param {Number} b amount to scale the vector by
+ * @returns {quat} out
+ * @function
+ */
+quat.scale = vec4.scale;
 
+/**
+ * Rotates a quaternion by the given angle about the X axis
+ *
+ * @param {quat} out quat receiving operation result
+ * @param {quat} a quat to rotate
+ * @param {number} rad angle (in radians) to rotate
+ * @returns {quat} out
+ */
+quat.rotateX = function (out, a, rad) {
+    rad *= 0.5; 
 
+    var ax = a[0], ay = a[1], az = a[2], aw = a[3],
+        bx = Math.sin(rad), bw = Math.cos(rad);
 
-InlineLexer.prototype.smartypants=function(text){
-if(!this.options.smartypants)return text;
-return text.
-
-replace(/---/g,'\u2014').
-
-replace(/--/g,'\u2013').
-
-replace(/(^|[-\u2014/(\[{"\s])'/g,'$1\u2018').
-
-replace(/'/g,'\u2019').
-
-replace(/(^|[-\u2014/(\[{\u2018\s])"/g,'$1\u201c').
-
-replace(/"/g,'\u201d').
-
-replace(/\.{3}/g,'\u2026');
+    out[0] = ax * bw + aw * bx;
+    out[1] = ay * bw + az * bx;
+    out[2] = az * bw - ay * bx;
+    out[3] = aw * bw - ax * bx;
+    return out;
 };
 
+/**
+ * Rotates a quaternion by the given angle about the Y axis
+ *
+ * @param {quat} out quat receiving operation result
+ * @param {quat} a quat to rotate
+ * @param {number} rad angle (in radians) to rotate
+ * @returns {quat} out
+ */
+quat.rotateY = function (out, a, rad) {
+    rad *= 0.5; 
 
+    var ax = a[0], ay = a[1], az = a[2], aw = a[3],
+        by = Math.sin(rad), bw = Math.cos(rad);
 
-
-
-InlineLexer.prototype.mangle=function(text){
-if(!this.options.mangle)return text;
-var out='',
-l=text.length,
-i=0,
-ch;
-
-for(;i<l;i++){
-ch=text.charCodeAt(i);
-if(Math.random()>0.5){
-ch='x'+ch.toString(16);
-}
-out+='&#'+ch+';';
-}
-
-return out;
+    out[0] = ax * bw - az * by;
+    out[1] = ay * bw + aw * by;
+    out[2] = az * bw + ax * by;
+    out[3] = aw * bw - ay * by;
+    return out;
 };
 
+/**
+ * Rotates a quaternion by the given angle about the Z axis
+ *
+ * @param {quat} out quat receiving operation result
+ * @param {quat} a quat to rotate
+ * @param {number} rad angle (in radians) to rotate
+ * @returns {quat} out
+ */
+quat.rotateZ = function (out, a, rad) {
+    rad *= 0.5; 
 
+    var ax = a[0], ay = a[1], az = a[2], aw = a[3],
+        bz = Math.sin(rad), bw = Math.cos(rad);
 
-
-
-function Renderer(options){
-this.options=options||{};
-}
-
-Renderer.prototype.code=function(code,lang,escaped){
-if(this.options.highlight){
-var out=this.options.highlight(code,lang);
-if(out!=null&&out!==code){
-escaped=true;
-code=out;
-}
-}
-
-if(!lang){
-return'<pre><code>'+(
-escaped?code:escape(code,true))+
-'\n</code></pre>';
-}
-
-return'<pre><code class="'+
-this.options.langPrefix+
-escape(lang,true)+
-'">'+(
-escaped?code:escape(code,true))+
-'\n</code></pre>\n';
+    out[0] = ax * bw + ay * bz;
+    out[1] = ay * bw - ax * bz;
+    out[2] = az * bw + aw * bz;
+    out[3] = aw * bw - az * bz;
+    return out;
 };
 
-Renderer.prototype.blockquote=function(quote){
-return'<blockquote>\n'+quote+'</blockquote>\n';
+/**
+ * Calculates the W component of a quat from the X, Y, and Z components.
+ * Assumes that quaternion is 1 unit in length.
+ * Any existing W component will be ignored.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {quat} a quat to calculate W component of
+ * @returns {quat} out
+ */
+quat.calculateW = function (out, a) {
+    var x = a[0], y = a[1], z = a[2];
+
+    out[0] = x;
+    out[1] = y;
+    out[2] = z;
+    out[3] = Math.sqrt(Math.abs(1.0 - x * x - y * y - z * z));
+    return out;
 };
 
-Renderer.prototype.html=function(html){
-return html;
+/**
+ * Calculates the dot product of two quat's
+ *
+ * @param {quat} a the first operand
+ * @param {quat} b the second operand
+ * @returns {Number} dot product of a and b
+ * @function
+ */
+quat.dot = vec4.dot;
+
+/**
+ * Performs a linear interpolation between two quat's
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {quat} a the first operand
+ * @param {quat} b the second operand
+ * @param {Number} t interpolation amount between the two inputs
+ * @returns {quat} out
+ * @function
+ */
+quat.lerp = vec4.lerp;
+
+/**
+ * Performs a spherical linear interpolation between two quat
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {quat} a the first operand
+ * @param {quat} b the second operand
+ * @param {Number} t interpolation amount between the two inputs
+ * @returns {quat} out
+ */
+quat.slerp = function (out, a, b, t) {
+    // benchmarks:
+    //    http://jsperf.com/quaternion-slerp-implementations
+
+    var ax = a[0], ay = a[1], az = a[2], aw = a[3],
+        bx = b[0], by = b[1], bz = b[2], bw = b[3];
+
+    var        omega, cosom, sinom, scale0, scale1;
+
+    // calc cosine
+    cosom = ax * bx + ay * by + az * bz + aw * bw;
+    // adjust signs (if necessary)
+    if ( cosom < 0.0 ) {
+        cosom = -cosom;
+        bx = - bx;
+        by = - by;
+        bz = - bz;
+        bw = - bw;
+    }
+    // calculate coefficients
+    if ( (1.0 - cosom) > 0.000001 ) {
+        // standard case (slerp)
+        omega  = Math.acos(cosom);
+        sinom  = Math.sin(omega);
+        scale0 = Math.sin((1.0 - t) * omega) / sinom;
+        scale1 = Math.sin(t * omega) / sinom;
+    } else {        
+        // "from" and "to" quaternions are very close 
+        //  ... so we can do a linear interpolation
+        scale0 = 1.0 - t;
+        scale1 = t;
+    }
+    // calculate final values
+    out[0] = scale0 * ax + scale1 * bx;
+    out[1] = scale0 * ay + scale1 * by;
+    out[2] = scale0 * az + scale1 * bz;
+    out[3] = scale0 * aw + scale1 * bw;
+    
+    return out;
 };
 
-Renderer.prototype.heading=function(text,level,raw){
-return'<h'+
-level+
-' id="'+
-this.options.headerPrefix+
-raw.toLowerCase().replace(/[^\w]+/g,'-')+
-'">'+
-text+
-'</h'+
-level+
-'>\n';
-};
-
-Renderer.prototype.hr=function(){
-return this.options.xhtml?'<hr/>\n':'<hr>\n';
-};
-
-Renderer.prototype.list=function(body,ordered){
-var type=ordered?'ol':'ul';
-return'<'+type+'>\n'+body+'</'+type+'>\n';
-};
-
-Renderer.prototype.listitem=function(text){
-return'<li>'+text+'</li>\n';
-};
-
-Renderer.prototype.paragraph=function(text){
-return'<p>'+text+'</p>\n';
-};
-
-Renderer.prototype.table=function(header,body){
-return'<table>\n'+
-'<thead>\n'+
-header+
-'</thead>\n'+
-'<tbody>\n'+
-body+
-'</tbody>\n'+
-'</table>\n';
-};
-
-Renderer.prototype.tablerow=function(content){
-return'<tr>\n'+content+'</tr>\n';
-};
-
-Renderer.prototype.tablecell=function(content,flags){
-var type=flags.header?'th':'td';
-var tag=flags.align?
-'<'+type+' style="text-align:'+flags.align+'">':
-'<'+type+'>';
-return tag+content+'</'+type+'>\n';
-};
-
-
-Renderer.prototype.strong=function(text){
-return'<strong>'+text+'</strong>';
-};
-
-Renderer.prototype.em=function(text){
-return'<em>'+text+'</em>';
-};
-
-Renderer.prototype.codespan=function(text){
-return'<code>'+text+'</code>';
-};
-
-Renderer.prototype.br=function(){
-return this.options.xhtml?'<br/>':'<br>';
-};
-
-Renderer.prototype.del=function(text){
-return'<del>'+text+'</del>';
-};
-
-Renderer.prototype.link=function(href,title,text){
-if(this.options.sanitize){
-try{
-var prot=decodeURIComponent(unescape(href)).
-replace(/[^\w:]/g,'').
-toLowerCase();
-}catch(e){
-return'';
-}
-if(prot.indexOf('javascript:')===0||prot.indexOf('vbscript:')===0){
-return'';
-}
-}
-var out='<a href="'+href+'"';
-if(title){
-out+=' title="'+title+'"';
-}
-out+='>'+text+'</a>';
-return out;
-};
-
-Renderer.prototype.image=function(href,title,text){
-var out='<img src="'+href+'" alt="'+text+'"';
-if(title){
-out+=' title="'+title+'"';
-}
-out+=this.options.xhtml?'/>':'>';
-return out;
-};
-
-Renderer.prototype.text=function(text){
-return text;
-};
-
-
-
-
-
-function Parser(options){
-this.tokens=[];
-this.token=null;
-this.options=options||marked.defaults;
-this.options.renderer=this.options.renderer||new Renderer();
-this.renderer=this.options.renderer;
-this.renderer.options=this.options;
-}
-
-
-
-
-
-Parser.parse=function(src,options,renderer){
-var parser=new Parser(options,renderer);
-return parser.parse(src);
-};
-
-
-
-
-
-Parser.prototype.parse=function(src){
-this.inline=new InlineLexer(src.links,this.options,this.renderer);
-this.tokens=src.reverse();
-
-var out='';
-while(this.next()){
-out+=this.tok();
-}
-
-return out;
-};
-
-
-
-
-
-Parser.prototype.next=function(){
-return this.token=this.tokens.pop();
-};
-
-
-
-
-
-Parser.prototype.peek=function(){
-return this.tokens[this.tokens.length-1]||0;
-};
-
-
-
-
-
-Parser.prototype.parseText=function(){
-var body=this.token.text;
-
-while(this.peek().type==='text'){
-body+='\n'+this.next().text;
-}
-
-return this.inline.output(body);
-};
-
-
-
-
-
-Parser.prototype.tok=function(){
-switch(this.token.type){
-case'space':{
-return'';
-}
-case'hr':{
-return this.renderer.hr();
-}
-case'heading':{
-return this.renderer.heading(
-this.inline.output(this.token.text),
-this.token.depth,
-this.token.text);
-}
-case'code':{
-return this.renderer.code(this.token.text,
-this.token.lang,
-this.token.escaped);
-}
-case'table':{
-var header='',
-body='',
-i,
-row,
-cell,
-flags,
-j;
-
-
-cell='';
-for(i=0;i<this.token.header.length;i++){
-flags={header:true,align:this.token.align[i]};
-cell+=this.renderer.tablecell(
-this.inline.output(this.token.header[i]),
-{header:true,align:this.token.align[i]});
-
-}
-header+=this.renderer.tablerow(cell);
-
-for(i=0;i<this.token.cells.length;i++){
-row=this.token.cells[i];
-
-cell='';
-for(j=0;j<row.length;j++){
-cell+=this.renderer.tablecell(
-this.inline.output(row[j]),
-{header:false,align:this.token.align[j]});
-
-}
-
-body+=this.renderer.tablerow(cell);
-}
-return this.renderer.table(header,body);
-}
-case'blockquote_start':{
-var body='';
-
-while(this.next().type!=='blockquote_end'){
-body+=this.tok();
-}
-
-return this.renderer.blockquote(body);
-}
-case'list_start':{
-var body='',
-ordered=this.token.ordered;
-
-while(this.next().type!=='list_end'){
-body+=this.tok();
-}
-
-return this.renderer.list(body,ordered);
-}
-case'list_item_start':{
-var body='';
-
-while(this.next().type!=='list_item_end'){
-body+=this.token.type==='text'?
-this.parseText():
-this.tok();
-}
-
-return this.renderer.listitem(body);
-}
-case'loose_item_start':{
-var body='';
-
-while(this.next().type!=='list_item_end'){
-body+=this.tok();
-}
-
-return this.renderer.listitem(body);
-}
-case'html':{
-var html=!this.token.pre&&!this.options.pedantic?
-this.inline.output(this.token.text):
-this.token.text;
-return this.renderer.html(html);
-}
-case'paragraph':{
-return this.renderer.paragraph(this.inline.output(this.token.text));
-}
-case'text':{
-return this.renderer.paragraph(this.parseText());
-}}
-
-};
-
-
-
-
-
-function escape(html,encode){
-return html.
-replace(!encode?/&(?!#?\w+;)/g:/&/g,'&amp;').
-replace(/</g,'&lt;').
-replace(/>/g,'&gt;').
-replace(/"/g,'&quot;').
-replace(/'/g,'&#39;');
-}
-
-function unescape(html){
-
-return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/g,function(_,n){
-n=n.toLowerCase();
-if(n==='colon')return':';
-if(n.charAt(0)==='#'){
-return n.charAt(1)==='x'?
-String.fromCharCode(parseInt(n.substring(2),16)):
-String.fromCharCode(+n.substring(1));
-}
-return'';
-});
-}
-
-function replace(regex,opt){
-regex=regex.source;
-opt=opt||'';
-return function self(name,val){
-if(!name)return new RegExp(regex,opt);
-val=val.source||val;
-val=val.replace(/(^|[^\[])\^/g,'$1');
-regex=regex.replace(name,val);
-return self;
-};
-}
-
-function noop(){}
-noop.exec=noop;
-
-function merge(obj){
-var i=1,
-target,
-key;
-
-for(;i<arguments.length;i++){
-target=arguments[i];
-for(key in target){
-if(Object.prototype.hasOwnProperty.call(target,key)){
-obj[key]=target[key];
-}
-}
-}
-
-return obj;
-}
-
-
-
-
-
-
-function marked(src,opt,callback){
-if(callback||typeof opt==='function'){
-if(!callback){
-callback=opt;
-opt=null;
-}
-
-opt=merge({},marked.defaults,opt||{});
-
-var highlight=opt.highlight,
-tokens,
-pending,
-i=0;
-
-try{
-tokens=Lexer.lex(src,opt);
-}catch(e){
-return callback(e);
-}
-
-pending=tokens.length;
-
-var done=function(err){
-if(err){
-opt.highlight=highlight;
-return callback(err);
-}
-
-var out;
-
-try{
-out=Parser.parse(tokens,opt);
-}catch(e){
-err=e;
-}
-
-opt.highlight=highlight;
-
-return err?
-callback(err):
-callback(null,out);
-};
-
-if(!highlight||highlight.length<3){
-return done();
-}
-
-delete opt.highlight;
-
-if(!pending)return done();
-
-for(;i<tokens.length;i++){
-(function(token){
-if(token.type!=='code'){
-return--pending||done();
-}
-return highlight(token.text,token.lang,function(err,code){
-if(err)return done(err);
-if(code==null||code===token.text){
-return--pending||done();
-}
-token.text=code;
-token.escaped=true;
---pending||done();
-});
-})(tokens[i]);
-}
-
-return;
-}
-try{
-if(opt)opt=merge({},marked.defaults,opt);
-return Parser.parse(Lexer.lex(src,opt),opt);
-}catch(e){
-e.message+='\nPlease report this to https://github.com/chjj/marked.';
-if((opt||marked.defaults).silent){
-return'<p>An error occured:</p><pre>'+
-escape(e.message+'',true)+
-'</pre>';
-}
-throw e;
-}
-}
-
-
-
-
-
-marked.options=
-marked.setOptions=function(opt){
-merge(marked.defaults,opt);
-return marked;
-};
-
-marked.defaults={
-gfm:true,
-tables:true,
-breaks:false,
-pedantic:false,
-sanitize:false,
-sanitizer:null,
-mangle:true,
-smartLists:false,
-silent:false,
-highlight:null,
-langPrefix:'lang-',
-smartypants:false,
-headerPrefix:'',
-renderer:new Renderer(),
-xhtml:false};
-
-
-
-
-
-
-marked.Parser=Parser;
-marked.parser=Parser.parse;
-
-marked.Renderer=Renderer;
-
-marked.Lexer=Lexer;
-marked.lexer=Lexer.lex;
-
-marked.InlineLexer=InlineLexer;
-marked.inlineLexer=InlineLexer.output;
-
-marked.parse=marked;
-
-if(typeof module!=='undefined'&&typeof exports==='object'){
-module.exports=marked;
-}else if(typeof define==='function'&&define.amd){
-define(function(){return marked;});
-}else{
-this.marked=marked;
-}
-
-}).call(function(){
-return this||(typeof window!=='undefined'?window:global);
+/**
+ * Performs a spherical linear interpolation with two control points
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {quat} a the first operand
+ * @param {quat} b the second operand
+ * @param {quat} c the third operand
+ * @param {quat} d the fourth operand
+ * @param {Number} t interpolation amount
+ * @returns {quat} out
+ */
+quat.sqlerp = (function () {
+  var temp1 = quat.create();
+  var temp2 = quat.create();
+  
+  return function (out, a, b, c, d, t) {
+    quat.slerp(temp1, a, d, t);
+    quat.slerp(temp2, b, c, t);
+    quat.slerp(out, temp1, temp2, 2 * t * (1 - t));
+    
+    return out;
+  };
 }());
 
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{}],270:[function(require,module,exports){
-exports.getRenderingDataFromViewport=function(viewportProperties,uaDeviceWidth,uaDeviceHeight,uaMaxZoom,uaMinZoom){
+/**
+ * Calculates the inverse of a quat
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {quat} a quat to calculate inverse of
+ * @returns {quat} out
+ */
+quat.invert = function(out, a) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3],
+        dot = a0*a0 + a1*a1 + a2*a2 + a3*a3,
+        invDot = dot ? 1.0/dot : 0;
+    
+    // TODO: Would be faster to return [0,0,0,0] immediately if dot == 0
 
-var vw=uaDeviceWidth/100;
-var vh=uaDeviceHeight/100;
-
-
-
-var maxZoom=null;
-var minZoom=null;
-var zoom=null;
-var minWidth=null;
-var minHeight=null;
-var maxWidth=null;
-var maxHeight=null;
-var width=null,height=null;
-var initialWidth=uaDeviceWidth;
-var initialHeight=uaDeviceHeight;
-var userZoom="zoom";
-if(viewportProperties["maximum-scale"]!==undefined){
-maxZoom=translateZoomProperty(viewportProperties["maximum-scale"]);
-}
-if(viewportProperties["minimum-scale"]!==undefined){
-minZoom=translateZoomProperty(viewportProperties["minimum-scale"]);
-}
-if(viewportProperties["initial-scale"]!==undefined){
-zoom=translateZoomProperty(viewportProperties["initial-scale"]);
-}
-
-
-
-
-
-
-
-if(minZoom!==null&&maxZoom===null){
-minZoom=min(uaMaxZoom,translateZoomProperty(viewportProperties["minimum-scale"]));
-}
-
-if(viewportProperties["width"]!==undefined){
-minWidth="extend-to-zoom";
-maxWidth=translateLengthProperty(viewportProperties["width"],vw,vh);
-}
-
-if(viewportProperties["height"]!==undefined){
-minHeight="extend-to-zoom";
-maxHeight=translateLengthProperty(viewportProperties["height"],vw,vh);
-}
-
-
-if(viewportProperties["user-scalable"]!==undefined){
-userZoom=viewportProperties["user-scalable"];
-if(typeof userZoom==="number"){
-if(userZoom>=1||userZoom<=-1){
-userZoom="zoom";
-}else{
-userZoom="fixed";
-}
-}else{
-switch(userZoom){
-case"yes":
-case"device-width":
-case"device-height":
-userZoom="zoom";
-break;
-case"no":
-default:
-userZoom="fixed";
-break;}
-
-}
-}
-
-
-
-if(zoom!==null&&(
-viewportProperties["width"]===undefined||width===undefined)){
-if(viewportProperties["height"]!==undefined){
-
-minWidth=null;
-maxWidth=null;
-}else{
-
-minWidth="extend-to-zoom";
-maxWidth="extend-to-zoom";
-}
-}
-
-
-
-
-
-
-if(minZoom!==null&&maxZoom!==null){
-maxZoom=max(minZoom,maxZoom);
-}
-
-
-if(zoom!==null){
-zoom=clamp(zoom,minZoom,maxZoom);
-}
-
-
-var extendZoom=zoom===null&&maxZoom===null?null:min(zoom,maxZoom);
-var extendWidth,extendHeight;
-if(extendZoom===null){
-if(maxWidth==="extend-to-zoom"){
-maxWidth=null;
-}
-if(maxHeight==="extend-to-zoom"){
-maxHeight=null;
-}
-if(minWidth==="extend-to-zoom"){
-minWidth=maxWidth;
-}
-if(minHeight==="extend-to-zoom"){
-minHeight=maxHeight;
-}
-}else{
-extendWidth=initialWidth/extendZoom;
-extendHeight=initialHeight/extendZoom;
-
-if(maxWidth==="extend-to-zoom"){
-maxWidth=extendWidth;
-}
-if(maxHeight==="extend-to-zoom"){
-maxHeight=extendHeight;
-}
-if(minWidth==="extend-to-zoom"){
-minWidth=max(extendWidth,maxWidth);
-}
-if(minHeight==="extend-to-zoom"){
-minHeight=max(extendHeight,maxHeight);
-}
-}
-
-
-if(minWidth!==null||maxWidth!==null){
-width=max(minWidth,min(maxWidth,initialWidth));
-}
-if(minHeight!==null||maxHeight!==null){
-height=max(minHeight,min(maxHeight,initialHeight));
-}
-
-
-if(width===null){
-if(height===null){
-width=initialWidth;
-}else{
-if(initialHeight!==0){
-width=Math.round(height*(initialWidth/initialHeight));
-}else{
-width=initialWidth;
-}
-}
-}
-if(height===null){
-if(initialWidth!==0){
-height=Math.round(width*(initialHeight/initialWidth));
-}else{
-height=initialHeight;
-}
-}
-
-return{zoom:zoom,width:width,height:height,userZoom:userZoom};
+    out[0] = -a0*invDot;
+    out[1] = -a1*invDot;
+    out[2] = -a2*invDot;
+    out[3] = a3*invDot;
+    return out;
 };
 
-function min(a,b){
-if(a===null)return b;
-if(b===null)return a;
-return Math.min(a,b);
-}
-
-function max(a,b){
-if(a===null)return b;
-if(b===null)return a;
-return Math.max(a,b);
-}
-
-
-function translateLengthProperty(prop,vw,vh){
-
-if(typeof prop==="number"){
-if(prop>=0){
-
-return clamp(prop,1,10000);
-}else{
-return undefined;
-}
-}
-if(prop==="device-width"){
-return 100*vw;
-}
-if(prop==="device-height"){
-return 100*vh;
-}
-return 1;
-}
-
-function translateZoomProperty(prop){
-
-if(typeof prop==="number"){
-if(prop>=0){
-
-return clamp(prop,0.1,10);
-}else{
-return undefined;
-}
-}
-if(prop==="yes"){
-return 1;
-}
-if(prop==="device-width"||prop==="device-height"){
-return 10;
-}
-if(prop==="no"||prop===null){
-return 0.1;
-}
-}
-
-
-function clamp(value,minv,maxv){
-return max(min(value,maxv),minv);
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-exports.parseMetaViewPortContent=function(S){
-var parsedContent={
-validProperties:{},
-unknownProperties:{},
-invalidValues:{}};
-
-var i=1;
-while(i<=S.length){
-while(i<=S.length&&RegExp(' |\x0A|\x09|\0d|,|;|=').test(S[i-1])){
-i++;
-}
-if(i<=S.length){
-i=parseProperty(parsedContent,S,i);
-}
-}
-return parsedContent;
+/**
+ * Calculates the conjugate of a quat
+ * If the quaternion is normalized, this function is faster than quat.inverse and produces the same result.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {quat} a quat to calculate conjugate of
+ * @returns {quat} out
+ */
+quat.conjugate = function (out, a) {
+    out[0] = -a[0];
+    out[1] = -a[1];
+    out[2] = -a[2];
+    out[3] = a[3];
+    return out;
 };
 
-var propertyNames=["width","height","initial-scale","minimum-scale","maximum-scale","user-scalable"];
+/**
+ * Calculates the length of a quat
+ *
+ * @param {quat} a vector to calculate length of
+ * @returns {Number} length of a
+ * @function
+ */
+quat.length = vec4.length;
 
-function parseProperty(parsedContent,S,i){
-var start=i;
-while(i<=S.length&&!RegExp(' |\x0A|\x09|\0d|,|;|=').test(S[i-1])){
-i++;
-}
-if(i>S.length||RegExp(',|;').test(S[i-1])){
-return i;
-}
-var propertyName=S.slice(start-1,i-1);
-while(i<=S.length&&!RegExp(',|;|=').test(S[i-1])){
-i++;
-}
-if(i>S.length||RegExp(',|;').test(S[i-1])){
-return i;
-}
-while(i<=S.length&&RegExp(' |\x0A|\x09|\0d|=').test(S[i-1])){
-i++;
-}
-if(i>S.length||RegExp(',|;').test(S[i-1])){
-return i;
-}
-start=i;
-while(i<=S.length&&!RegExp(' |\x0A|\x09|\0d|,|;|=').test(S[i-1])){
-i++;
-}
-var propertyValue=S.slice(start-1,i-1);
-setProperty(parsedContent,propertyName,propertyValue);
-return i;
-}
+/**
+ * Alias for {@link quat.length}
+ * @function
+ */
+quat.len = quat.length;
 
-function setProperty(parsedContent,name,value){
-if(propertyNames.indexOf(name)>=0){
-var number=parseFloat(value);
-if(!isNaN(number)){
-parsedContent.validProperties[name]=number;
-return;
-}
-var string=value.toLowerCase();
-if(string==="yes"||string==="no"||string==="device-width"||string==="device-height"){
-parsedContent.validProperties[name]=string;
-return;
-}
-parsedContent.validProperties[name]=null;
-parsedContent.invalidValues[name]=value;
-}else{
-parsedContent.unknownProperties[name]=value;
-}
-}
+/**
+ * Calculates the squared length of a quat
+ *
+ * @param {quat} a vector to calculate squared length of
+ * @returns {Number} squared length of a
+ * @function
+ */
+quat.squaredLength = vec4.squaredLength;
 
-exports.expectedValues={
-"width":["device-width","device-height","a positive number"],
-"height":["device-width","device-height","a positive number"],
-"initial-scale":["a positive number"],
-"minimum-scale":["a positive number"],
-"maximum-scale":["a positive number"],
-"user-scalable":["yes","no","0","1"]};
+/**
+ * Alias for {@link quat.squaredLength}
+ * @function
+ */
+quat.sqrLen = quat.squaredLength;
 
-},{}],271:[function(require,module,exports){
+/**
+ * Normalize a quat
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {quat} a quaternion to normalize
+ * @returns {quat} out
+ * @function
+ */
+quat.normalize = vec4.normalize;
 
+/**
+ * Creates a quaternion from the given 3x3 rotation matrix.
+ *
+ * NOTE: The resultant quaternion is not normalized, so you should be sure
+ * to renormalize the quaternion yourself where necessary.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {mat3} m rotation matrix
+ * @returns {quat} out
+ * @function
+ */
+quat.fromMat3 = function(out, m) {
+    // Algorithm in Ken Shoemake's article in 1987 SIGGRAPH course notes
+    // article "Quaternion Calculus and Fast Animation".
+    var fTrace = m[0] + m[4] + m[8];
+    var fRoot;
 
-
-
-var s=1000;
-var m=s*60;
-var h=m*60;
-var d=h*24;
-var y=d*365.25;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-module.exports=function(val,options){
-options=options||{};
-if('string'==typeof val)return parse(val);
-return options.long?
-long(val):
-short(val);
+    if ( fTrace > 0.0 ) {
+        // |w| > 1/2, may as well choose w > 1/2
+        fRoot = Math.sqrt(fTrace + 1.0);  // 2w
+        out[3] = 0.5 * fRoot;
+        fRoot = 0.5/fRoot;  // 1/(4w)
+        out[0] = (m[5]-m[7])*fRoot;
+        out[1] = (m[6]-m[2])*fRoot;
+        out[2] = (m[1]-m[3])*fRoot;
+    } else {
+        // |w| <= 1/2
+        var i = 0;
+        if ( m[4] > m[0] )
+          i = 1;
+        if ( m[8] > m[i*3+i] )
+          i = 2;
+        var j = (i+1)%3;
+        var k = (i+2)%3;
+        
+        fRoot = Math.sqrt(m[i*3+i]-m[j*3+j]-m[k*3+k] + 1.0);
+        out[i] = 0.5 * fRoot;
+        fRoot = 0.5 / fRoot;
+        out[3] = (m[j*3+k] - m[k*3+j]) * fRoot;
+        out[j] = (m[j*3+i] + m[i*3+j]) * fRoot;
+        out[k] = (m[k*3+i] + m[i*3+k]) * fRoot;
+    }
+    
+    return out;
 };
 
+/**
+ * Returns a string representation of a quatenion
+ *
+ * @param {quat} vec vector to represent as a string
+ * @returns {String} string representation of the vector
+ */
+quat.str = function (a) {
+    return 'quat(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + a[3] + ')';
+};
+
+/**
+ * Returns whether or not the quaternions have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {quat} a The first quaternion.
+ * @param {quat} b The second quaternion.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+quat.exactEquals = vec4.exactEquals;
+
+/**
+ * Returns whether or not the quaternions have approximately the same elements in the same position.
+ *
+ * @param {quat} a The first vector.
+ * @param {quat} b The second vector.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+quat.equals = vec4.equals;
+
+module.exports = quat;
+
+},{"./common.js":266,"./mat3.js":269,"./vec3.js":273,"./vec4.js":274}],272:[function(require,module,exports){
+/* Copyright (c) 2015, Brandon Jones, Colin MacKenzie IV.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE. */
+
+var glMatrix = require("./common.js");
+
+/**
+ * @class 2 Dimensional Vector
+ * @name vec2
+ */
+var vec2 = {};
+
+/**
+ * Creates a new, empty vec2
+ *
+ * @returns {vec2} a new 2D vector
+ */
+vec2.create = function() {
+    var out = new glMatrix.ARRAY_TYPE(2);
+    out[0] = 0;
+    out[1] = 0;
+    return out;
+};
+
+/**
+ * Creates a new vec2 initialized with values from an existing vector
+ *
+ * @param {vec2} a vector to clone
+ * @returns {vec2} a new 2D vector
+ */
+vec2.clone = function(a) {
+    var out = new glMatrix.ARRAY_TYPE(2);
+    out[0] = a[0];
+    out[1] = a[1];
+    return out;
+};
+
+/**
+ * Creates a new vec2 initialized with the given values
+ *
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @returns {vec2} a new 2D vector
+ */
+vec2.fromValues = function(x, y) {
+    var out = new glMatrix.ARRAY_TYPE(2);
+    out[0] = x;
+    out[1] = y;
+    return out;
+};
+
+/**
+ * Copy the values from one vec2 to another
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the source vector
+ * @returns {vec2} out
+ */
+vec2.copy = function(out, a) {
+    out[0] = a[0];
+    out[1] = a[1];
+    return out;
+};
+
+/**
+ * Set the components of a vec2 to the given values
+ *
+ * @param {vec2} out the receiving vector
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @returns {vec2} out
+ */
+vec2.set = function(out, x, y) {
+    out[0] = x;
+    out[1] = y;
+    return out;
+};
+
+/**
+ * Adds two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @returns {vec2} out
+ */
+vec2.add = function(out, a, b) {
+    out[0] = a[0] + b[0];
+    out[1] = a[1] + b[1];
+    return out;
+};
+
+/**
+ * Subtracts vector b from vector a
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @returns {vec2} out
+ */
+vec2.subtract = function(out, a, b) {
+    out[0] = a[0] - b[0];
+    out[1] = a[1] - b[1];
+    return out;
+};
+
+/**
+ * Alias for {@link vec2.subtract}
+ * @function
+ */
+vec2.sub = vec2.subtract;
+
+/**
+ * Multiplies two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @returns {vec2} out
+ */
+vec2.multiply = function(out, a, b) {
+    out[0] = a[0] * b[0];
+    out[1] = a[1] * b[1];
+    return out;
+};
+
+/**
+ * Alias for {@link vec2.multiply}
+ * @function
+ */
+vec2.mul = vec2.multiply;
+
+/**
+ * Divides two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @returns {vec2} out
+ */
+vec2.divide = function(out, a, b) {
+    out[0] = a[0] / b[0];
+    out[1] = a[1] / b[1];
+    return out;
+};
+
+/**
+ * Alias for {@link vec2.divide}
+ * @function
+ */
+vec2.div = vec2.divide;
+
+/**
+ * Math.ceil the components of a vec2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a vector to ceil
+ * @returns {vec2} out
+ */
+vec2.ceil = function (out, a) {
+    out[0] = Math.ceil(a[0]);
+    out[1] = Math.ceil(a[1]);
+    return out;
+};
+
+/**
+ * Math.floor the components of a vec2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a vector to floor
+ * @returns {vec2} out
+ */
+vec2.floor = function (out, a) {
+    out[0] = Math.floor(a[0]);
+    out[1] = Math.floor(a[1]);
+    return out;
+};
+
+/**
+ * Returns the minimum of two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @returns {vec2} out
+ */
+vec2.min = function(out, a, b) {
+    out[0] = Math.min(a[0], b[0]);
+    out[1] = Math.min(a[1], b[1]);
+    return out;
+};
+
+/**
+ * Returns the maximum of two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @returns {vec2} out
+ */
+vec2.max = function(out, a, b) {
+    out[0] = Math.max(a[0], b[0]);
+    out[1] = Math.max(a[1], b[1]);
+    return out;
+};
+
+/**
+ * Math.round the components of a vec2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a vector to round
+ * @returns {vec2} out
+ */
+vec2.round = function (out, a) {
+    out[0] = Math.round(a[0]);
+    out[1] = Math.round(a[1]);
+    return out;
+};
+
+/**
+ * Scales a vec2 by a scalar number
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the vector to scale
+ * @param {Number} b amount to scale the vector by
+ * @returns {vec2} out
+ */
+vec2.scale = function(out, a, b) {
+    out[0] = a[0] * b;
+    out[1] = a[1] * b;
+    return out;
+};
+
+/**
+ * Adds two vec2's after scaling the second operand by a scalar value
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @param {Number} scale the amount to scale b by before adding
+ * @returns {vec2} out
+ */
+vec2.scaleAndAdd = function(out, a, b, scale) {
+    out[0] = a[0] + (b[0] * scale);
+    out[1] = a[1] + (b[1] * scale);
+    return out;
+};
+
+/**
+ * Calculates the euclidian distance between two vec2's
+ *
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @returns {Number} distance between a and b
+ */
+vec2.distance = function(a, b) {
+    var x = b[0] - a[0],
+        y = b[1] - a[1];
+    return Math.sqrt(x*x + y*y);
+};
+
+/**
+ * Alias for {@link vec2.distance}
+ * @function
+ */
+vec2.dist = vec2.distance;
+
+/**
+ * Calculates the squared euclidian distance between two vec2's
+ *
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @returns {Number} squared distance between a and b
+ */
+vec2.squaredDistance = function(a, b) {
+    var x = b[0] - a[0],
+        y = b[1] - a[1];
+    return x*x + y*y;
+};
+
+/**
+ * Alias for {@link vec2.squaredDistance}
+ * @function
+ */
+vec2.sqrDist = vec2.squaredDistance;
+
+/**
+ * Calculates the length of a vec2
+ *
+ * @param {vec2} a vector to calculate length of
+ * @returns {Number} length of a
+ */
+vec2.length = function (a) {
+    var x = a[0],
+        y = a[1];
+    return Math.sqrt(x*x + y*y);
+};
+
+/**
+ * Alias for {@link vec2.length}
+ * @function
+ */
+vec2.len = vec2.length;
+
+/**
+ * Calculates the squared length of a vec2
+ *
+ * @param {vec2} a vector to calculate squared length of
+ * @returns {Number} squared length of a
+ */
+vec2.squaredLength = function (a) {
+    var x = a[0],
+        y = a[1];
+    return x*x + y*y;
+};
+
+/**
+ * Alias for {@link vec2.squaredLength}
+ * @function
+ */
+vec2.sqrLen = vec2.squaredLength;
+
+/**
+ * Negates the components of a vec2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a vector to negate
+ * @returns {vec2} out
+ */
+vec2.negate = function(out, a) {
+    out[0] = -a[0];
+    out[1] = -a[1];
+    return out;
+};
+
+/**
+ * Returns the inverse of the components of a vec2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a vector to invert
+ * @returns {vec2} out
+ */
+vec2.inverse = function(out, a) {
+  out[0] = 1.0 / a[0];
+  out[1] = 1.0 / a[1];
+  return out;
+};
+
+/**
+ * Normalize a vec2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a vector to normalize
+ * @returns {vec2} out
+ */
+vec2.normalize = function(out, a) {
+    var x = a[0],
+        y = a[1];
+    var len = x*x + y*y;
+    if (len > 0) {
+        //TODO: evaluate use of glm_invsqrt here?
+        len = 1 / Math.sqrt(len);
+        out[0] = a[0] * len;
+        out[1] = a[1] * len;
+    }
+    return out;
+};
+
+/**
+ * Calculates the dot product of two vec2's
+ *
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @returns {Number} dot product of a and b
+ */
+vec2.dot = function (a, b) {
+    return a[0] * b[0] + a[1] * b[1];
+};
+
+/**
+ * Computes the cross product of two vec2's
+ * Note that the cross product must by definition produce a 3D vector
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @returns {vec3} out
+ */
+vec2.cross = function(out, a, b) {
+    var z = a[0] * b[1] - a[1] * b[0];
+    out[0] = out[1] = 0;
+    out[2] = z;
+    return out;
+};
+
+/**
+ * Performs a linear interpolation between two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @param {Number} t interpolation amount between the two inputs
+ * @returns {vec2} out
+ */
+vec2.lerp = function (out, a, b, t) {
+    var ax = a[0],
+        ay = a[1];
+    out[0] = ax + t * (b[0] - ax);
+    out[1] = ay + t * (b[1] - ay);
+    return out;
+};
+
+/**
+ * Generates a random vector with the given scale
+ *
+ * @param {vec2} out the receiving vector
+ * @param {Number} [scale] Length of the resulting vector. If ommitted, a unit vector will be returned
+ * @returns {vec2} out
+ */
+vec2.random = function (out, scale) {
+    scale = scale || 1.0;
+    var r = glMatrix.RANDOM() * 2.0 * Math.PI;
+    out[0] = Math.cos(r) * scale;
+    out[1] = Math.sin(r) * scale;
+    return out;
+};
+
+/**
+ * Transforms the vec2 with a mat2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the vector to transform
+ * @param {mat2} m matrix to transform with
+ * @returns {vec2} out
+ */
+vec2.transformMat2 = function(out, a, m) {
+    var x = a[0],
+        y = a[1];
+    out[0] = m[0] * x + m[2] * y;
+    out[1] = m[1] * x + m[3] * y;
+    return out;
+};
+
+/**
+ * Transforms the vec2 with a mat2d
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the vector to transform
+ * @param {mat2d} m matrix to transform with
+ * @returns {vec2} out
+ */
+vec2.transformMat2d = function(out, a, m) {
+    var x = a[0],
+        y = a[1];
+    out[0] = m[0] * x + m[2] * y + m[4];
+    out[1] = m[1] * x + m[3] * y + m[5];
+    return out;
+};
+
+/**
+ * Transforms the vec2 with a mat3
+ * 3rd vector component is implicitly '1'
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the vector to transform
+ * @param {mat3} m matrix to transform with
+ * @returns {vec2} out
+ */
+vec2.transformMat3 = function(out, a, m) {
+    var x = a[0],
+        y = a[1];
+    out[0] = m[0] * x + m[3] * y + m[6];
+    out[1] = m[1] * x + m[4] * y + m[7];
+    return out;
+};
+
+/**
+ * Transforms the vec2 with a mat4
+ * 3rd vector component is implicitly '0'
+ * 4th vector component is implicitly '1'
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the vector to transform
+ * @param {mat4} m matrix to transform with
+ * @returns {vec2} out
+ */
+vec2.transformMat4 = function(out, a, m) {
+    var x = a[0], 
+        y = a[1];
+    out[0] = m[0] * x + m[4] * y + m[12];
+    out[1] = m[1] * x + m[5] * y + m[13];
+    return out;
+};
+
+/**
+ * Perform some operation over an array of vec2s.
+ *
+ * @param {Array} a the array of vectors to iterate over
+ * @param {Number} stride Number of elements between the start of each vec2. If 0 assumes tightly packed
+ * @param {Number} offset Number of elements to skip at the beginning of the array
+ * @param {Number} count Number of vec2s to iterate over. If 0 iterates over entire array
+ * @param {Function} fn Function to call for each vector in the array
+ * @param {Object} [arg] additional argument to pass to fn
+ * @returns {Array} a
+ * @function
+ */
+vec2.forEach = (function() {
+    var vec = vec2.create();
+
+    return function(a, stride, offset, count, fn, arg) {
+        var i, l;
+        if(!stride) {
+            stride = 2;
+        }
+
+        if(!offset) {
+            offset = 0;
+        }
+        
+        if(count) {
+            l = Math.min((count * stride) + offset, a.length);
+        } else {
+            l = a.length;
+        }
+
+        for(i = offset; i < l; i += stride) {
+            vec[0] = a[i]; vec[1] = a[i+1];
+            fn(vec, vec, arg);
+            a[i] = vec[0]; a[i+1] = vec[1];
+        }
+        
+        return a;
+    };
+})();
+
+/**
+ * Returns a string representation of a vector
+ *
+ * @param {vec2} vec vector to represent as a string
+ * @returns {String} string representation of the vector
+ */
+vec2.str = function (a) {
+    return 'vec2(' + a[0] + ', ' + a[1] + ')';
+};
+
+/**
+ * Returns whether or not the vectors exactly have the same elements in the same position (when compared with ===)
+ *
+ * @param {vec2} a The first vector.
+ * @param {vec2} b The second vector.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+vec2.exactEquals = function (a, b) {
+    return a[0] === b[0] && a[1] === b[1];
+};
+
+/**
+ * Returns whether or not the vectors have approximately the same elements in the same position.
+ *
+ * @param {vec2} a The first vector.
+ * @param {vec2} b The second vector.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+vec2.equals = function (a, b) {
+    var a0 = a[0], a1 = a[1];
+    var b0 = b[0], b1 = b[1];
+    return (Math.abs(a0 - b0) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a0), Math.abs(b0)) &&
+            Math.abs(a1 - b1) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a1), Math.abs(b1)));
+};
+
+module.exports = vec2;
+
+},{"./common.js":266}],273:[function(require,module,exports){
+/* Copyright (c) 2015, Brandon Jones, Colin MacKenzie IV.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE. */
+
+var glMatrix = require("./common.js");
+
+/**
+ * @class 3 Dimensional Vector
+ * @name vec3
+ */
+var vec3 = {};
+
+/**
+ * Creates a new, empty vec3
+ *
+ * @returns {vec3} a new 3D vector
+ */
+vec3.create = function() {
+    var out = new glMatrix.ARRAY_TYPE(3);
+    out[0] = 0;
+    out[1] = 0;
+    out[2] = 0;
+    return out;
+};
+
+/**
+ * Creates a new vec3 initialized with values from an existing vector
+ *
+ * @param {vec3} a vector to clone
+ * @returns {vec3} a new 3D vector
+ */
+vec3.clone = function(a) {
+    var out = new glMatrix.ARRAY_TYPE(3);
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    return out;
+};
+
+/**
+ * Creates a new vec3 initialized with the given values
+ *
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @returns {vec3} a new 3D vector
+ */
+vec3.fromValues = function(x, y, z) {
+    var out = new glMatrix.ARRAY_TYPE(3);
+    out[0] = x;
+    out[1] = y;
+    out[2] = z;
+    return out;
+};
+
+/**
+ * Copy the values from one vec3 to another
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the source vector
+ * @returns {vec3} out
+ */
+vec3.copy = function(out, a) {
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    return out;
+};
+
+/**
+ * Set the components of a vec3 to the given values
+ *
+ * @param {vec3} out the receiving vector
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @returns {vec3} out
+ */
+vec3.set = function(out, x, y, z) {
+    out[0] = x;
+    out[1] = y;
+    out[2] = z;
+    return out;
+};
+
+/**
+ * Adds two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @returns {vec3} out
+ */
+vec3.add = function(out, a, b) {
+    out[0] = a[0] + b[0];
+    out[1] = a[1] + b[1];
+    out[2] = a[2] + b[2];
+    return out;
+};
+
+/**
+ * Subtracts vector b from vector a
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @returns {vec3} out
+ */
+vec3.subtract = function(out, a, b) {
+    out[0] = a[0] - b[0];
+    out[1] = a[1] - b[1];
+    out[2] = a[2] - b[2];
+    return out;
+};
+
+/**
+ * Alias for {@link vec3.subtract}
+ * @function
+ */
+vec3.sub = vec3.subtract;
+
+/**
+ * Multiplies two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @returns {vec3} out
+ */
+vec3.multiply = function(out, a, b) {
+    out[0] = a[0] * b[0];
+    out[1] = a[1] * b[1];
+    out[2] = a[2] * b[2];
+    return out;
+};
+
+/**
+ * Alias for {@link vec3.multiply}
+ * @function
+ */
+vec3.mul = vec3.multiply;
+
+/**
+ * Divides two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @returns {vec3} out
+ */
+vec3.divide = function(out, a, b) {
+    out[0] = a[0] / b[0];
+    out[1] = a[1] / b[1];
+    out[2] = a[2] / b[2];
+    return out;
+};
+
+/**
+ * Alias for {@link vec3.divide}
+ * @function
+ */
+vec3.div = vec3.divide;
+
+/**
+ * Math.ceil the components of a vec3
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a vector to ceil
+ * @returns {vec3} out
+ */
+vec3.ceil = function (out, a) {
+    out[0] = Math.ceil(a[0]);
+    out[1] = Math.ceil(a[1]);
+    out[2] = Math.ceil(a[2]);
+    return out;
+};
+
+/**
+ * Math.floor the components of a vec3
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a vector to floor
+ * @returns {vec3} out
+ */
+vec3.floor = function (out, a) {
+    out[0] = Math.floor(a[0]);
+    out[1] = Math.floor(a[1]);
+    out[2] = Math.floor(a[2]);
+    return out;
+};
+
+/**
+ * Returns the minimum of two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @returns {vec3} out
+ */
+vec3.min = function(out, a, b) {
+    out[0] = Math.min(a[0], b[0]);
+    out[1] = Math.min(a[1], b[1]);
+    out[2] = Math.min(a[2], b[2]);
+    return out;
+};
+
+/**
+ * Returns the maximum of two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @returns {vec3} out
+ */
+vec3.max = function(out, a, b) {
+    out[0] = Math.max(a[0], b[0]);
+    out[1] = Math.max(a[1], b[1]);
+    out[2] = Math.max(a[2], b[2]);
+    return out;
+};
+
+/**
+ * Math.round the components of a vec3
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a vector to round
+ * @returns {vec3} out
+ */
+vec3.round = function (out, a) {
+    out[0] = Math.round(a[0]);
+    out[1] = Math.round(a[1]);
+    out[2] = Math.round(a[2]);
+    return out;
+};
+
+/**
+ * Scales a vec3 by a scalar number
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the vector to scale
+ * @param {Number} b amount to scale the vector by
+ * @returns {vec3} out
+ */
+vec3.scale = function(out, a, b) {
+    out[0] = a[0] * b;
+    out[1] = a[1] * b;
+    out[2] = a[2] * b;
+    return out;
+};
+
+/**
+ * Adds two vec3's after scaling the second operand by a scalar value
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @param {Number} scale the amount to scale b by before adding
+ * @returns {vec3} out
+ */
+vec3.scaleAndAdd = function(out, a, b, scale) {
+    out[0] = a[0] + (b[0] * scale);
+    out[1] = a[1] + (b[1] * scale);
+    out[2] = a[2] + (b[2] * scale);
+    return out;
+};
+
+/**
+ * Calculates the euclidian distance between two vec3's
+ *
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @returns {Number} distance between a and b
+ */
+vec3.distance = function(a, b) {
+    var x = b[0] - a[0],
+        y = b[1] - a[1],
+        z = b[2] - a[2];
+    return Math.sqrt(x*x + y*y + z*z);
+};
+
+/**
+ * Alias for {@link vec3.distance}
+ * @function
+ */
+vec3.dist = vec3.distance;
+
+/**
+ * Calculates the squared euclidian distance between two vec3's
+ *
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @returns {Number} squared distance between a and b
+ */
+vec3.squaredDistance = function(a, b) {
+    var x = b[0] - a[0],
+        y = b[1] - a[1],
+        z = b[2] - a[2];
+    return x*x + y*y + z*z;
+};
+
+/**
+ * Alias for {@link vec3.squaredDistance}
+ * @function
+ */
+vec3.sqrDist = vec3.squaredDistance;
+
+/**
+ * Calculates the length of a vec3
+ *
+ * @param {vec3} a vector to calculate length of
+ * @returns {Number} length of a
+ */
+vec3.length = function (a) {
+    var x = a[0],
+        y = a[1],
+        z = a[2];
+    return Math.sqrt(x*x + y*y + z*z);
+};
+
+/**
+ * Alias for {@link vec3.length}
+ * @function
+ */
+vec3.len = vec3.length;
+
+/**
+ * Calculates the squared length of a vec3
+ *
+ * @param {vec3} a vector to calculate squared length of
+ * @returns {Number} squared length of a
+ */
+vec3.squaredLength = function (a) {
+    var x = a[0],
+        y = a[1],
+        z = a[2];
+    return x*x + y*y + z*z;
+};
+
+/**
+ * Alias for {@link vec3.squaredLength}
+ * @function
+ */
+vec3.sqrLen = vec3.squaredLength;
+
+/**
+ * Negates the components of a vec3
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a vector to negate
+ * @returns {vec3} out
+ */
+vec3.negate = function(out, a) {
+    out[0] = -a[0];
+    out[1] = -a[1];
+    out[2] = -a[2];
+    return out;
+};
+
+/**
+ * Returns the inverse of the components of a vec3
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a vector to invert
+ * @returns {vec3} out
+ */
+vec3.inverse = function(out, a) {
+  out[0] = 1.0 / a[0];
+  out[1] = 1.0 / a[1];
+  out[2] = 1.0 / a[2];
+  return out;
+};
+
+/**
+ * Normalize a vec3
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a vector to normalize
+ * @returns {vec3} out
+ */
+vec3.normalize = function(out, a) {
+    var x = a[0],
+        y = a[1],
+        z = a[2];
+    var len = x*x + y*y + z*z;
+    if (len > 0) {
+        //TODO: evaluate use of glm_invsqrt here?
+        len = 1 / Math.sqrt(len);
+        out[0] = a[0] * len;
+        out[1] = a[1] * len;
+        out[2] = a[2] * len;
+    }
+    return out;
+};
+
+/**
+ * Calculates the dot product of two vec3's
+ *
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @returns {Number} dot product of a and b
+ */
+vec3.dot = function (a, b) {
+    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
+};
+
+/**
+ * Computes the cross product of two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @returns {vec3} out
+ */
+vec3.cross = function(out, a, b) {
+    var ax = a[0], ay = a[1], az = a[2],
+        bx = b[0], by = b[1], bz = b[2];
+
+    out[0] = ay * bz - az * by;
+    out[1] = az * bx - ax * bz;
+    out[2] = ax * by - ay * bx;
+    return out;
+};
+
+/**
+ * Performs a linear interpolation between two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @param {Number} t interpolation amount between the two inputs
+ * @returns {vec3} out
+ */
+vec3.lerp = function (out, a, b, t) {
+    var ax = a[0],
+        ay = a[1],
+        az = a[2];
+    out[0] = ax + t * (b[0] - ax);
+    out[1] = ay + t * (b[1] - ay);
+    out[2] = az + t * (b[2] - az);
+    return out;
+};
+
+/**
+ * Performs a hermite interpolation with two control points
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @param {vec3} c the third operand
+ * @param {vec3} d the fourth operand
+ * @param {Number} t interpolation amount between the two inputs
+ * @returns {vec3} out
+ */
+vec3.hermite = function (out, a, b, c, d, t) {
+  var factorTimes2 = t * t,
+      factor1 = factorTimes2 * (2 * t - 3) + 1,
+      factor2 = factorTimes2 * (t - 2) + t,
+      factor3 = factorTimes2 * (t - 1),
+      factor4 = factorTimes2 * (3 - 2 * t);
+  
+  out[0] = a[0] * factor1 + b[0] * factor2 + c[0] * factor3 + d[0] * factor4;
+  out[1] = a[1] * factor1 + b[1] * factor2 + c[1] * factor3 + d[1] * factor4;
+  out[2] = a[2] * factor1 + b[2] * factor2 + c[2] * factor3 + d[2] * factor4;
+  
+  return out;
+};
+
+/**
+ * Performs a bezier interpolation with two control points
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @param {vec3} c the third operand
+ * @param {vec3} d the fourth operand
+ * @param {Number} t interpolation amount between the two inputs
+ * @returns {vec3} out
+ */
+vec3.bezier = function (out, a, b, c, d, t) {
+  var inverseFactor = 1 - t,
+      inverseFactorTimesTwo = inverseFactor * inverseFactor,
+      factorTimes2 = t * t,
+      factor1 = inverseFactorTimesTwo * inverseFactor,
+      factor2 = 3 * t * inverseFactorTimesTwo,
+      factor3 = 3 * factorTimes2 * inverseFactor,
+      factor4 = factorTimes2 * t;
+  
+  out[0] = a[0] * factor1 + b[0] * factor2 + c[0] * factor3 + d[0] * factor4;
+  out[1] = a[1] * factor1 + b[1] * factor2 + c[1] * factor3 + d[1] * factor4;
+  out[2] = a[2] * factor1 + b[2] * factor2 + c[2] * factor3 + d[2] * factor4;
+  
+  return out;
+};
+
+/**
+ * Generates a random vector with the given scale
+ *
+ * @param {vec3} out the receiving vector
+ * @param {Number} [scale] Length of the resulting vector. If ommitted, a unit vector will be returned
+ * @returns {vec3} out
+ */
+vec3.random = function (out, scale) {
+    scale = scale || 1.0;
+
+    var r = glMatrix.RANDOM() * 2.0 * Math.PI;
+    var z = (glMatrix.RANDOM() * 2.0) - 1.0;
+    var zScale = Math.sqrt(1.0-z*z) * scale;
+
+    out[0] = Math.cos(r) * zScale;
+    out[1] = Math.sin(r) * zScale;
+    out[2] = z * scale;
+    return out;
+};
+
+/**
+ * Transforms the vec3 with a mat4.
+ * 4th vector component is implicitly '1'
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the vector to transform
+ * @param {mat4} m matrix to transform with
+ * @returns {vec3} out
+ */
+vec3.transformMat4 = function(out, a, m) {
+    var x = a[0], y = a[1], z = a[2],
+        w = m[3] * x + m[7] * y + m[11] * z + m[15];
+    w = w || 1.0;
+    out[0] = (m[0] * x + m[4] * y + m[8] * z + m[12]) / w;
+    out[1] = (m[1] * x + m[5] * y + m[9] * z + m[13]) / w;
+    out[2] = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w;
+    return out;
+};
+
+/**
+ * Transforms the vec3 with a mat3.
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the vector to transform
+ * @param {mat4} m the 3x3 matrix to transform with
+ * @returns {vec3} out
+ */
+vec3.transformMat3 = function(out, a, m) {
+    var x = a[0], y = a[1], z = a[2];
+    out[0] = x * m[0] + y * m[3] + z * m[6];
+    out[1] = x * m[1] + y * m[4] + z * m[7];
+    out[2] = x * m[2] + y * m[5] + z * m[8];
+    return out;
+};
+
+/**
+ * Transforms the vec3 with a quat
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the vector to transform
+ * @param {quat} q quaternion to transform with
+ * @returns {vec3} out
+ */
+vec3.transformQuat = function(out, a, q) {
+    // benchmarks: http://jsperf.com/quaternion-transform-vec3-implementations
+
+    var x = a[0], y = a[1], z = a[2],
+        qx = q[0], qy = q[1], qz = q[2], qw = q[3],
+
+        // calculate quat * vec
+        ix = qw * x + qy * z - qz * y,
+        iy = qw * y + qz * x - qx * z,
+        iz = qw * z + qx * y - qy * x,
+        iw = -qx * x - qy * y - qz * z;
+
+    // calculate result * inverse quat
+    out[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy;
+    out[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz;
+    out[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx;
+    return out;
+};
+
+/**
+ * Rotate a 3D vector around the x-axis
+ * @param {vec3} out The receiving vec3
+ * @param {vec3} a The vec3 point to rotate
+ * @param {vec3} b The origin of the rotation
+ * @param {Number} c The angle of rotation
+ * @returns {vec3} out
+ */
+vec3.rotateX = function(out, a, b, c){
+   var p = [], r=[];
+	  //Translate point to the origin
+	  p[0] = a[0] - b[0];
+	  p[1] = a[1] - b[1];
+  	p[2] = a[2] - b[2];
+
+	  //perform rotation
+	  r[0] = p[0];
+	  r[1] = p[1]*Math.cos(c) - p[2]*Math.sin(c);
+	  r[2] = p[1]*Math.sin(c) + p[2]*Math.cos(c);
+
+	  //translate to correct position
+	  out[0] = r[0] + b[0];
+	  out[1] = r[1] + b[1];
+	  out[2] = r[2] + b[2];
+
+  	return out;
+};
+
+/**
+ * Rotate a 3D vector around the y-axis
+ * @param {vec3} out The receiving vec3
+ * @param {vec3} a The vec3 point to rotate
+ * @param {vec3} b The origin of the rotation
+ * @param {Number} c The angle of rotation
+ * @returns {vec3} out
+ */
+vec3.rotateY = function(out, a, b, c){
+  	var p = [], r=[];
+  	//Translate point to the origin
+  	p[0] = a[0] - b[0];
+  	p[1] = a[1] - b[1];
+  	p[2] = a[2] - b[2];
+  
+  	//perform rotation
+  	r[0] = p[2]*Math.sin(c) + p[0]*Math.cos(c);
+  	r[1] = p[1];
+  	r[2] = p[2]*Math.cos(c) - p[0]*Math.sin(c);
+  
+  	//translate to correct position
+  	out[0] = r[0] + b[0];
+  	out[1] = r[1] + b[1];
+  	out[2] = r[2] + b[2];
+  
+  	return out;
+};
+
+/**
+ * Rotate a 3D vector around the z-axis
+ * @param {vec3} out The receiving vec3
+ * @param {vec3} a The vec3 point to rotate
+ * @param {vec3} b The origin of the rotation
+ * @param {Number} c The angle of rotation
+ * @returns {vec3} out
+ */
+vec3.rotateZ = function(out, a, b, c){
+  	var p = [], r=[];
+  	//Translate point to the origin
+  	p[0] = a[0] - b[0];
+  	p[1] = a[1] - b[1];
+  	p[2] = a[2] - b[2];
+  
+  	//perform rotation
+  	r[0] = p[0]*Math.cos(c) - p[1]*Math.sin(c);
+  	r[1] = p[0]*Math.sin(c) + p[1]*Math.cos(c);
+  	r[2] = p[2];
+  
+  	//translate to correct position
+  	out[0] = r[0] + b[0];
+  	out[1] = r[1] + b[1];
+  	out[2] = r[2] + b[2];
+  
+  	return out;
+};
+
+/**
+ * Perform some operation over an array of vec3s.
+ *
+ * @param {Array} a the array of vectors to iterate over
+ * @param {Number} stride Number of elements between the start of each vec3. If 0 assumes tightly packed
+ * @param {Number} offset Number of elements to skip at the beginning of the array
+ * @param {Number} count Number of vec3s to iterate over. If 0 iterates over entire array
+ * @param {Function} fn Function to call for each vector in the array
+ * @param {Object} [arg] additional argument to pass to fn
+ * @returns {Array} a
+ * @function
+ */
+vec3.forEach = (function() {
+    var vec = vec3.create();
+
+    return function(a, stride, offset, count, fn, arg) {
+        var i, l;
+        if(!stride) {
+            stride = 3;
+        }
+
+        if(!offset) {
+            offset = 0;
+        }
+        
+        if(count) {
+            l = Math.min((count * stride) + offset, a.length);
+        } else {
+            l = a.length;
+        }
+
+        for(i = offset; i < l; i += stride) {
+            vec[0] = a[i]; vec[1] = a[i+1]; vec[2] = a[i+2];
+            fn(vec, vec, arg);
+            a[i] = vec[0]; a[i+1] = vec[1]; a[i+2] = vec[2];
+        }
+        
+        return a;
+    };
+})();
+
+/**
+ * Get the angle between two 3D vectors
+ * @param {vec3} a The first operand
+ * @param {vec3} b The second operand
+ * @returns {Number} The angle in radians
+ */
+vec3.angle = function(a, b) {
+   
+    var tempA = vec3.fromValues(a[0], a[1], a[2]);
+    var tempB = vec3.fromValues(b[0], b[1], b[2]);
+ 
+    vec3.normalize(tempA, tempA);
+    vec3.normalize(tempB, tempB);
+ 
+    var cosine = vec3.dot(tempA, tempB);
+
+    if(cosine > 1.0){
+        return 0;
+    } else {
+        return Math.acos(cosine);
+    }     
+};
+
+/**
+ * Returns a string representation of a vector
+ *
+ * @param {vec3} vec vector to represent as a string
+ * @returns {String} string representation of the vector
+ */
+vec3.str = function (a) {
+    return 'vec3(' + a[0] + ', ' + a[1] + ', ' + a[2] + ')';
+};
+
+/**
+ * Returns whether or not the vectors have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {vec3} a The first vector.
+ * @param {vec3} b The second vector.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+vec3.exactEquals = function (a, b) {
+    return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
+};
+
+/**
+ * Returns whether or not the vectors have approximately the same elements in the same position.
+ *
+ * @param {vec3} a The first vector.
+ * @param {vec3} b The second vector.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+vec3.equals = function (a, b) {
+    var a0 = a[0], a1 = a[1], a2 = a[2];
+    var b0 = b[0], b1 = b[1], b2 = b[2];
+    return (Math.abs(a0 - b0) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a0), Math.abs(b0)) &&
+            Math.abs(a1 - b1) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a1), Math.abs(b1)) &&
+            Math.abs(a2 - b2) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a2), Math.abs(b2)));
+};
+
+module.exports = vec3;
+
+},{"./common.js":266}],274:[function(require,module,exports){
+/* Copyright (c) 2015, Brandon Jones, Colin MacKenzie IV.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE. */
+
+var glMatrix = require("./common.js");
+
+/**
+ * @class 4 Dimensional Vector
+ * @name vec4
+ */
+var vec4 = {};
+
+/**
+ * Creates a new, empty vec4
+ *
+ * @returns {vec4} a new 4D vector
+ */
+vec4.create = function() {
+    var out = new glMatrix.ARRAY_TYPE(4);
+    out[0] = 0;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 0;
+    return out;
+};
+
+/**
+ * Creates a new vec4 initialized with values from an existing vector
+ *
+ * @param {vec4} a vector to clone
+ * @returns {vec4} a new 4D vector
+ */
+vec4.clone = function(a) {
+    var out = new glMatrix.ARRAY_TYPE(4);
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[3];
+    return out;
+};
+
+/**
+ * Creates a new vec4 initialized with the given values
+ *
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @param {Number} w W component
+ * @returns {vec4} a new 4D vector
+ */
+vec4.fromValues = function(x, y, z, w) {
+    var out = new glMatrix.ARRAY_TYPE(4);
+    out[0] = x;
+    out[1] = y;
+    out[2] = z;
+    out[3] = w;
+    return out;
+};
+
+/**
+ * Copy the values from one vec4 to another
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the source vector
+ * @returns {vec4} out
+ */
+vec4.copy = function(out, a) {
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[3];
+    return out;
+};
+
+/**
+ * Set the components of a vec4 to the given values
+ *
+ * @param {vec4} out the receiving vector
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @param {Number} w W component
+ * @returns {vec4} out
+ */
+vec4.set = function(out, x, y, z, w) {
+    out[0] = x;
+    out[1] = y;
+    out[2] = z;
+    out[3] = w;
+    return out;
+};
+
+/**
+ * Adds two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @returns {vec4} out
+ */
+vec4.add = function(out, a, b) {
+    out[0] = a[0] + b[0];
+    out[1] = a[1] + b[1];
+    out[2] = a[2] + b[2];
+    out[3] = a[3] + b[3];
+    return out;
+};
+
+/**
+ * Subtracts vector b from vector a
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @returns {vec4} out
+ */
+vec4.subtract = function(out, a, b) {
+    out[0] = a[0] - b[0];
+    out[1] = a[1] - b[1];
+    out[2] = a[2] - b[2];
+    out[3] = a[3] - b[3];
+    return out;
+};
+
+/**
+ * Alias for {@link vec4.subtract}
+ * @function
+ */
+vec4.sub = vec4.subtract;
+
+/**
+ * Multiplies two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @returns {vec4} out
+ */
+vec4.multiply = function(out, a, b) {
+    out[0] = a[0] * b[0];
+    out[1] = a[1] * b[1];
+    out[2] = a[2] * b[2];
+    out[3] = a[3] * b[3];
+    return out;
+};
+
+/**
+ * Alias for {@link vec4.multiply}
+ * @function
+ */
+vec4.mul = vec4.multiply;
+
+/**
+ * Divides two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @returns {vec4} out
+ */
+vec4.divide = function(out, a, b) {
+    out[0] = a[0] / b[0];
+    out[1] = a[1] / b[1];
+    out[2] = a[2] / b[2];
+    out[3] = a[3] / b[3];
+    return out;
+};
+
+/**
+ * Alias for {@link vec4.divide}
+ * @function
+ */
+vec4.div = vec4.divide;
+
+/**
+ * Math.ceil the components of a vec4
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a vector to ceil
+ * @returns {vec4} out
+ */
+vec4.ceil = function (out, a) {
+    out[0] = Math.ceil(a[0]);
+    out[1] = Math.ceil(a[1]);
+    out[2] = Math.ceil(a[2]);
+    out[3] = Math.ceil(a[3]);
+    return out;
+};
+
+/**
+ * Math.floor the components of a vec4
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a vector to floor
+ * @returns {vec4} out
+ */
+vec4.floor = function (out, a) {
+    out[0] = Math.floor(a[0]);
+    out[1] = Math.floor(a[1]);
+    out[2] = Math.floor(a[2]);
+    out[3] = Math.floor(a[3]);
+    return out;
+};
+
+/**
+ * Returns the minimum of two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @returns {vec4} out
+ */
+vec4.min = function(out, a, b) {
+    out[0] = Math.min(a[0], b[0]);
+    out[1] = Math.min(a[1], b[1]);
+    out[2] = Math.min(a[2], b[2]);
+    out[3] = Math.min(a[3], b[3]);
+    return out;
+};
+
+/**
+ * Returns the maximum of two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @returns {vec4} out
+ */
+vec4.max = function(out, a, b) {
+    out[0] = Math.max(a[0], b[0]);
+    out[1] = Math.max(a[1], b[1]);
+    out[2] = Math.max(a[2], b[2]);
+    out[3] = Math.max(a[3], b[3]);
+    return out;
+};
+
+/**
+ * Math.round the components of a vec4
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a vector to round
+ * @returns {vec4} out
+ */
+vec4.round = function (out, a) {
+    out[0] = Math.round(a[0]);
+    out[1] = Math.round(a[1]);
+    out[2] = Math.round(a[2]);
+    out[3] = Math.round(a[3]);
+    return out;
+};
+
+/**
+ * Scales a vec4 by a scalar number
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the vector to scale
+ * @param {Number} b amount to scale the vector by
+ * @returns {vec4} out
+ */
+vec4.scale = function(out, a, b) {
+    out[0] = a[0] * b;
+    out[1] = a[1] * b;
+    out[2] = a[2] * b;
+    out[3] = a[3] * b;
+    return out;
+};
+
+/**
+ * Adds two vec4's after scaling the second operand by a scalar value
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @param {Number} scale the amount to scale b by before adding
+ * @returns {vec4} out
+ */
+vec4.scaleAndAdd = function(out, a, b, scale) {
+    out[0] = a[0] + (b[0] * scale);
+    out[1] = a[1] + (b[1] * scale);
+    out[2] = a[2] + (b[2] * scale);
+    out[3] = a[3] + (b[3] * scale);
+    return out;
+};
+
+/**
+ * Calculates the euclidian distance between two vec4's
+ *
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @returns {Number} distance between a and b
+ */
+vec4.distance = function(a, b) {
+    var x = b[0] - a[0],
+        y = b[1] - a[1],
+        z = b[2] - a[2],
+        w = b[3] - a[3];
+    return Math.sqrt(x*x + y*y + z*z + w*w);
+};
+
+/**
+ * Alias for {@link vec4.distance}
+ * @function
+ */
+vec4.dist = vec4.distance;
+
+/**
+ * Calculates the squared euclidian distance between two vec4's
+ *
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @returns {Number} squared distance between a and b
+ */
+vec4.squaredDistance = function(a, b) {
+    var x = b[0] - a[0],
+        y = b[1] - a[1],
+        z = b[2] - a[2],
+        w = b[3] - a[3];
+    return x*x + y*y + z*z + w*w;
+};
+
+/**
+ * Alias for {@link vec4.squaredDistance}
+ * @function
+ */
+vec4.sqrDist = vec4.squaredDistance;
+
+/**
+ * Calculates the length of a vec4
+ *
+ * @param {vec4} a vector to calculate length of
+ * @returns {Number} length of a
+ */
+vec4.length = function (a) {
+    var x = a[0],
+        y = a[1],
+        z = a[2],
+        w = a[3];
+    return Math.sqrt(x*x + y*y + z*z + w*w);
+};
+
+/**
+ * Alias for {@link vec4.length}
+ * @function
+ */
+vec4.len = vec4.length;
+
+/**
+ * Calculates the squared length of a vec4
+ *
+ * @param {vec4} a vector to calculate squared length of
+ * @returns {Number} squared length of a
+ */
+vec4.squaredLength = function (a) {
+    var x = a[0],
+        y = a[1],
+        z = a[2],
+        w = a[3];
+    return x*x + y*y + z*z + w*w;
+};
+
+/**
+ * Alias for {@link vec4.squaredLength}
+ * @function
+ */
+vec4.sqrLen = vec4.squaredLength;
+
+/**
+ * Negates the components of a vec4
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a vector to negate
+ * @returns {vec4} out
+ */
+vec4.negate = function(out, a) {
+    out[0] = -a[0];
+    out[1] = -a[1];
+    out[2] = -a[2];
+    out[3] = -a[3];
+    return out;
+};
+
+/**
+ * Returns the inverse of the components of a vec4
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a vector to invert
+ * @returns {vec4} out
+ */
+vec4.inverse = function(out, a) {
+  out[0] = 1.0 / a[0];
+  out[1] = 1.0 / a[1];
+  out[2] = 1.0 / a[2];
+  out[3] = 1.0 / a[3];
+  return out;
+};
+
+/**
+ * Normalize a vec4
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a vector to normalize
+ * @returns {vec4} out
+ */
+vec4.normalize = function(out, a) {
+    var x = a[0],
+        y = a[1],
+        z = a[2],
+        w = a[3];
+    var len = x*x + y*y + z*z + w*w;
+    if (len > 0) {
+        len = 1 / Math.sqrt(len);
+        out[0] = x * len;
+        out[1] = y * len;
+        out[2] = z * len;
+        out[3] = w * len;
+    }
+    return out;
+};
+
+/**
+ * Calculates the dot product of two vec4's
+ *
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @returns {Number} dot product of a and b
+ */
+vec4.dot = function (a, b) {
+    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
+};
+
+/**
+ * Performs a linear interpolation between two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @param {Number} t interpolation amount between the two inputs
+ * @returns {vec4} out
+ */
+vec4.lerp = function (out, a, b, t) {
+    var ax = a[0],
+        ay = a[1],
+        az = a[2],
+        aw = a[3];
+    out[0] = ax + t * (b[0] - ax);
+    out[1] = ay + t * (b[1] - ay);
+    out[2] = az + t * (b[2] - az);
+    out[3] = aw + t * (b[3] - aw);
+    return out;
+};
+
+/**
+ * Generates a random vector with the given scale
+ *
+ * @param {vec4} out the receiving vector
+ * @param {Number} [scale] Length of the resulting vector. If ommitted, a unit vector will be returned
+ * @returns {vec4} out
+ */
+vec4.random = function (out, scale) {
+    scale = scale || 1.0;
+
+    //TODO: This is a pretty awful way of doing this. Find something better.
+    out[0] = glMatrix.RANDOM();
+    out[1] = glMatrix.RANDOM();
+    out[2] = glMatrix.RANDOM();
+    out[3] = glMatrix.RANDOM();
+    vec4.normalize(out, out);
+    vec4.scale(out, out, scale);
+    return out;
+};
+
+/**
+ * Transforms the vec4 with a mat4.
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the vector to transform
+ * @param {mat4} m matrix to transform with
+ * @returns {vec4} out
+ */
+vec4.transformMat4 = function(out, a, m) {
+    var x = a[0], y = a[1], z = a[2], w = a[3];
+    out[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w;
+    out[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w;
+    out[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w;
+    out[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w;
+    return out;
+};
+
+/**
+ * Transforms the vec4 with a quat
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the vector to transform
+ * @param {quat} q quaternion to transform with
+ * @returns {vec4} out
+ */
+vec4.transformQuat = function(out, a, q) {
+    var x = a[0], y = a[1], z = a[2],
+        qx = q[0], qy = q[1], qz = q[2], qw = q[3],
+
+        // calculate quat * vec
+        ix = qw * x + qy * z - qz * y,
+        iy = qw * y + qz * x - qx * z,
+        iz = qw * z + qx * y - qy * x,
+        iw = -qx * x - qy * y - qz * z;
+
+    // calculate result * inverse quat
+    out[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy;
+    out[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz;
+    out[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx;
+    out[3] = a[3];
+    return out;
+};
+
+/**
+ * Perform some operation over an array of vec4s.
+ *
+ * @param {Array} a the array of vectors to iterate over
+ * @param {Number} stride Number of elements between the start of each vec4. If 0 assumes tightly packed
+ * @param {Number} offset Number of elements to skip at the beginning of the array
+ * @param {Number} count Number of vec4s to iterate over. If 0 iterates over entire array
+ * @param {Function} fn Function to call for each vector in the array
+ * @param {Object} [arg] additional argument to pass to fn
+ * @returns {Array} a
+ * @function
+ */
+vec4.forEach = (function() {
+    var vec = vec4.create();
+
+    return function(a, stride, offset, count, fn, arg) {
+        var i, l;
+        if(!stride) {
+            stride = 4;
+        }
+
+        if(!offset) {
+            offset = 0;
+        }
+        
+        if(count) {
+            l = Math.min((count * stride) + offset, a.length);
+        } else {
+            l = a.length;
+        }
+
+        for(i = offset; i < l; i += stride) {
+            vec[0] = a[i]; vec[1] = a[i+1]; vec[2] = a[i+2]; vec[3] = a[i+3];
+            fn(vec, vec, arg);
+            a[i] = vec[0]; a[i+1] = vec[1]; a[i+2] = vec[2]; a[i+3] = vec[3];
+        }
+        
+        return a;
+    };
+})();
+
+/**
+ * Returns a string representation of a vector
+ *
+ * @param {vec4} vec vector to represent as a string
+ * @returns {String} string representation of the vector
+ */
+vec4.str = function (a) {
+    return 'vec4(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + a[3] + ')';
+};
+
+/**
+ * Returns whether or not the vectors have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {vec4} a The first vector.
+ * @param {vec4} b The second vector.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+vec4.exactEquals = function (a, b) {
+    return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3];
+};
+
+/**
+ * Returns whether or not the vectors have approximately the same elements in the same position.
+ *
+ * @param {vec4} a The first vector.
+ * @param {vec4} b The second vector.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+vec4.equals = function (a, b) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3];
+    var b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
+    return (Math.abs(a0 - b0) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a0), Math.abs(b0)) &&
+            Math.abs(a1 - b1) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a1), Math.abs(b1)) &&
+            Math.abs(a2 - b2) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a2), Math.abs(b2)) &&
+            Math.abs(a3 - b3) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a3), Math.abs(b3)));
+};
+
+module.exports = vec4;
+
+},{"./common.js":266}],275:[function(require,module,exports){
+'use strict';
+
+exports.__esModule = true;
+// istanbul ignore next
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+// istanbul ignore next
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+var _handlebarsBase = require('./handlebars/base');
+
+var base = _interopRequireWildcard(_handlebarsBase);
+
+// Each of these augment the Handlebars object. No need to setup here.
+// (This is done to easily share code between commonjs and browse envs)
+
+var _handlebarsSafeString = require('./handlebars/safe-string');
+
+var _handlebarsSafeString2 = _interopRequireDefault(_handlebarsSafeString);
+
+var _handlebarsException = require('./handlebars/exception');
+
+var _handlebarsException2 = _interopRequireDefault(_handlebarsException);
+
+var _handlebarsUtils = require('./handlebars/utils');
+
+var Utils = _interopRequireWildcard(_handlebarsUtils);
+
+var _handlebarsRuntime = require('./handlebars/runtime');
+
+var runtime = _interopRequireWildcard(_handlebarsRuntime);
+
+var _handlebarsNoConflict = require('./handlebars/no-conflict');
+
+var _handlebarsNoConflict2 = _interopRequireDefault(_handlebarsNoConflict);
+
+// For compatibility and usage outside of module systems, make the Handlebars object a namespace
+function create() {
+  var hb = new base.HandlebarsEnvironment();
+
+  Utils.extend(hb, base);
+  hb.SafeString = _handlebarsSafeString2['default'];
+  hb.Exception = _handlebarsException2['default'];
+  hb.Utils = Utils;
+  hb.escapeExpression = Utils.escapeExpression;
+
+  hb.VM = runtime;
+  hb.template = function (spec) {
+    return runtime.template(spec, hb);
+  };
+
+  return hb;
+}
+
+var inst = create();
+inst.create = create;
+
+_handlebarsNoConflict2['default'](inst);
+
+inst['default'] = inst;
+
+exports['default'] = inst;
+module.exports = exports['default'];
 
 
+},{"./handlebars/base":276,"./handlebars/exception":279,"./handlebars/no-conflict":289,"./handlebars/runtime":290,"./handlebars/safe-string":291,"./handlebars/utils":292}],276:[function(require,module,exports){
+'use strict';
+
+exports.__esModule = true;
+exports.HandlebarsEnvironment = HandlebarsEnvironment;
+// istanbul ignore next
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _utils = require('./utils');
+
+var _exception = require('./exception');
+
+var _exception2 = _interopRequireDefault(_exception);
+
+var _helpers = require('./helpers');
+
+var _decorators = require('./decorators');
+
+var _logger = require('./logger');
+
+var _logger2 = _interopRequireDefault(_logger);
+
+var VERSION = '4.0.5';
+exports.VERSION = VERSION;
+var COMPILER_REVISION = 7;
+
+exports.COMPILER_REVISION = COMPILER_REVISION;
+var REVISION_CHANGES = {
+  1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
+  2: '== 1.0.0-rc.3',
+  3: '== 1.0.0-rc.4',
+  4: '== 1.x.x',
+  5: '== 2.0.0-alpha.x',
+  6: '>= 2.0.0-beta.1',
+  7: '>= 4.0.0'
+};
+
+exports.REVISION_CHANGES = REVISION_CHANGES;
+var objectType = '[object Object]';
+
+function HandlebarsEnvironment(helpers, partials, decorators) {
+  this.helpers = helpers || {};
+  this.partials = partials || {};
+  this.decorators = decorators || {};
+
+  _helpers.registerDefaultHelpers(this);
+  _decorators.registerDefaultDecorators(this);
+}
+
+HandlebarsEnvironment.prototype = {
+  constructor: HandlebarsEnvironment,
+
+  logger: _logger2['default'],
+  log: _logger2['default'].log,
+
+  registerHelper: function registerHelper(name, fn) {
+    if (_utils.toString.call(name) === objectType) {
+      if (fn) {
+        throw new _exception2['default']('Arg not supported with multiple helpers');
+      }
+      _utils.extend(this.helpers, name);
+    } else {
+      this.helpers[name] = fn;
+    }
+  },
+  unregisterHelper: function unregisterHelper(name) {
+    delete this.helpers[name];
+  },
+
+  registerPartial: function registerPartial(name, partial) {
+    if (_utils.toString.call(name) === objectType) {
+      _utils.extend(this.partials, name);
+    } else {
+      if (typeof partial === 'undefined') {
+        throw new _exception2['default']('Attempting to register a partial called "' + name + '" as undefined');
+      }
+      this.partials[name] = partial;
+    }
+  },
+  unregisterPartial: function unregisterPartial(name) {
+    delete this.partials[name];
+  },
+
+  registerDecorator: function registerDecorator(name, fn) {
+    if (_utils.toString.call(name) === objectType) {
+      if (fn) {
+        throw new _exception2['default']('Arg not supported with multiple decorators');
+      }
+      _utils.extend(this.decorators, name);
+    } else {
+      this.decorators[name] = fn;
+    }
+  },
+  unregisterDecorator: function unregisterDecorator(name) {
+    delete this.decorators[name];
+  }
+};
+
+var log = _logger2['default'].log;
+
+exports.log = log;
+exports.createFrame = _utils.createFrame;
+exports.logger = _logger2['default'];
 
 
+},{"./decorators":277,"./exception":279,"./helpers":280,"./logger":288,"./utils":292}],277:[function(require,module,exports){
+'use strict';
 
+exports.__esModule = true;
+exports.registerDefaultDecorators = registerDefaultDecorators;
+// istanbul ignore next
 
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
 
+var _decoratorsInline = require('./decorators/inline');
 
-function parse(str){
-str=''+str;
-if(str.length>10000)return;
-var match=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(str);
-if(!match)return;
-var n=parseFloat(match[1]);
-var type=(match[2]||'ms').toLowerCase();
-switch(type){
-case'years':
-case'year':
-case'yrs':
-case'yr':
-case'y':
-return n*y;
-case'days':
-case'day':
-case'd':
-return n*d;
-case'hours':
-case'hour':
-case'hrs':
-case'hr':
-case'h':
-return n*h;
-case'minutes':
-case'minute':
-case'mins':
-case'min':
-case'm':
-return n*m;
-case'seconds':
-case'second':
-case'secs':
-case'sec':
-case's':
-return n*s;
-case'milliseconds':
-case'millisecond':
-case'msecs':
-case'msec':
-case'ms':
-return n;}
+var _decoratorsInline2 = _interopRequireDefault(_decoratorsInline);
 
+function registerDefaultDecorators(instance) {
+  _decoratorsInline2['default'](instance);
 }
 
 
+},{"./decorators/inline":278}],278:[function(require,module,exports){
+'use strict';
+
+exports.__esModule = true;
+
+var _utils = require('../utils');
+
+exports['default'] = function (instance) {
+  instance.registerDecorator('inline', function (fn, props, container, options) {
+    var ret = fn;
+    if (!props.partials) {
+      props.partials = {};
+      ret = function (context, options) {
+        // Create a new partials stack frame prior to exec.
+        var original = container.partials;
+        container.partials = _utils.extend({}, original, props.partials);
+        var ret = fn(context, options);
+        container.partials = original;
+        return ret;
+      };
+    }
+
+    props.partials[options.args[0]] = options.fn;
+
+    return ret;
+  });
+};
+
+module.exports = exports['default'];
 
 
+},{"../utils":292}],279:[function(require,module,exports){
+'use strict';
+
+exports.__esModule = true;
+
+var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
+
+function Exception(message, node) {
+  var loc = node && node.loc,
+      line = undefined,
+      column = undefined;
+  if (loc) {
+    line = loc.start.line;
+    column = loc.start.column;
+
+    message += ' - ' + line + ':' + column;
+  }
+
+  var tmp = Error.prototype.constructor.call(this, message);
+
+  // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
+  for (var idx = 0; idx < errorProps.length; idx++) {
+    this[errorProps[idx]] = tmp[errorProps[idx]];
+  }
+
+  /* istanbul ignore else */
+  if (Error.captureStackTrace) {
+    Error.captureStackTrace(this, Exception);
+  }
+
+  if (loc) {
+    this.lineNumber = line;
+    this.column = column;
+  }
+}
+
+Exception.prototype = new Error();
+
+exports['default'] = Exception;
+module.exports = exports['default'];
 
 
+},{}],280:[function(require,module,exports){
+'use strict';
 
+exports.__esModule = true;
+exports.registerDefaultHelpers = registerDefaultHelpers;
+// istanbul ignore next
 
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
 
-function short(ms){
-if(ms>=d)return Math.round(ms/d)+'d';
-if(ms>=h)return Math.round(ms/h)+'h';
-if(ms>=m)return Math.round(ms/m)+'m';
-if(ms>=s)return Math.round(ms/s)+'s';
-return ms+'ms';
+var _helpersBlockHelperMissing = require('./helpers/block-helper-missing');
+
+var _helpersBlockHelperMissing2 = _interopRequireDefault(_helpersBlockHelperMissing);
+
+var _helpersEach = require('./helpers/each');
+
+var _helpersEach2 = _interopRequireDefault(_helpersEach);
+
+var _helpersHelperMissing = require('./helpers/helper-missing');
+
+var _helpersHelperMissing2 = _interopRequireDefault(_helpersHelperMissing);
+
+var _helpersIf = require('./helpers/if');
+
+var _helpersIf2 = _interopRequireDefault(_helpersIf);
+
+var _helpersLog = require('./helpers/log');
+
+var _helpersLog2 = _interopRequireDefault(_helpersLog);
+
+var _helpersLookup = require('./helpers/lookup');
+
+var _helpersLookup2 = _interopRequireDefault(_helpersLookup);
+
+var _helpersWith = require('./helpers/with');
+
+var _helpersWith2 = _interopRequireDefault(_helpersWith);
+
+function registerDefaultHelpers(instance) {
+  _helpersBlockHelperMissing2['default'](instance);
+  _helpersEach2['default'](instance);
+  _helpersHelperMissing2['default'](instance);
+  _helpersIf2['default'](instance);
+  _helpersLog2['default'](instance);
+  _helpersLookup2['default'](instance);
+  _helpersWith2['default'](instance);
 }
 
 
+},{"./helpers/block-helper-missing":281,"./helpers/each":282,"./helpers/helper-missing":283,"./helpers/if":284,"./helpers/log":285,"./helpers/lookup":286,"./helpers/with":287}],281:[function(require,module,exports){
+'use strict';
+
+exports.__esModule = true;
+
+var _utils = require('../utils');
+
+exports['default'] = function (instance) {
+  instance.registerHelper('blockHelperMissing', function (context, options) {
+    var inverse = options.inverse,
+        fn = options.fn;
+
+    if (context === true) {
+      return fn(this);
+    } else if (context === false || context == null) {
+      return inverse(this);
+    } else if (_utils.isArray(context)) {
+      if (context.length > 0) {
+        if (options.ids) {
+          options.ids = [options.name];
+        }
+
+        return instance.helpers.each(context, options);
+      } else {
+        return inverse(this);
+      }
+    } else {
+      if (options.data && options.ids) {
+        var data = _utils.createFrame(options.data);
+        data.contextPath = _utils.appendContextPath(options.data.contextPath, options.name);
+        options = { data: data };
+      }
+
+      return fn(context, options);
+    }
+  });
+};
+
+module.exports = exports['default'];
 
 
+},{"../utils":292}],282:[function(require,module,exports){
+'use strict';
+
+exports.__esModule = true;
+// istanbul ignore next
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _utils = require('../utils');
+
+var _exception = require('../exception');
+
+var _exception2 = _interopRequireDefault(_exception);
+
+exports['default'] = function (instance) {
+  instance.registerHelper('each', function (context, options) {
+    if (!options) {
+      throw new _exception2['default']('Must pass iterator to #each');
+    }
+
+    var fn = options.fn,
+        inverse = options.inverse,
+        i = 0,
+        ret = '',
+        data = undefined,
+        contextPath = undefined;
+
+    if (options.data && options.ids) {
+      contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.';
+    }
+
+    if (_utils.isFunction(context)) {
+      context = context.call(this);
+    }
+
+    if (options.data) {
+      data = _utils.createFrame(options.data);
+    }
+
+    function execIteration(field, index, last) {
+      if (data) {
+        data.key = field;
+        data.index = index;
+        data.first = index === 0;
+        data.last = !!last;
+
+        if (contextPath) {
+          data.contextPath = contextPath + field;
+        }
+      }
+
+      ret = ret + fn(context[field], {
+        data: data,
+        blockParams: _utils.blockParams([context[field], field], [contextPath + field, null])
+      });
+    }
+
+    if (context && typeof context === 'object') {
+      if (_utils.isArray(context)) {
+        for (var j = context.length; i < j; i++) {
+          if (i in context) {
+            execIteration(i, i, i === context.length - 1);
+          }
+        }
+      } else {
+        var priorKey = undefined;
+
+        for (var key in context) {
+          if (context.hasOwnProperty(key)) {
+            // We're running the iterations one step out of sync so we can detect
+            // the last iteration without have to scan the object twice and create
+            // an itermediate keys array.
+            if (priorKey !== undefined) {
+              execIteration(priorKey, i - 1);
+            }
+            priorKey = key;
+            i++;
+          }
+        }
+        if (priorKey !== undefined) {
+          execIteration(priorKey, i - 1, true);
+        }
+      }
+    }
+
+    if (i === 0) {
+      ret = inverse(this);
+    }
+
+    return ret;
+  });
+};
+
+module.exports = exports['default'];
 
 
+},{"../exception":279,"../utils":292}],283:[function(require,module,exports){
+'use strict';
+
+exports.__esModule = true;
+// istanbul ignore next
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _exception = require('../exception');
+
+var _exception2 = _interopRequireDefault(_exception);
+
+exports['default'] = function (instance) {
+  instance.registerHelper('helperMissing', function () /* [args, ]options */{
+    if (arguments.length === 1) {
+      // A missing field in a {{foo}} construct.
+      return undefined;
+    } else {
+      // Someone is actually trying to call something, blow up.
+      throw new _exception2['default']('Missing helper: "' + arguments[arguments.length - 1].name + '"');
+    }
+  });
+};
+
+module.exports = exports['default'];
 
 
+},{"../exception":279}],284:[function(require,module,exports){
+'use strict';
 
-function long(ms){
-return plural(ms,d,'day')||
-plural(ms,h,'hour')||
-plural(ms,m,'minute')||
-plural(ms,s,'second')||
-ms+' ms';
+exports.__esModule = true;
+
+var _utils = require('../utils');
+
+exports['default'] = function (instance) {
+  instance.registerHelper('if', function (conditional, options) {
+    if (_utils.isFunction(conditional)) {
+      conditional = conditional.call(this);
+    }
+
+    // Default behavior is to render the positive path if the value is truthy and not empty.
+    // The `includeZero` option may be set to treat the condtional as purely not empty based on the
+    // behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
+    if (!options.hash.includeZero && !conditional || _utils.isEmpty(conditional)) {
+      return options.inverse(this);
+    } else {
+      return options.fn(this);
+    }
+  });
+
+  instance.registerHelper('unless', function (conditional, options) {
+    return instance.helpers['if'].call(this, conditional, { fn: options.inverse, inverse: options.fn, hash: options.hash });
+  });
+};
+
+module.exports = exports['default'];
+
+
+},{"../utils":292}],285:[function(require,module,exports){
+'use strict';
+
+exports.__esModule = true;
+
+exports['default'] = function (instance) {
+  instance.registerHelper('log', function () /* message, options */{
+    var args = [undefined],
+        options = arguments[arguments.length - 1];
+    for (var i = 0; i < arguments.length - 1; i++) {
+      args.push(arguments[i]);
+    }
+
+    var level = 1;
+    if (options.hash.level != null) {
+      level = options.hash.level;
+    } else if (options.data && options.data.level != null) {
+      level = options.data.level;
+    }
+    args[0] = level;
+
+    instance.log.apply(instance, args);
+  });
+};
+
+module.exports = exports['default'];
+
+
+},{}],286:[function(require,module,exports){
+'use strict';
+
+exports.__esModule = true;
+
+exports['default'] = function (instance) {
+  instance.registerHelper('lookup', function (obj, field) {
+    return obj && obj[field];
+  });
+};
+
+module.exports = exports['default'];
+
+
+},{}],287:[function(require,module,exports){
+'use strict';
+
+exports.__esModule = true;
+
+var _utils = require('../utils');
+
+exports['default'] = function (instance) {
+  instance.registerHelper('with', function (context, options) {
+    if (_utils.isFunction(context)) {
+      context = context.call(this);
+    }
+
+    var fn = options.fn;
+
+    if (!_utils.isEmpty(context)) {
+      var data = options.data;
+      if (options.data && options.ids) {
+        data = _utils.createFrame(options.data);
+        data.contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]);
+      }
+
+      return fn(context, {
+        data: data,
+        blockParams: _utils.blockParams([context], [data && data.contextPath])
+      });
+    } else {
+      return options.inverse(this);
+    }
+  });
+};
+
+module.exports = exports['default'];
+
+
+},{"../utils":292}],288:[function(require,module,exports){
+'use strict';
+
+exports.__esModule = true;
+
+var _utils = require('./utils');
+
+var logger = {
+  methodMap: ['debug', 'info', 'warn', 'error'],
+  level: 'info',
+
+  // Maps a given level value to the `methodMap` indexes above.
+  lookupLevel: function lookupLevel(level) {
+    if (typeof level === 'string') {
+      var levelMap = _utils.indexOf(logger.methodMap, level.toLowerCase());
+      if (levelMap >= 0) {
+        level = levelMap;
+      } else {
+        level = parseInt(level, 10);
+      }
+    }
+
+    return level;
+  },
+
+  // Can be overridden in the host environment
+  log: function log(level) {
+    level = logger.lookupLevel(level);
+
+    if (typeof console !== 'undefined' && logger.lookupLevel(logger.level) <= level) {
+      var method = logger.methodMap[level];
+      if (!console[method]) {
+        // eslint-disable-line no-console
+        method = 'log';
+      }
+
+      for (var _len = arguments.length, message = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+        message[_key - 1] = arguments[_key];
+      }
+
+      console[method].apply(console, message); // eslint-disable-line no-console
+    }
+  }
+};
+
+exports['default'] = logger;
+module.exports = exports['default'];
+
+
+},{"./utils":292}],289:[function(require,module,exports){
+(function (global){
+/* global window */
+'use strict';
+
+exports.__esModule = true;
+
+exports['default'] = function (Handlebars) {
+  /* istanbul ignore next */
+  var root = typeof global !== 'undefined' ? global : window,
+      $Handlebars = root.Handlebars;
+  /* istanbul ignore next */
+  Handlebars.noConflict = function () {
+    if (root.Handlebars === Handlebars) {
+      root.Handlebars = $Handlebars;
+    }
+    return Handlebars;
+  };
+};
+
+module.exports = exports['default'];
+
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],290:[function(require,module,exports){
+'use strict';
+
+exports.__esModule = true;
+exports.checkRevision = checkRevision;
+exports.template = template;
+exports.wrapProgram = wrapProgram;
+exports.resolvePartial = resolvePartial;
+exports.invokePartial = invokePartial;
+exports.noop = noop;
+// istanbul ignore next
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+// istanbul ignore next
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+var _utils = require('./utils');
+
+var Utils = _interopRequireWildcard(_utils);
+
+var _exception = require('./exception');
+
+var _exception2 = _interopRequireDefault(_exception);
+
+var _base = require('./base');
+
+function checkRevision(compilerInfo) {
+  var compilerRevision = compilerInfo && compilerInfo[0] || 1,
+      currentRevision = _base.COMPILER_REVISION;
+
+  if (compilerRevision !== currentRevision) {
+    if (compilerRevision < currentRevision) {
+      var runtimeVersions = _base.REVISION_CHANGES[currentRevision],
+          compilerVersions = _base.REVISION_CHANGES[compilerRevision];
+      throw new _exception2['default']('Template was precompiled with an older version of Handlebars than the current runtime. ' + 'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').');
+    } else {
+      // Use the embedded version info since the runtime doesn't know about this revision yet
+      throw new _exception2['default']('Template was precompiled with a newer version of Handlebars than the current runtime. ' + 'Please update your runtime to a newer version (' + compilerInfo[1] + ').');
+    }
+  }
+}
+
+function template(templateSpec, env) {
+  /* istanbul ignore next */
+  if (!env) {
+    throw new _exception2['default']('No environment passed to template');
+  }
+  if (!templateSpec || !templateSpec.main) {
+    throw new _exception2['default']('Unknown template object: ' + typeof templateSpec);
+  }
+
+  templateSpec.main.decorator = templateSpec.main_d;
+
+  // Note: Using env.VM references rather than local var references throughout this section to allow
+  // for external users to override these as psuedo-supported APIs.
+  env.VM.checkRevision(templateSpec.compiler);
+
+  function invokePartialWrapper(partial, context, options) {
+    if (options.hash) {
+      context = Utils.extend({}, context, options.hash);
+      if (options.ids) {
+        options.ids[0] = true;
+      }
+    }
+
+    partial = env.VM.resolvePartial.call(this, partial, context, options);
+    var result = env.VM.invokePartial.call(this, partial, context, options);
+
+    if (result == null && env.compile) {
+      options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env);
+      result = options.partials[options.name](context, options);
+    }
+    if (result != null) {
+      if (options.indent) {
+        var lines = result.split('\n');
+        for (var i = 0, l = lines.length; i < l; i++) {
+          if (!lines[i] && i + 1 === l) {
+            break;
+          }
+
+          lines[i] = options.indent + lines[i];
+        }
+        result = lines.join('\n');
+      }
+      return result;
+    } else {
+      throw new _exception2['default']('The partial ' + options.name + ' could not be compiled when running in runtime-only mode');
+    }
+  }
+
+  // Just add water
+  var container = {
+    strict: function strict(obj, name) {
+      if (!(name in obj)) {
+        throw new _exception2['default']('"' + name + '" not defined in ' + obj);
+      }
+      return obj[name];
+    },
+    lookup: function lookup(depths, name) {
+      var len = depths.length;
+      for (var i = 0; i < len; i++) {
+        if (depths[i] && depths[i][name] != null) {
+          return depths[i][name];
+        }
+      }
+    },
+    lambda: function lambda(current, context) {
+      return typeof current === 'function' ? current.call(context) : current;
+    },
+
+    escapeExpression: Utils.escapeExpression,
+    invokePartial: invokePartialWrapper,
+
+    fn: function fn(i) {
+      var ret = templateSpec[i];
+      ret.decorator = templateSpec[i + '_d'];
+      return ret;
+    },
+
+    programs: [],
+    program: function program(i, data, declaredBlockParams, blockParams, depths) {
+      var programWrapper = this.programs[i],
+          fn = this.fn(i);
+      if (data || depths || blockParams || declaredBlockParams) {
+        programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths);
+      } else if (!programWrapper) {
+        programWrapper = this.programs[i] = wrapProgram(this, i, fn);
+      }
+      return programWrapper;
+    },
+
+    data: function data(value, depth) {
+      while (value && depth--) {
+        value = value._parent;
+      }
+      return value;
+    },
+    merge: function merge(param, common) {
+      var obj = param || common;
+
+      if (param && common && param !== common) {
+        obj = Utils.extend({}, common, param);
+      }
+
+      return obj;
+    },
+
+    noop: env.VM.noop,
+    compilerInfo: templateSpec.compiler
+  };
+
+  function ret(context) {
+    var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+    var data = options.data;
+
+    ret._setup(options);
+    if (!options.partial && templateSpec.useData) {
+      data = initData(context, data);
+    }
+    var depths = undefined,
+        blockParams = templateSpec.useBlockParams ? [] : undefined;
+    if (templateSpec.useDepths) {
+      if (options.depths) {
+        depths = context !== options.depths[0] ? [context].concat(options.depths) : options.depths;
+      } else {
+        depths = [context];
+      }
+    }
+
+    function main(context /*, options*/) {
+      return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths);
+    }
+    main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams);
+    return main(context, options);
+  }
+  ret.isTop = true;
+
+  ret._setup = function (options) {
+    if (!options.partial) {
+      container.helpers = container.merge(options.helpers, env.helpers);
+
+      if (templateSpec.usePartial) {
+        container.partials = container.merge(options.partials, env.partials);
+      }
+      if (templateSpec.usePartial || templateSpec.useDecorators) {
+        container.decorators = container.merge(options.decorators, env.decorators);
+      }
+    } else {
+      container.helpers = options.helpers;
+      container.partials = options.partials;
+      container.decorators = options.decorators;
+    }
+  };
+
+  ret._child = function (i, data, blockParams, depths) {
+    if (templateSpec.useBlockParams && !blockParams) {
+      throw new _exception2['default']('must pass block params');
+    }
+    if (templateSpec.useDepths && !depths) {
+      throw new _exception2['default']('must pass parent depths');
+    }
+
+    return wrapProgram(container, i, templateSpec[i], data, 0, blockParams, depths);
+  };
+  return ret;
+}
+
+function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) {
+  function prog(context) {
+    var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+    var currentDepths = depths;
+    if (depths && context !== depths[0]) {
+      currentDepths = [context].concat(depths);
+    }
+
+    return fn(container, context, container.helpers, container.partials, options.data || data, blockParams && [options.blockParams].concat(blockParams), currentDepths);
+  }
+
+  prog = executeDecorators(fn, prog, container, depths, data, blockParams);
+
+  prog.program = i;
+  prog.depth = depths ? depths.length : 0;
+  prog.blockParams = declaredBlockParams || 0;
+  return prog;
+}
+
+function resolvePartial(partial, context, options) {
+  if (!partial) {
+    if (options.name === '@partial-block') {
+      partial = options.data['partial-block'];
+    } else {
+      partial = options.partials[options.name];
+    }
+  } else if (!partial.call && !options.name) {
+    // This is a dynamic partial that returned a string
+    options.name = partial;
+    partial = options.partials[partial];
+  }
+  return partial;
+}
+
+function invokePartial(partial, context, options) {
+  options.partial = true;
+  if (options.ids) {
+    options.data.contextPath = options.ids[0] || options.data.contextPath;
+  }
+
+  var partialBlock = undefined;
+  if (options.fn && options.fn !== noop) {
+    options.data = _base.createFrame(options.data);
+    partialBlock = options.data['partial-block'] = options.fn;
+
+    if (partialBlock.partials) {
+      options.partials = Utils.extend({}, options.partials, partialBlock.partials);
+    }
+  }
+
+  if (partial === undefined && partialBlock) {
+    partial = partialBlock;
+  }
+
+  if (partial === undefined) {
+    throw new _exception2['default']('The partial ' + options.name + ' could not be found');
+  } else if (partial instanceof Function) {
+    return partial(context, options);
+  }
+}
+
+function noop() {
+  return '';
+}
+
+function initData(context, data) {
+  if (!data || !('root' in data)) {
+    data = data ? _base.createFrame(data) : {};
+    data.root = context;
+  }
+  return data;
+}
+
+function executeDecorators(fn, prog, container, depths, data, blockParams) {
+  if (fn.decorator) {
+    var props = {};
+    prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths);
+    Utils.extend(prog, props);
+  }
+  return prog;
 }
 
 
+},{"./base":276,"./exception":279,"./utils":292}],291:[function(require,module,exports){
+// Build out our basic SafeString type
+'use strict';
 
-
-
-function plural(ms,n,name){
-if(ms<n)return;
-if(ms<n*1.5)return Math.floor(ms/n)+' '+name;
-return Math.ceil(ms/n)+' '+name+'s';
+exports.__esModule = true;
+function SafeString(string) {
+  this.string = string;
 }
 
-},{}],272:[function(require,module,exports){
-(function(Buffer){
+SafeString.prototype.toString = SafeString.prototype.toHTML = function () {
+  return '' + this.string;
+};
+
+exports['default'] = SafeString;
+module.exports = exports['default'];
+
+
+},{}],292:[function(require,module,exports){
+'use strict';
+
+exports.__esModule = true;
+exports.extend = extend;
+exports.indexOf = indexOf;
+exports.escapeExpression = escapeExpression;
+exports.isEmpty = isEmpty;
+exports.createFrame = createFrame;
+exports.blockParams = blockParams;
+exports.appendContextPath = appendContextPath;
+var escape = {
+  '&': '&amp;',
+  '<': '&lt;',
+  '>': '&gt;',
+  '"': '&quot;',
+  "'": '&#x27;',
+  '`': '&#x60;',
+  '=': '&#x3D;'
+};
+
+var badChars = /[&<>"'`=]/g,
+    possible = /[&<>"'`=]/;
+
+function escapeChar(chr) {
+  return escape[chr];
+}
+
+function extend(obj /* , ...source */) {
+  for (var i = 1; i < arguments.length; i++) {
+    for (var key in arguments[i]) {
+      if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
+        obj[key] = arguments[i][key];
+      }
+    }
+  }
+
+  return obj;
+}
+
+var toString = Object.prototype.toString;
+
+exports.toString = toString;
+// Sourced from lodash
+// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
+/* eslint-disable func-style */
+var isFunction = function isFunction(value) {
+  return typeof value === 'function';
+};
+// fallback for older versions of Chrome and Safari
+/* istanbul ignore next */
+if (isFunction(/x/)) {
+  exports.isFunction = isFunction = function (value) {
+    return typeof value === 'function' && toString.call(value) === '[object Function]';
+  };
+}
+exports.isFunction = isFunction;
+
+/* eslint-enable func-style */
+
+/* istanbul ignore next */
+var isArray = Array.isArray || function (value) {
+  return value && typeof value === 'object' ? toString.call(value) === '[object Array]' : false;
+};
+
+exports.isArray = isArray;
+// Older IE versions do not directly support indexOf so we must implement our own, sadly.
+
+function indexOf(array, value) {
+  for (var i = 0, len = array.length; i < len; i++) {
+    if (array[i] === value) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+function escapeExpression(string) {
+  if (typeof string !== 'string') {
+    // don't escape SafeStrings, since they're already safe
+    if (string && string.toHTML) {
+      return string.toHTML();
+    } else if (string == null) {
+      return '';
+    } else if (!string) {
+      return string + '';
+    }
+
+    // Force a string conversion as this will be done by the append regardless and
+    // the regex test will do this transparently behind the scenes, causing issues if
+    // an object's to string has escaped characters in it.
+    string = '' + string;
+  }
+
+  if (!possible.test(string)) {
+    return string;
+  }
+  return string.replace(badChars, escapeChar);
+}
+
+function isEmpty(value) {
+  if (!value && value !== 0) {
+    return true;
+  } else if (isArray(value) && value.length === 0) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function createFrame(object) {
+  var frame = extend({}, object);
+  frame._parent = object;
+  return frame;
+}
+
+function blockParams(params, ids) {
+  params.path = ids;
+  return params;
+}
+
+function appendContextPath(contextPath, id) {
+  return (contextPath ? contextPath + '.' : '') + id;
+}
+
+
+},{}],293:[function(require,module,exports){
+// Create a simple path alias to allow browserify to resolve
+// the runtime on a supported path.
+module.exports = require('./dist/cjs/handlebars.runtime')['default'];
+
+},{"./dist/cjs/handlebars.runtime":275}],294:[function(require,module,exports){
+/**
+ * @preserve
+ * Copyright 2015 Igor Bezkrovny
+ * All rights reserved. (MIT Licensed)
+ *
+ * ssim.ts - part of Image Quantization Library
+ */
+/**
+ * - Original TypeScript implementation:
+ *   https://github.com/igor-bezkrovny/image-quantization/blob/9f62764ac047c3e53accdf1d7e4e424b0ef2fb60/src/quality/ssim.ts
+ * - Based on Java implementation: https://github.com/rhys-e/structural-similarity
+ * - For more information see: http://en.wikipedia.org/wiki/Structural_similarity
+ */
+var ImageSSIM;
+(function (ImageSSIM) {
+    'use strict';
+    /**
+     * Grey = 1, GreyAlpha = 2, RGB = 3, RGBAlpha = 4
+     */
+    (function (Channels) {
+        Channels[Channels["Grey"] = 1] = "Grey";
+        Channels[Channels["GreyAlpha"] = 2] = "GreyAlpha";
+        Channels[Channels["RGB"] = 3] = "RGB";
+        Channels[Channels["RGBAlpha"] = 4] = "RGBAlpha";
+    })(ImageSSIM.Channels || (ImageSSIM.Channels = {}));
+    var Channels = ImageSSIM.Channels;
+    /**
+     * Entry point.
+     * @throws new Error('Images have different sizes!')
+     */
+    function compare(image1, image2, windowSize, K1, K2, luminance, bitsPerComponent) {
+        if (windowSize === void 0) { windowSize = 8; }
+        if (K1 === void 0) { K1 = 0.01; }
+        if (K2 === void 0) { K2 = 0.03; }
+        if (luminance === void 0) { luminance = true; }
+        if (bitsPerComponent === void 0) { bitsPerComponent = 8; }
+        if (image1.width !== image2.width || image1.height !== image2.height) {
+            throw new Error('Images have different sizes!');
+        }
+        /* tslint:disable:no-bitwise */
+        var L = (1 << bitsPerComponent) - 1;
+        /* tslint:enable:no-bitwise */
+        var c1 = Math.pow((K1 * L), 2), c2 = Math.pow((K2 * L), 2), numWindows = 0, mssim = 0.0;
+        var mcs = 0.0;
+        function iteration(lumaValues1, lumaValues2, averageLumaValue1, averageLumaValue2) {
+            // calculate variance and covariance
+            var sigxy, sigsqx, sigsqy;
+            sigxy = sigsqx = sigsqy = 0.0;
+            for (var i = 0; i < lumaValues1.length; i++) {
+                sigsqx += Math.pow((lumaValues1[i] - averageLumaValue1), 2);
+                sigsqy += Math.pow((lumaValues2[i] - averageLumaValue2), 2);
+                sigxy += (lumaValues1[i] - averageLumaValue1) * (lumaValues2[i] - averageLumaValue2);
+            }
+            var numPixelsInWin = lumaValues1.length - 1;
+            sigsqx /= numPixelsInWin;
+            sigsqy /= numPixelsInWin;
+            sigxy /= numPixelsInWin;
+            // perform ssim calculation on window
+            var numerator = (2 * averageLumaValue1 * averageLumaValue2 + c1) * (2 * sigxy + c2);
+            var denominator = (Math.pow(averageLumaValue1, 2) + Math.pow(averageLumaValue2, 2) + c1) * (sigsqx + sigsqy + c2);
+            mssim += numerator / denominator;
+            mcs += (2 * sigxy + c2) / (sigsqx + sigsqy + c2);
+            numWindows++;
+        }
+        // calculate SSIM for each window
+        Internals._iterate(image1, image2, windowSize, luminance, iteration);
+        return { ssim: mssim / numWindows, mcs: mcs / numWindows };
+    }
+    ImageSSIM.compare = compare;
+    /**
+     * Internal functions.
+     */
+    var Internals;
+    (function (Internals) {
+        function _iterate(image1, image2, windowSize, luminance, callback) {
+            var width = image1.width, height = image1.height;
+            for (var y = 0; y < height; y += windowSize) {
+                for (var x = 0; x < width; x += windowSize) {
+                    // avoid out-of-width/height
+                    var windowWidth = Math.min(windowSize, width - x), windowHeight = Math.min(windowSize, height - y);
+                    var lumaValues1 = _lumaValuesForWindow(image1, x, y, windowWidth, windowHeight, luminance), lumaValues2 = _lumaValuesForWindow(image2, x, y, windowWidth, windowHeight, luminance), averageLuma1 = _averageLuma(lumaValues1), averageLuma2 = _averageLuma(lumaValues2);
+                    callback(lumaValues1, lumaValues2, averageLuma1, averageLuma2);
+                }
+            }
+        }
+        Internals._iterate = _iterate;
+        function _lumaValuesForWindow(image, x, y, width, height, luminance) {
+            var array = image.data, lumaValues = new Float32Array(new ArrayBuffer(width * height * 4)), counter = 0;
+            var maxj = y + height;
+            for (var j = y; j < maxj; j++) {
+                var offset = j * image.width;
+                var i = (offset + x) * image.channels;
+                var maxi = (offset + x + width) * image.channels;
+                switch (image.channels) {
+                    case 1 /* Grey */:
+                        while (i < maxi) {
+                            // (0.212655 +  0.715158 + 0.072187) === 1
+                            lumaValues[counter++] = array[i++];
+                        }
+                        break;
+                    case 2 /* GreyAlpha */:
+                        while (i < maxi) {
+                            lumaValues[counter++] = array[i++] * (array[i++] / 255);
+                        }
+                        break;
+                    case 3 /* RGB */:
+                        if (luminance) {
+                            while (i < maxi) {
+                                lumaValues[counter++] = (array[i++] * 0.212655 + array[i++] * 0.715158 + array[i++] * 0.072187);
+                            }
+                        }
+                        else {
+                            while (i < maxi) {
+                                lumaValues[counter++] = (array[i++] + array[i++] + array[i++]);
+                            }
+                        }
+                        break;
+                    case 4 /* RGBAlpha */:
+                        if (luminance) {
+                            while (i < maxi) {
+                                lumaValues[counter++] = (array[i++] * 0.212655 + array[i++] * 0.715158 + array[i++] * 0.072187) * (array[i++] / 255);
+                            }
+                        }
+                        else {
+                            while (i < maxi) {
+                                lumaValues[counter++] = (array[i++] + array[i++] + array[i++]) * (array[i++] / 255);
+                            }
+                        }
+                        break;
+                }
+            }
+            return lumaValues;
+        }
+        function _averageLuma(lumaValues) {
+            var sumLuma = 0.0;
+            for (var i = 0; i < lumaValues.length; i++) {
+                sumLuma += lumaValues[i];
+            }
+            return sumLuma / lumaValues.length;
+        }
+    })(Internals || (Internals = {}));
+})(ImageSSIM || (ImageSSIM = {}));
+module.exports = ImageSSIM;
+
+},{}],295:[function(require,module,exports){
+var encode = require('./lib/encoder'),
+    decode = require('./lib/decoder');
+
+module.exports = {
+  encode: encode,
+  decode: decode
+};
+
+},{"./lib/decoder":296,"./lib/encoder":297}],296:[function(require,module,exports){
+(function (Buffer){
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/*
+   Copyright 2011 notmasteryet
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+// - The JPEG specification can be found in the ITU CCITT Recommendation T.81
+//   (www.w3.org/Graphics/JPEG/itu-t81.pdf)
+// - The JFIF specification can be found in the JPEG File Interchange Format
+//   (www.w3.org/Graphics/JPEG/jfif3.pdf)
+// - The Adobe Application-Specific JPEG markers in the Supporting the DCT Filters
+//   in PostScript Level 2, Technical Note #5116
+//   (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf)
+
+var JpegImage = (function jpegImage() {
+  "use strict";
+  var dctZigZag = new Int32Array([
+     0,
+     1,  8,
+    16,  9,  2,
+     3, 10, 17, 24,
+    32, 25, 18, 11, 4,
+     5, 12, 19, 26, 33, 40,
+    48, 41, 34, 27, 20, 13,  6,
+     7, 14, 21, 28, 35, 42, 49, 56,
+    57, 50, 43, 36, 29, 22, 15,
+    23, 30, 37, 44, 51, 58,
+    59, 52, 45, 38, 31,
+    39, 46, 53, 60,
+    61, 54, 47,
+    55, 62,
+    63
+  ]);
+
+  var dctCos1  =  4017   // cos(pi/16)
+  var dctSin1  =   799   // sin(pi/16)
+  var dctCos3  =  3406   // cos(3*pi/16)
+  var dctSin3  =  2276   // sin(3*pi/16)
+  var dctCos6  =  1567   // cos(6*pi/16)
+  var dctSin6  =  3784   // sin(6*pi/16)
+  var dctSqrt2 =  5793   // sqrt(2)
+  var dctSqrt1d2 = 2896  // sqrt(2) / 2
+
+  function constructor() {
+  }
+
+  function buildHuffmanTable(codeLengths, values) {
+    var k = 0, code = [], i, j, length = 16;
+    while (length > 0 && !codeLengths[length - 1])
+      length--;
+    code.push({children: [], index: 0});
+    var p = code[0], q;
+    for (i = 0; i < length; i++) {
+      for (j = 0; j < codeLengths[i]; j++) {
+        p = code.pop();
+        p.children[p.index] = values[k];
+        while (p.index > 0) {
+          p = code.pop();
+        }
+        p.index++;
+        code.push(p);
+        while (code.length <= i) {
+          code.push(q = {children: [], index: 0});
+          p.children[p.index] = q.children;
+          p = q;
+        }
+        k++;
+      }
+      if (i + 1 < length) {
+        // p here points to last code
+        code.push(q = {children: [], index: 0});
+        p.children[p.index] = q.children;
+        p = q;
+      }
+    }
+    return code[0].children;
+  }
+
+  function decodeScan(data, offset,
+                      frame, components, resetInterval,
+                      spectralStart, spectralEnd,
+                      successivePrev, successive) {
+    var precision = frame.precision;
+    var samplesPerLine = frame.samplesPerLine;
+    var scanLines = frame.scanLines;
+    var mcusPerLine = frame.mcusPerLine;
+    var progressive = frame.progressive;
+    var maxH = frame.maxH, maxV = frame.maxV;
+
+    var startOffset = offset, bitsData = 0, bitsCount = 0;
+    function readBit() {
+      if (bitsCount > 0) {
+        bitsCount--;
+        return (bitsData >> bitsCount) & 1;
+      }
+      bitsData = data[offset++];
+      if (bitsData == 0xFF) {
+        var nextByte = data[offset++];
+        if (nextByte) {
+          throw "unexpected marker: " + ((bitsData << 8) | nextByte).toString(16);
+        }
+        // unstuff 0
+      }
+      bitsCount = 7;
+      return bitsData >>> 7;
+    }
+    function decodeHuffman(tree) {
+      var node = tree, bit;
+      while ((bit = readBit()) !== null) {
+        node = node[bit];
+        if (typeof node === 'number')
+          return node;
+        if (typeof node !== 'object')
+          throw "invalid huffman sequence";
+      }
+      return null;
+    }
+    function receive(length) {
+      var n = 0;
+      while (length > 0) {
+        var bit = readBit();
+        if (bit === null) return;
+        n = (n << 1) | bit;
+        length--;
+      }
+      return n;
+    }
+    function receiveAndExtend(length) {
+      var n = receive(length);
+      if (n >= 1 << (length - 1))
+        return n;
+      return n + (-1 << length) + 1;
+    }
+    function decodeBaseline(component, zz) {
+      var t = decodeHuffman(component.huffmanTableDC);
+      var diff = t === 0 ? 0 : receiveAndExtend(t);
+      zz[0]= (component.pred += diff);
+      var k = 1;
+      while (k < 64) {
+        var rs = decodeHuffman(component.huffmanTableAC);
+        var s = rs & 15, r = rs >> 4;
+        if (s === 0) {
+          if (r < 15)
+            break;
+          k += 16;
+          continue;
+        }
+        k += r;
+        var z = dctZigZag[k];
+        zz[z] = receiveAndExtend(s);
+        k++;
+      }
+    }
+    function decodeDCFirst(component, zz) {
+      var t = decodeHuffman(component.huffmanTableDC);
+      var diff = t === 0 ? 0 : (receiveAndExtend(t) << successive);
+      zz[0] = (component.pred += diff);
+    }
+    function decodeDCSuccessive(component, zz) {
+      zz[0] |= readBit() << successive;
+    }
+    var eobrun = 0;
+    function decodeACFirst(component, zz) {
+      if (eobrun > 0) {
+        eobrun--;
+        return;
+      }
+      var k = spectralStart, e = spectralEnd;
+      while (k <= e) {
+        var rs = decodeHuffman(component.huffmanTableAC);
+        var s = rs & 15, r = rs >> 4;
+        if (s === 0) {
+          if (r < 15) {
+            eobrun = receive(r) + (1 << r) - 1;
+            break;
+          }
+          k += 16;
+          continue;
+        }
+        k += r;
+        var z = dctZigZag[k];
+        zz[z] = receiveAndExtend(s) * (1 << successive);
+        k++;
+      }
+    }
+    var successiveACState = 0, successiveACNextValue;
+    function decodeACSuccessive(component, zz) {
+      var k = spectralStart, e = spectralEnd, r = 0;
+      while (k <= e) {
+        var z = dctZigZag[k];
+        switch (successiveACState) {
+        case 0: // initial state
+          var rs = decodeHuffman(component.huffmanTableAC);
+          var s = rs & 15, r = rs >> 4;
+          if (s === 0) {
+            if (r < 15) {
+              eobrun = receive(r) + (1 << r);
+              successiveACState = 4;
+            } else {
+              r = 16;
+              successiveACState = 1;
+            }
+          } else {
+            if (s !== 1)
+              throw "invalid ACn encoding";
+            successiveACNextValue = receiveAndExtend(s);
+            successiveACState = r ? 2 : 3;
+          }
+          continue;
+        case 1: // skipping r zero items
+        case 2:
+          if (zz[z])
+            zz[z] += (readBit() << successive);
+          else {
+            r--;
+            if (r === 0)
+              successiveACState = successiveACState == 2 ? 3 : 0;
+          }
+          break;
+        case 3: // set value for a zero item
+          if (zz[z])
+            zz[z] += (readBit() << successive);
+          else {
+            zz[z] = successiveACNextValue << successive;
+            successiveACState = 0;
+          }
+          break;
+        case 4: // eob
+          if (zz[z])
+            zz[z] += (readBit() << successive);
+          break;
+        }
+        k++;
+      }
+      if (successiveACState === 4) {
+        eobrun--;
+        if (eobrun === 0)
+          successiveACState = 0;
+      }
+    }
+    function decodeMcu(component, decode, mcu, row, col) {
+      var mcuRow = (mcu / mcusPerLine) | 0;
+      var mcuCol = mcu % mcusPerLine;
+      var blockRow = mcuRow * component.v + row;
+      var blockCol = mcuCol * component.h + col;
+      decode(component, component.blocks[blockRow][blockCol]);
+    }
+    function decodeBlock(component, decode, mcu) {
+      var blockRow = (mcu / component.blocksPerLine) | 0;
+      var blockCol = mcu % component.blocksPerLine;
+      decode(component, component.blocks[blockRow][blockCol]);
+    }
+
+    var componentsLength = components.length;
+    var component, i, j, k, n;
+    var decodeFn;
+    if (progressive) {
+      if (spectralStart === 0)
+        decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive;
+      else
+        decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive;
+    } else {
+      decodeFn = decodeBaseline;
+    }
+
+    var mcu = 0, marker;
+    var mcuExpected;
+    if (componentsLength == 1) {
+      mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn;
+    } else {
+      mcuExpected = mcusPerLine * frame.mcusPerColumn;
+    }
+    if (!resetInterval) resetInterval = mcuExpected;
+
+    var h, v;
+    while (mcu < mcuExpected) {
+      // reset interval stuff
+      for (i = 0; i < componentsLength; i++)
+        components[i].pred = 0;
+      eobrun = 0;
+
+      if (componentsLength == 1) {
+        component = components[0];
+        for (n = 0; n < resetInterval; n++) {
+          decodeBlock(component, decodeFn, mcu);
+          mcu++;
+        }
+      } else {
+        for (n = 0; n < resetInterval; n++) {
+          for (i = 0; i < componentsLength; i++) {
+            component = components[i];
+            h = component.h;
+            v = component.v;
+            for (j = 0; j < v; j++) {
+              for (k = 0; k < h; k++) {
+                decodeMcu(component, decodeFn, mcu, j, k);
+              }
+            }
+          }
+          mcu++;
+
+          // If we've reached our expected MCU's, stop decoding
+          if (mcu === mcuExpected) break;
+        }
+      }
+
+      // find marker
+      bitsCount = 0;
+      marker = (data[offset] << 8) | data[offset + 1];
+      if (marker < 0xFF00) {
+        throw "marker was not found";
+      }
+
+      if (marker >= 0xFFD0 && marker <= 0xFFD7) { // RSTx
+        offset += 2;
+      }
+      else
+        break;
+    }
+
+    return offset - startOffset;
+  }
+
+  function buildComponentData(frame, component) {
+    var lines = [];
+    var blocksPerLine = component.blocksPerLine;
+    var blocksPerColumn = component.blocksPerColumn;
+    var samplesPerLine = blocksPerLine << 3;
+    var R = new Int32Array(64), r = new Uint8Array(64);
+
+    // A port of poppler's IDCT method which in turn is taken from:
+    //   Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz,
+    //   "Practical Fast 1-D DCT Algorithms with 11 Multiplications",
+    //   IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989,
+    //   988-991.
+    function quantizeAndInverse(zz, dataOut, dataIn) {
+      var qt = component.quantizationTable;
+      var v0, v1, v2, v3, v4, v5, v6, v7, t;
+      var p = dataIn;
+      var i;
+
+      // dequant
+      for (i = 0; i < 64; i++)
+        p[i] = zz[i] * qt[i];
+
+      // inverse DCT on rows
+      for (i = 0; i < 8; ++i) {
+        var row = 8 * i;
+
+        // check for all-zero AC coefficients
+        if (p[1 + row] == 0 && p[2 + row] == 0 && p[3 + row] == 0 &&
+            p[4 + row] == 0 && p[5 + row] == 0 && p[6 + row] == 0 &&
+            p[7 + row] == 0) {
+          t = (dctSqrt2 * p[0 + row] + 512) >> 10;
+          p[0 + row] = t;
+          p[1 + row] = t;
+          p[2 + row] = t;
+          p[3 + row] = t;
+          p[4 + row] = t;
+          p[5 + row] = t;
+          p[6 + row] = t;
+          p[7 + row] = t;
+          continue;
+        }
+
+        // stage 4
+        v0 = (dctSqrt2 * p[0 + row] + 128) >> 8;
+        v1 = (dctSqrt2 * p[4 + row] + 128) >> 8;
+        v2 = p[2 + row];
+        v3 = p[6 + row];
+        v4 = (dctSqrt1d2 * (p[1 + row] - p[7 + row]) + 128) >> 8;
+        v7 = (dctSqrt1d2 * (p[1 + row] + p[7 + row]) + 128) >> 8;
+        v5 = p[3 + row] << 4;
+        v6 = p[5 + row] << 4;
+
+        // stage 3
+        t = (v0 - v1+ 1) >> 1;
+        v0 = (v0 + v1 + 1) >> 1;
+        v1 = t;
+        t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8;
+        v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8;
+        v3 = t;
+        t = (v4 - v6 + 1) >> 1;
+        v4 = (v4 + v6 + 1) >> 1;
+        v6 = t;
+        t = (v7 + v5 + 1) >> 1;
+        v5 = (v7 - v5 + 1) >> 1;
+        v7 = t;
+
+        // stage 2
+        t = (v0 - v3 + 1) >> 1;
+        v0 = (v0 + v3 + 1) >> 1;
+        v3 = t;
+        t = (v1 - v2 + 1) >> 1;
+        v1 = (v1 + v2 + 1) >> 1;
+        v2 = t;
+        t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
+        v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
+        v7 = t;
+        t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12;
+        v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12;
+        v6 = t;
+
+        // stage 1
+        p[0 + row] = v0 + v7;
+        p[7 + row] = v0 - v7;
+        p[1 + row] = v1 + v6;
+        p[6 + row] = v1 - v6;
+        p[2 + row] = v2 + v5;
+        p[5 + row] = v2 - v5;
+        p[3 + row] = v3 + v4;
+        p[4 + row] = v3 - v4;
+      }
+
+      // inverse DCT on columns
+      for (i = 0; i < 8; ++i) {
+        var col = i;
+
+        // check for all-zero AC coefficients
+        if (p[1*8 + col] == 0 && p[2*8 + col] == 0 && p[3*8 + col] == 0 &&
+            p[4*8 + col] == 0 && p[5*8 + col] == 0 && p[6*8 + col] == 0 &&
+            p[7*8 + col] == 0) {
+          t = (dctSqrt2 * dataIn[i+0] + 8192) >> 14;
+          p[0*8 + col] = t;
+          p[1*8 + col] = t;
+          p[2*8 + col] = t;
+          p[3*8 + col] = t;
+          p[4*8 + col] = t;
+          p[5*8 + col] = t;
+          p[6*8 + col] = t;
+          p[7*8 + col] = t;
+          continue;
+        }
+
+        // stage 4
+        v0 = (dctSqrt2 * p[0*8 + col] + 2048) >> 12;
+        v1 = (dctSqrt2 * p[4*8 + col] + 2048) >> 12;
+        v2 = p[2*8 + col];
+        v3 = p[6*8 + col];
+        v4 = (dctSqrt1d2 * (p[1*8 + col] - p[7*8 + col]) + 2048) >> 12;
+        v7 = (dctSqrt1d2 * (p[1*8 + col] + p[7*8 + col]) + 2048) >> 12;
+        v5 = p[3*8 + col];
+        v6 = p[5*8 + col];
+
+        // stage 3
+        t = (v0 - v1 + 1) >> 1;
+        v0 = (v0 + v1 + 1) >> 1;
+        v1 = t;
+        t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12;
+        v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12;
+        v3 = t;
+        t = (v4 - v6 + 1) >> 1;
+        v4 = (v4 + v6 + 1) >> 1;
+        v6 = t;
+        t = (v7 + v5 + 1) >> 1;
+        v5 = (v7 - v5 + 1) >> 1;
+        v7 = t;
+
+        // stage 2
+        t = (v0 - v3 + 1) >> 1;
+        v0 = (v0 + v3 + 1) >> 1;
+        v3 = t;
+        t = (v1 - v2 + 1) >> 1;
+        v1 = (v1 + v2 + 1) >> 1;
+        v2 = t;
+        t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
+        v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
+        v7 = t;
+        t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12;
+        v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12;
+        v6 = t;
+
+        // stage 1
+        p[0*8 + col] = v0 + v7;
+        p[7*8 + col] = v0 - v7;
+        p[1*8 + col] = v1 + v6;
+        p[6*8 + col] = v1 - v6;
+        p[2*8 + col] = v2 + v5;
+        p[5*8 + col] = v2 - v5;
+        p[3*8 + col] = v3 + v4;
+        p[4*8 + col] = v3 - v4;
+      }
+
+      // convert to 8-bit integers
+      for (i = 0; i < 64; ++i) {
+        var sample = 128 + ((p[i] + 8) >> 4);
+        dataOut[i] = sample < 0 ? 0 : sample > 0xFF ? 0xFF : sample;
+      }
+    }
+
+    var i, j;
+    for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) {
+      var scanLine = blockRow << 3;
+      for (i = 0; i < 8; i++)
+        lines.push(new Uint8Array(samplesPerLine));
+      for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) {
+        quantizeAndInverse(component.blocks[blockRow][blockCol], r, R);
+
+        var offset = 0, sample = blockCol << 3;
+        for (j = 0; j < 8; j++) {
+          var line = lines[scanLine + j];
+          for (i = 0; i < 8; i++)
+            line[sample + i] = r[offset++];
+        }
+      }
+    }
+    return lines;
+  }
+
+  function clampTo8bit(a) {
+    return a < 0 ? 0 : a > 255 ? 255 : a;
+  }
+
+  constructor.prototype = {
+    load: function load(path) {
+      var xhr = new XMLHttpRequest();
+      xhr.open("GET", path, true);
+      xhr.responseType = "arraybuffer";
+      xhr.onload = (function() {
+        // TODO catch parse error
+        var data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer);
+        this.parse(data);
+        if (this.onload)
+          this.onload();
+      }).bind(this);
+      xhr.send(null);
+    },
+    parse: function parse(data) {
+      var offset = 0, length = data.length;
+      function readUint16() {
+        var value = (data[offset] << 8) | data[offset + 1];
+        offset += 2;
+        return value;
+      }
+      function readDataBlock() {
+        var length = readUint16();
+        var array = data.subarray(offset, offset + length - 2);
+        offset += array.length;
+        return array;
+      }
+      function prepareComponents(frame) {
+        var maxH = 0, maxV = 0;
+        var component, componentId;
+        for (componentId in frame.components) {
+          if (frame.components.hasOwnProperty(componentId)) {
+            component = frame.components[componentId];
+            if (maxH < component.h) maxH = component.h;
+            if (maxV < component.v) maxV = component.v;
+          }
+        }
+        var mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / maxH);
+        var mcusPerColumn = Math.ceil(frame.scanLines / 8 / maxV);
+        for (componentId in frame.components) {
+          if (frame.components.hasOwnProperty(componentId)) {
+            component = frame.components[componentId];
+            var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * component.h / maxH);
+            var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines  / 8) * component.v / maxV);
+            var blocksPerLineForMcu = mcusPerLine * component.h;
+            var blocksPerColumnForMcu = mcusPerColumn * component.v;
+            var blocks = [];
+            for (var i = 0; i < blocksPerColumnForMcu; i++) {
+              var row = [];
+              for (var j = 0; j < blocksPerLineForMcu; j++)
+                row.push(new Int32Array(64));
+              blocks.push(row);
+            }
+            component.blocksPerLine = blocksPerLine;
+            component.blocksPerColumn = blocksPerColumn;
+            component.blocks = blocks;
+          }
+        }
+        frame.maxH = maxH;
+        frame.maxV = maxV;
+        frame.mcusPerLine = mcusPerLine;
+        frame.mcusPerColumn = mcusPerColumn;
+      }
+      var jfif = null;
+      var adobe = null;
+      var pixels = null;
+      var frame, resetInterval;
+      var quantizationTables = [], frames = [];
+      var huffmanTablesAC = [], huffmanTablesDC = [];
+      var fileMarker = readUint16();
+      if (fileMarker != 0xFFD8) { // SOI (Start of Image)
+        throw "SOI not found";
+      }
+
+      fileMarker = readUint16();
+      while (fileMarker != 0xFFD9) { // EOI (End of image)
+        var i, j, l;
+        switch(fileMarker) {
+          case 0xFF00: break;
+          case 0xFFE0: // APP0 (Application Specific)
+          case 0xFFE1: // APP1
+          case 0xFFE2: // APP2
+          case 0xFFE3: // APP3
+          case 0xFFE4: // APP4
+          case 0xFFE5: // APP5
+          case 0xFFE6: // APP6
+          case 0xFFE7: // APP7
+          case 0xFFE8: // APP8
+          case 0xFFE9: // APP9
+          case 0xFFEA: // APP10
+          case 0xFFEB: // APP11
+          case 0xFFEC: // APP12
+          case 0xFFED: // APP13
+          case 0xFFEE: // APP14
+          case 0xFFEF: // APP15
+          case 0xFFFE: // COM (Comment)
+            var appData = readDataBlock();
+
+            if (fileMarker === 0xFFE0) {
+              if (appData[0] === 0x4A && appData[1] === 0x46 && appData[2] === 0x49 &&
+                appData[3] === 0x46 && appData[4] === 0) { // 'JFIF\x00'
+                jfif = {
+                  version: { major: appData[5], minor: appData[6] },
+                  densityUnits: appData[7],
+                  xDensity: (appData[8] << 8) | appData[9],
+                  yDensity: (appData[10] << 8) | appData[11],
+                  thumbWidth: appData[12],
+                  thumbHeight: appData[13],
+                  thumbData: appData.subarray(14, 14 + 3 * appData[12] * appData[13])
+                };
+              }
+            }
+            // TODO APP1 - Exif
+            if (fileMarker === 0xFFEE) {
+              if (appData[0] === 0x41 && appData[1] === 0x64 && appData[2] === 0x6F &&
+                appData[3] === 0x62 && appData[4] === 0x65 && appData[5] === 0) { // 'Adobe\x00'
+                adobe = {
+                  version: appData[6],
+                  flags0: (appData[7] << 8) | appData[8],
+                  flags1: (appData[9] << 8) | appData[10],
+                  transformCode: appData[11]
+                };
+              }
+            }
+            break;
+
+          case 0xFFDB: // DQT (Define Quantization Tables)
+            var quantizationTablesLength = readUint16();
+            var quantizationTablesEnd = quantizationTablesLength + offset - 2;
+            while (offset < quantizationTablesEnd) {
+              var quantizationTableSpec = data[offset++];
+              var tableData = new Int32Array(64);
+              if ((quantizationTableSpec >> 4) === 0) { // 8 bit values
+                for (j = 0; j < 64; j++) {
+                  var z = dctZigZag[j];
+                  tableData[z] = data[offset++];
+                }
+              } else if ((quantizationTableSpec >> 4) === 1) { //16 bit
+                for (j = 0; j < 64; j++) {
+                  var z = dctZigZag[j];
+                  tableData[z] = readUint16();
+                }
+              } else
+                throw "DQT: invalid table spec";
+              quantizationTables[quantizationTableSpec & 15] = tableData;
+            }
+            break;
+
+          case 0xFFC0: // SOF0 (Start of Frame, Baseline DCT)
+          case 0xFFC1: // SOF1 (Start of Frame, Extended DCT)
+          case 0xFFC2: // SOF2 (Start of Frame, Progressive DCT)
+            readUint16(); // skip data length
+            frame = {};
+            frame.extended = (fileMarker === 0xFFC1);
+            frame.progressive = (fileMarker === 0xFFC2);
+            frame.precision = data[offset++];
+            frame.scanLines = readUint16();
+            frame.samplesPerLine = readUint16();
+            frame.components = {};
+            frame.componentsOrder = [];
+            var componentsCount = data[offset++], componentId;
+            var maxH = 0, maxV = 0;
+            for (i = 0; i < componentsCount; i++) {
+              componentId = data[offset];
+              var h = data[offset + 1] >> 4;
+              var v = data[offset + 1] & 15;
+              var qId = data[offset + 2];
+              frame.componentsOrder.push(componentId);
+              frame.components[componentId] = {
+                h: h,
+                v: v,
+                quantizationIdx: qId
+              };
+              offset += 3;
+            }
+            prepareComponents(frame);
+            frames.push(frame);
+            break;
+
+          case 0xFFC4: // DHT (Define Huffman Tables)
+            var huffmanLength = readUint16();
+            for (i = 2; i < huffmanLength;) {
+              var huffmanTableSpec = data[offset++];
+              var codeLengths = new Uint8Array(16);
+              var codeLengthSum = 0;
+              for (j = 0; j < 16; j++, offset++)
+                codeLengthSum += (codeLengths[j] = data[offset]);
+              var huffmanValues = new Uint8Array(codeLengthSum);
+              for (j = 0; j < codeLengthSum; j++, offset++)
+                huffmanValues[j] = data[offset];
+              i += 17 + codeLengthSum;
+
+              ((huffmanTableSpec >> 4) === 0 ? 
+                huffmanTablesDC : huffmanTablesAC)[huffmanTableSpec & 15] =
+                buildHuffmanTable(codeLengths, huffmanValues);
+            }
+            break;
+
+          case 0xFFDD: // DRI (Define Restart Interval)
+            readUint16(); // skip data length
+            resetInterval = readUint16();
+            break;
+
+          case 0xFFDA: // SOS (Start of Scan)
+            var scanLength = readUint16();
+            var selectorsCount = data[offset++];
+            var components = [], component;
+            for (i = 0; i < selectorsCount; i++) {
+              component = frame.components[data[offset++]];
+              var tableSpec = data[offset++];
+              component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4];
+              component.huffmanTableAC = huffmanTablesAC[tableSpec & 15];
+              components.push(component);
+            }
+            var spectralStart = data[offset++];
+            var spectralEnd = data[offset++];
+            var successiveApproximation = data[offset++];
+            var processed = decodeScan(data, offset,
+              frame, components, resetInterval,
+              spectralStart, spectralEnd,
+              successiveApproximation >> 4, successiveApproximation & 15);
+            offset += processed;
+            break;
+          default:
+            if (data[offset - 3] == 0xFF &&
+                data[offset - 2] >= 0xC0 && data[offset - 2] <= 0xFE) {
+              // could be incorrect encoding -- last 0xFF byte of the previous
+              // block was eaten by the encoder
+              offset -= 3;
+              break;
+            }
+            throw "unknown JPEG marker " + fileMarker.toString(16);
+        }
+        fileMarker = readUint16();
+      }
+      if (frames.length != 1)
+        throw "only single frame JPEGs supported";
+
+      // set each frame's components quantization table
+      for (var i = 0; i < frames.length; i++) {
+        var cp = frames[i].components;
+        for (var j in cp) {
+          cp[j].quantizationTable = quantizationTables[cp[j].quantizationIdx];
+          delete cp[j].quantizationIdx;
+        }
+      }
+
+      this.width = frame.samplesPerLine;
+      this.height = frame.scanLines;
+      this.jfif = jfif;
+      this.adobe = adobe;
+      this.components = [];
+      for (var i = 0; i < frame.componentsOrder.length; i++) {
+        var component = frame.components[frame.componentsOrder[i]];
+        this.components.push({
+          lines: buildComponentData(frame, component),
+          scaleX: component.h / frame.maxH,
+          scaleY: component.v / frame.maxV
+        });
+      }
+    },
+    getData: function getData(width, height) {
+      var scaleX = this.width / width, scaleY = this.height / height;
+
+      var component1, component2, component3, component4;
+      var component1Line, component2Line, component3Line, component4Line;
+      var x, y;
+      var offset = 0;
+      var Y, Cb, Cr, K, C, M, Ye, R, G, B;
+      var colorTransform;
+      var dataLength = width * height * this.components.length;
+      var data = new Uint8Array(dataLength);
+      switch (this.components.length) {
+        case 1:
+          component1 = this.components[0];
+          for (y = 0; y < height; y++) {
+            component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)];
+            for (x = 0; x < width; x++) {
+              Y = component1Line[0 | (x * component1.scaleX * scaleX)];
+
+              data[offset++] = Y;
+            }
+          }
+          break;
+        case 2:
+          // PDF might compress two component data in custom colorspace
+          component1 = this.components[0];
+          component2 = this.components[1];
+          for (y = 0; y < height; y++) {
+            component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)];
+            component2Line = component2.lines[0 | (y * component2.scaleY * scaleY)];
+            for (x = 0; x < width; x++) {
+              Y = component1Line[0 | (x * component1.scaleX * scaleX)];
+              data[offset++] = Y;
+              Y = component2Line[0 | (x * component2.scaleX * scaleX)];
+              data[offset++] = Y;
+            }
+          }
+          break;
+        case 3:
+          // The default transform for three components is true
+          colorTransform = true;
+          // The adobe transform marker overrides any previous setting
+          if (this.adobe && this.adobe.transformCode)
+            colorTransform = true;
+          else if (typeof this.colorTransform !== 'undefined')
+            colorTransform = !!this.colorTransform;
+
+          component1 = this.components[0];
+          component2 = this.components[1];
+          component3 = this.components[2];
+          for (y = 0; y < height; y++) {
+            component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)];
+            component2Line = component2.lines[0 | (y * component2.scaleY * scaleY)];
+            component3Line = component3.lines[0 | (y * component3.scaleY * scaleY)];
+            for (x = 0; x < width; x++) {
+              if (!colorTransform) {
+                R = component1Line[0 | (x * component1.scaleX * scaleX)];
+                G = component2Line[0 | (x * component2.scaleX * scaleX)];
+                B = component3Line[0 | (x * component3.scaleX * scaleX)];
+              } else {
+                Y = component1Line[0 | (x * component1.scaleX * scaleX)];
+                Cb = component2Line[0 | (x * component2.scaleX * scaleX)];
+                Cr = component3Line[0 | (x * component3.scaleX * scaleX)];
+
+                R = clampTo8bit(Y + 1.402 * (Cr - 128));
+                G = clampTo8bit(Y - 0.3441363 * (Cb - 128) - 0.71413636 * (Cr - 128));
+                B = clampTo8bit(Y + 1.772 * (Cb - 128));
+              }
+
+              data[offset++] = R;
+              data[offset++] = G;
+              data[offset++] = B;
+            }
+          }
+          break;
+        case 4:
+          if (!this.adobe)
+            throw 'Unsupported color mode (4 components)';
+          // The default transform for four components is false
+          colorTransform = false;
+          // The adobe transform marker overrides any previous setting
+          if (this.adobe && this.adobe.transformCode)
+            colorTransform = true;
+          else if (typeof this.colorTransform !== 'undefined')
+            colorTransform = !!this.colorTransform;
+
+          component1 = this.components[0];
+          component2 = this.components[1];
+          component3 = this.components[2];
+          component4 = this.components[3];
+          for (y = 0; y < height; y++) {
+            component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)];
+            component2Line = component2.lines[0 | (y * component2.scaleY * scaleY)];
+            component3Line = component3.lines[0 | (y * component3.scaleY * scaleY)];
+            component4Line = component4.lines[0 | (y * component4.scaleY * scaleY)];
+            for (x = 0; x < width; x++) {
+              if (!colorTransform) {
+                C = component1Line[0 | (x * component1.scaleX * scaleX)];
+                M = component2Line[0 | (x * component2.scaleX * scaleX)];
+                Ye = component3Line[0 | (x * component3.scaleX * scaleX)];
+                K = component4Line[0 | (x * component4.scaleX * scaleX)];
+              } else {
+                Y = component1Line[0 | (x * component1.scaleX * scaleX)];
+                Cb = component2Line[0 | (x * component2.scaleX * scaleX)];
+                Cr = component3Line[0 | (x * component3.scaleX * scaleX)];
+                K = component4Line[0 | (x * component4.scaleX * scaleX)];
+
+                C = 255 - clampTo8bit(Y + 1.402 * (Cr - 128));
+                M = 255 - clampTo8bit(Y - 0.3441363 * (Cb - 128) - 0.71413636 * (Cr - 128));
+                Ye = 255 - clampTo8bit(Y + 1.772 * (Cb - 128));
+              }
+              data[offset++] = 255-C;
+              data[offset++] = 255-M;
+              data[offset++] = 255-Ye;
+              data[offset++] = 255-K;
+            }
+          }
+          break;
+        default:
+          throw 'Unsupported color mode';
+      }
+      return data;
+    },
+    copyToImageData: function copyToImageData(imageData) {
+      var width = imageData.width, height = imageData.height;
+      var imageDataArray = imageData.data;
+      var data = this.getData(width, height);
+      var i = 0, j = 0, x, y;
+      var Y, K, C, M, R, G, B;
+      switch (this.components.length) {
+        case 1:
+          for (y = 0; y < height; y++) {
+            for (x = 0; x < width; x++) {
+              Y = data[i++];
+
+              imageDataArray[j++] = Y;
+              imageDataArray[j++] = Y;
+              imageDataArray[j++] = Y;
+              imageDataArray[j++] = 255;
+            }
+          }
+          break;
+        case 3:
+          for (y = 0; y < height; y++) {
+            for (x = 0; x < width; x++) {
+              R = data[i++];
+              G = data[i++];
+              B = data[i++];
+
+              imageDataArray[j++] = R;
+              imageDataArray[j++] = G;
+              imageDataArray[j++] = B;
+              imageDataArray[j++] = 255;
+            }
+          }
+          break;
+        case 4:
+          for (y = 0; y < height; y++) {
+            for (x = 0; x < width; x++) {
+              C = data[i++];
+              M = data[i++];
+              Y = data[i++];
+              K = data[i++];
+
+              R = 255 - clampTo8bit(C * (1 - K / 255) + K);
+              G = 255 - clampTo8bit(M * (1 - K / 255) + K);
+              B = 255 - clampTo8bit(Y * (1 - K / 255) + K);
+
+              imageDataArray[j++] = R;
+              imageDataArray[j++] = G;
+              imageDataArray[j++] = B;
+              imageDataArray[j++] = 255;
+            }
+          }
+          break;
+        default:
+          throw 'Unsupported color mode';
+      }
+    }
+  };
+
+  return constructor;
+})();
+module.exports = decode;
+
+function decode(jpegData) {
+  var arr = new Uint8Array(jpegData);
+  var decoder = new JpegImage();
+  decoder.parse(arr);
+
+  var image = {
+    width: decoder.width,
+    height: decoder.height,
+    data: new Buffer(decoder.width * decoder.height * 4)
+  };
+  
+  decoder.copyToImageData(image);
+  
+  return image;
+}
+
+}).call(this,require("buffer").Buffer)
+},{"buffer":205}],297:[function(require,module,exports){
+(function (Buffer){
+/*
+  Copyright (c) 2008, Adobe Systems Incorporated
+  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without 
+  modification, are permitted provided that the following conditions are
+  met:
+
+  * Redistributions of source code must retain the above copyright notice, 
+    this list of conditions and the following disclaimer.
+  
+  * Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in the 
+    documentation and/or other materials provided with the distribution.
+  
+  * Neither the name of Adobe Systems Incorporated nor the names of its 
+    contributors may be used to endorse or promote products derived from 
+    this software without specific prior written permission.
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
+  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+/*
+JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009
+
+Basic GUI blocking jpeg encoder
+*/
+
+var btoa = btoa || function(buf) {
+  return new Buffer(buf).toString('base64');
+};
+
+function JPEGEncoder(quality) {
+  var self = this;
+	var fround = Math.round;
+	var ffloor = Math.floor;
+	var YTable = new Array(64);
+	var UVTable = new Array(64);
+	var fdtbl_Y = new Array(64);
+	var fdtbl_UV = new Array(64);
+	var YDC_HT;
+	var UVDC_HT;
+	var YAC_HT;
+	var UVAC_HT;
+	
+	var bitcode = new Array(65535);
+	var category = new Array(65535);
+	var outputfDCTQuant = new Array(64);
+	var DU = new Array(64);
+	var byteout = [];
+	var bytenew = 0;
+	var bytepos = 7;
+	
+	var YDU = new Array(64);
+	var UDU = new Array(64);
+	var VDU = new Array(64);
+	var clt = new Array(256);
+	var RGB_YUV_TABLE = new Array(2048);
+	var currentQuality;
+	
+	var ZigZag = [
+			 0, 1, 5, 6,14,15,27,28,
+			 2, 4, 7,13,16,26,29,42,
+			 3, 8,12,17,25,30,41,43,
+			 9,11,18,24,31,40,44,53,
+			10,19,23,32,39,45,52,54,
+			20,22,33,38,46,51,55,60,
+			21,34,37,47,50,56,59,61,
+			35,36,48,49,57,58,62,63
+		];
+	
+	var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];
+	var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11];
+	var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];
+	var std_ac_luminance_values = [
+			0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,
+			0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,
+			0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,
+			0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,
+			0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,
+			0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,
+			0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,
+			0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,
+			0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,
+			0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,
+			0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,
+			0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
+			0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,
+			0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,
+			0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,
+			0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,
+			0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,
+			0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,
+			0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,
+			0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
+			0xf9,0xfa
+		];
+	
+	var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];
+	var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11];
+	var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];
+	var std_ac_chrominance_values = [
+			0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,
+			0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,
+			0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,
+			0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,
+			0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,
+			0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,
+			0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,
+			0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,
+			0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,
+			0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,
+			0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,
+			0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,
+			0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,
+			0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,
+			0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,
+			0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,
+			0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,
+			0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,
+			0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,
+			0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
+			0xf9,0xfa
+		];
+	
+	function initQuantTables(sf){
+			var YQT = [
+				16, 11, 10, 16, 24, 40, 51, 61,
+				12, 12, 14, 19, 26, 58, 60, 55,
+				14, 13, 16, 24, 40, 57, 69, 56,
+				14, 17, 22, 29, 51, 87, 80, 62,
+				18, 22, 37, 56, 68,109,103, 77,
+				24, 35, 55, 64, 81,104,113, 92,
+				49, 64, 78, 87,103,121,120,101,
+				72, 92, 95, 98,112,100,103, 99
+			];
+			
+			for (var i = 0; i < 64; i++) {
+				var t = ffloor((YQT[i]*sf+50)/100);
+				if (t < 1) {
+					t = 1;
+				} else if (t > 255) {
+					t = 255;
+				}
+				YTable[ZigZag[i]] = t;
+			}
+			var UVQT = [
+				17, 18, 24, 47, 99, 99, 99, 99,
+				18, 21, 26, 66, 99, 99, 99, 99,
+				24, 26, 56, 99, 99, 99, 99, 99,
+				47, 66, 99, 99, 99, 99, 99, 99,
+				99, 99, 99, 99, 99, 99, 99, 99,
+				99, 99, 99, 99, 99, 99, 99, 99,
+				99, 99, 99, 99, 99, 99, 99, 99,
+				99, 99, 99, 99, 99, 99, 99, 99
+			];
+			for (var j = 0; j < 64; j++) {
+				var u = ffloor((UVQT[j]*sf+50)/100);
+				if (u < 1) {
+					u = 1;
+				} else if (u > 255) {
+					u = 255;
+				}
+				UVTable[ZigZag[j]] = u;
+			}
+			var aasf = [
+				1.0, 1.387039845, 1.306562965, 1.175875602,
+				1.0, 0.785694958, 0.541196100, 0.275899379
+			];
+			var k = 0;
+			for (var row = 0; row < 8; row++)
+			{
+				for (var col = 0; col < 8; col++)
+				{
+					fdtbl_Y[k]  = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
+					fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
+					k++;
+				}
+			}
+		}
+		
+		function computeHuffmanTbl(nrcodes, std_table){
+			var codevalue = 0;
+			var pos_in_table = 0;
+			var HT = new Array();
+			for (var k = 1; k <= 16; k++) {
+				for (var j = 1; j <= nrcodes[k]; j++) {
+					HT[std_table[pos_in_table]] = [];
+					HT[std_table[pos_in_table]][0] = codevalue;
+					HT[std_table[pos_in_table]][1] = k;
+					pos_in_table++;
+					codevalue++;
+				}
+				codevalue*=2;
+			}
+			return HT;
+		}
+		
+		function initHuffmanTbl()
+		{
+			YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values);
+			UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values);
+			YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values);
+			UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values);
+		}
+	
+		function initCategoryNumber()
+		{
+			var nrlower = 1;
+			var nrupper = 2;
+			for (var cat = 1; cat <= 15; cat++) {
+				//Positive numbers
+				for (var nr = nrlower; nr<nrupper; nr++) {
+					category[32767+nr] = cat;
+					bitcode[32767+nr] = [];
+					bitcode[32767+nr][1] = cat;
+					bitcode[32767+nr][0] = nr;
+				}
+				//Negative numbers
+				for (var nrneg =-(nrupper-1); nrneg<=-nrlower; nrneg++) {
+					category[32767+nrneg] = cat;
+					bitcode[32767+nrneg] = [];
+					bitcode[32767+nrneg][1] = cat;
+					bitcode[32767+nrneg][0] = nrupper-1+nrneg;
+				}
+				nrlower <<= 1;
+				nrupper <<= 1;
+			}
+		}
+		
+		function initRGBYUVTable() {
+			for(var i = 0; i < 256;i++) {
+				RGB_YUV_TABLE[i]      		=  19595 * i;
+				RGB_YUV_TABLE[(i+ 256)>>0] 	=  38470 * i;
+				RGB_YUV_TABLE[(i+ 512)>>0] 	=   7471 * i + 0x8000;
+				RGB_YUV_TABLE[(i+ 768)>>0] 	= -11059 * i;
+				RGB_YUV_TABLE[(i+1024)>>0] 	= -21709 * i;
+				RGB_YUV_TABLE[(i+1280)>>0] 	=  32768 * i + 0x807FFF;
+				RGB_YUV_TABLE[(i+1536)>>0] 	= -27439 * i;
+				RGB_YUV_TABLE[(i+1792)>>0] 	= - 5329 * i;
+			}
+		}
+		
+		// IO functions
+		function writeBits(bs)
+		{
+			var value = bs[0];
+			var posval = bs[1]-1;
+			while ( posval >= 0 ) {
+				if (value & (1 << posval) ) {
+					bytenew |= (1 << bytepos);
+				}
+				posval--;
+				bytepos--;
+				if (bytepos < 0) {
+					if (bytenew == 0xFF) {
+						writeByte(0xFF);
+						writeByte(0);
+					}
+					else {
+						writeByte(bytenew);
+					}
+					bytepos=7;
+					bytenew=0;
+				}
+			}
+		}
+	
+		function writeByte(value)
+		{
+			//byteout.push(clt[value]); // write char directly instead of converting later
+      byteout.push(value);
+		}
+	
+		function writeWord(value)
+		{
+			writeByte((value>>8)&0xFF);
+			writeByte((value   )&0xFF);
+		}
+		
+		// DCT & quantization core
+		function fDCTQuant(data, fdtbl)
+		{
+			var d0, d1, d2, d3, d4, d5, d6, d7;
+			/* Pass 1: process rows. */
+			var dataOff=0;
+			var i;
+			const I8 = 8;
+			const I64 = 64;
+			for (i=0; i<I8; ++i)
+			{
+				d0 = data[dataOff];
+				d1 = data[dataOff+1];
+				d2 = data[dataOff+2];
+				d3 = data[dataOff+3];
+				d4 = data[dataOff+4];
+				d5 = data[dataOff+5];
+				d6 = data[dataOff+6];
+				d7 = data[dataOff+7];
+				
+				var tmp0 = d0 + d7;
+				var tmp7 = d0 - d7;
+				var tmp1 = d1 + d6;
+				var tmp6 = d1 - d6;
+				var tmp2 = d2 + d5;
+				var tmp5 = d2 - d5;
+				var tmp3 = d3 + d4;
+				var tmp4 = d3 - d4;
+	
+				/* Even part */
+				var tmp10 = tmp0 + tmp3;	/* phase 2 */
+				var tmp13 = tmp0 - tmp3;
+				var tmp11 = tmp1 + tmp2;
+				var tmp12 = tmp1 - tmp2;
+	
+				data[dataOff] = tmp10 + tmp11; /* phase 3 */
+				data[dataOff+4] = tmp10 - tmp11;
+	
+				var z1 = (tmp12 + tmp13) * 0.707106781; /* c4 */
+				data[dataOff+2] = tmp13 + z1; /* phase 5 */
+				data[dataOff+6] = tmp13 - z1;
+	
+				/* Odd part */
+				tmp10 = tmp4 + tmp5; /* phase 2 */
+				tmp11 = tmp5 + tmp6;
+				tmp12 = tmp6 + tmp7;
+	
+				/* The rotator is modified from fig 4-8 to avoid extra negations. */
+				var z5 = (tmp10 - tmp12) * 0.382683433; /* c6 */
+				var z2 = 0.541196100 * tmp10 + z5; /* c2-c6 */
+				var z4 = 1.306562965 * tmp12 + z5; /* c2+c6 */
+				var z3 = tmp11 * 0.707106781; /* c4 */
+	
+				var z11 = tmp7 + z3;	/* phase 5 */
+				var z13 = tmp7 - z3;
+	
+				data[dataOff+5] = z13 + z2;	/* phase 6 */
+				data[dataOff+3] = z13 - z2;
+				data[dataOff+1] = z11 + z4;
+				data[dataOff+7] = z11 - z4;
+	
+				dataOff += 8; /* advance pointer to next row */
+			}
+	
+			/* Pass 2: process columns. */
+			dataOff = 0;
+			for (i=0; i<I8; ++i)
+			{
+				d0 = data[dataOff];
+				d1 = data[dataOff + 8];
+				d2 = data[dataOff + 16];
+				d3 = data[dataOff + 24];
+				d4 = data[dataOff + 32];
+				d5 = data[dataOff + 40];
+				d6 = data[dataOff + 48];
+				d7 = data[dataOff + 56];
+				
+				var tmp0p2 = d0 + d7;
+				var tmp7p2 = d0 - d7;
+				var tmp1p2 = d1 + d6;
+				var tmp6p2 = d1 - d6;
+				var tmp2p2 = d2 + d5;
+				var tmp5p2 = d2 - d5;
+				var tmp3p2 = d3 + d4;
+				var tmp4p2 = d3 - d4;
+	
+				/* Even part */
+				var tmp10p2 = tmp0p2 + tmp3p2;	/* phase 2 */
+				var tmp13p2 = tmp0p2 - tmp3p2;
+				var tmp11p2 = tmp1p2 + tmp2p2;
+				var tmp12p2 = tmp1p2 - tmp2p2;
+	
+				data[dataOff] = tmp10p2 + tmp11p2; /* phase 3 */
+				data[dataOff+32] = tmp10p2 - tmp11p2;
+	
+				var z1p2 = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */
+				data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */
+				data[dataOff+48] = tmp13p2 - z1p2;
+	
+				/* Odd part */
+				tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */
+				tmp11p2 = tmp5p2 + tmp6p2;
+				tmp12p2 = tmp6p2 + tmp7p2;
+	
+				/* The rotator is modified from fig 4-8 to avoid extra negations. */
+				var z5p2 = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */
+				var z2p2 = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */
+				var z4p2 = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */
+				var z3p2 = tmp11p2 * 0.707106781; /* c4 */
+	
+				var z11p2 = tmp7p2 + z3p2;	/* phase 5 */
+				var z13p2 = tmp7p2 - z3p2;
+	
+				data[dataOff+40] = z13p2 + z2p2; /* phase 6 */
+				data[dataOff+24] = z13p2 - z2p2;
+				data[dataOff+ 8] = z11p2 + z4p2;
+				data[dataOff+56] = z11p2 - z4p2;
+	
+				dataOff++; /* advance pointer to next column */
+			}
+	
+			// Quantize/descale the coefficients
+			var fDCTQuant;
+			for (i=0; i<I64; ++i)
+			{
+				// Apply the quantization and scaling factor & Round to nearest integer
+				fDCTQuant = data[i]*fdtbl[i];
+				outputfDCTQuant[i] = (fDCTQuant > 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0);
+				//outputfDCTQuant[i] = fround(fDCTQuant);
+
+			}
+			return outputfDCTQuant;
+		}
+		
+		function writeAPP0()
+		{
+			writeWord(0xFFE0); // marker
+			writeWord(16); // length
+			writeByte(0x4A); // J
+			writeByte(0x46); // F
+			writeByte(0x49); // I
+			writeByte(0x46); // F
+			writeByte(0); // = "JFIF",'\0'
+			writeByte(1); // versionhi
+			writeByte(1); // versionlo
+			writeByte(0); // xyunits
+			writeWord(1); // xdensity
+			writeWord(1); // ydensity
+			writeByte(0); // thumbnwidth
+			writeByte(0); // thumbnheight
+		}
+	
+		function writeSOF0(width, height)
+		{
+			writeWord(0xFFC0); // marker
+			writeWord(17);   // length, truecolor YUV JPG
+			writeByte(8);    // precision
+			writeWord(height);
+			writeWord(width);
+			writeByte(3);    // nrofcomponents
+			writeByte(1);    // IdY
+			writeByte(0x11); // HVY
+			writeByte(0);    // QTY
+			writeByte(2);    // IdU
+			writeByte(0x11); // HVU
+			writeByte(1);    // QTU
+			writeByte(3);    // IdV
+			writeByte(0x11); // HVV
+			writeByte(1);    // QTV
+		}
+	
+		function writeDQT()
+		{
+			writeWord(0xFFDB); // marker
+			writeWord(132);	   // length
+			writeByte(0);
+			for (var i=0; i<64; i++) {
+				writeByte(YTable[i]);
+			}
+			writeByte(1);
+			for (var j=0; j<64; j++) {
+				writeByte(UVTable[j]);
+			}
+		}
+	
+		function writeDHT()
+		{
+			writeWord(0xFFC4); // marker
+			writeWord(0x01A2); // length
+	
+			writeByte(0); // HTYDCinfo
+			for (var i=0; i<16; i++) {
+				writeByte(std_dc_luminance_nrcodes[i+1]);
+			}
+			for (var j=0; j<=11; j++) {
+				writeByte(std_dc_luminance_values[j]);
+			}
+	
+			writeByte(0x10); // HTYACinfo
+			for (var k=0; k<16; k++) {
+				writeByte(std_ac_luminance_nrcodes[k+1]);
+			}
+			for (var l=0; l<=161; l++) {
+				writeByte(std_ac_luminance_values[l]);
+			}
+	
+			writeByte(1); // HTUDCinfo
+			for (var m=0; m<16; m++) {
+				writeByte(std_dc_chrominance_nrcodes[m+1]);
+			}
+			for (var n=0; n<=11; n++) {
+				writeByte(std_dc_chrominance_values[n]);
+			}
+	
+			writeByte(0x11); // HTUACinfo
+			for (var o=0; o<16; o++) {
+				writeByte(std_ac_chrominance_nrcodes[o+1]);
+			}
+			for (var p=0; p<=161; p++) {
+				writeByte(std_ac_chrominance_values[p]);
+			}
+		}
+	
+		function writeSOS()
+		{
+			writeWord(0xFFDA); // marker
+			writeWord(12); // length
+			writeByte(3); // nrofcomponents
+			writeByte(1); // IdY
+			writeByte(0); // HTY
+			writeByte(2); // IdU
+			writeByte(0x11); // HTU
+			writeByte(3); // IdV
+			writeByte(0x11); // HTV
+			writeByte(0); // Ss
+			writeByte(0x3f); // Se
+			writeByte(0); // Bf
+		}
+		
+		function processDU(CDU, fdtbl, DC, HTDC, HTAC){
+			var EOB = HTAC[0x00];
+			var M16zeroes = HTAC[0xF0];
+			var pos;
+			const I16 = 16;
+			const I63 = 63;
+			const I64 = 64;
+			var DU_DCT = fDCTQuant(CDU, fdtbl);
+			//ZigZag reorder
+			for (var j=0;j<I64;++j) {
+				DU[ZigZag[j]]=DU_DCT[j];
+			}
+			var Diff = DU[0] - DC; DC = DU[0];
+			//Encode DC
+			if (Diff==0) {
+				writeBits(HTDC[0]); // Diff might be 0
+			} else {
+				pos = 32767+Diff;
+				writeBits(HTDC[category[pos]]);
+				writeBits(bitcode[pos]);
+			}
+			//Encode ACs
+			var end0pos = 63; // was const... which is crazy
+			for (; (end0pos>0)&&(DU[end0pos]==0); end0pos--) {};
+			//end0pos = first element in reverse order !=0
+			if ( end0pos == 0) {
+				writeBits(EOB);
+				return DC;
+			}
+			var i = 1;
+			var lng;
+			while ( i <= end0pos ) {
+				var startpos = i;
+				for (; (DU[i]==0) && (i<=end0pos); ++i) {}
+				var nrzeroes = i-startpos;
+				if ( nrzeroes >= I16 ) {
+					lng = nrzeroes>>4;
+					for (var nrmarker=1; nrmarker <= lng; ++nrmarker)
+						writeBits(M16zeroes);
+					nrzeroes = nrzeroes&0xF;
+				}
+				pos = 32767+DU[i];
+				writeBits(HTAC[(nrzeroes<<4)+category[pos]]);
+				writeBits(bitcode[pos]);
+				i++;
+			}
+			if ( end0pos != I63 ) {
+				writeBits(EOB);
+			}
+			return DC;
+		}
+
+		function initCharLookupTable(){
+			var sfcc = String.fromCharCode;
+			for(var i=0; i < 256; i++){ ///// ACHTUNG // 255
+				clt[i] = sfcc(i);
+			}
+		}
+		
+		this.encode = function(image,quality) // image data object
+		{
+			var time_start = new Date().getTime();
+			
+			if(quality) setQuality(quality);
+			
+			// Initialize bit writer
+			byteout = new Array();
+			bytenew=0;
+			bytepos=7;
+	
+			// Add JPEG headers
+			writeWord(0xFFD8); // SOI
+			writeAPP0();
+			writeDQT();
+			writeSOF0(image.width,image.height);
+			writeDHT();
+			writeSOS();
+
+	
+			// Encode 8x8 macroblocks
+			var DCY=0;
+			var DCU=0;
+			var DCV=0;
+			
+			bytenew=0;
+			bytepos=7;
+			
+			
+			this.encode.displayName = "_encode_";
+
+			var imageData = image.data;
+			var width = image.width;
+			var height = image.height;
+
+			var quadWidth = width*4;
+			var tripleWidth = width*3;
+			
+			var x, y = 0;
+			var r, g, b;
+			var start,p, col,row,pos;
+			while(y < height){
+				x = 0;
+				while(x < quadWidth){
+				start = quadWidth * y + x;
+				p = start;
+				col = -1;
+				row = 0;
+				
+				for(pos=0; pos < 64; pos++){
+					row = pos >> 3;// /8
+					col = ( pos & 7 ) * 4; // %8
+					p = start + ( row * quadWidth ) + col;		
+					
+					if(y+row >= height){ // padding bottom
+						p-= (quadWidth*(y+1+row-height));
+					}
+
+					if(x+col >= quadWidth){ // padding right	
+						p-= ((x+col) - quadWidth +4)
+					}
+					
+					r = imageData[ p++ ];
+					g = imageData[ p++ ];
+					b = imageData[ p++ ];
+					
+					
+					/* // calculate YUV values dynamically
+					YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80
+					UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b));
+					VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b));
+					*/
+					
+					// use lookup table (slightly faster)
+					YDU[pos] = ((RGB_YUV_TABLE[r]             + RGB_YUV_TABLE[(g +  256)>>0] + RGB_YUV_TABLE[(b +  512)>>0]) >> 16)-128;
+					UDU[pos] = ((RGB_YUV_TABLE[(r +  768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128;
+					VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128;
+
+				}
+				
+				DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
+				DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
+				DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
+				x+=32;
+				}
+				y+=8;
+			}
+			
+			
+			////////////////////////////////////////////////////////////////
+	
+			// Do the bit alignment of the EOI marker
+			if ( bytepos >= 0 ) {
+				var fillbits = [];
+				fillbits[1] = bytepos+1;
+				fillbits[0] = (1<<(bytepos+1))-1;
+				writeBits(fillbits);
+			}
+	
+			writeWord(0xFFD9); //EOI
+
+      //return new Uint8Array(byteout);
+      return new Buffer(byteout);
+
+			var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join(''));
+			
+			byteout = [];
+			
+			// benchmarking
+			var duration = new Date().getTime() - time_start;
+    		//console.log('Encoding time: '+ duration + 'ms');
+    		//
+			
+			return jpegDataUri			
+	}
+	
+	function setQuality(quality){
+		if (quality <= 0) {
+			quality = 1;
+		}
+		if (quality > 100) {
+			quality = 100;
+		}
+		
+		if(currentQuality == quality) return // don't recalc if unchanged
+		
+		var sf = 0;
+		if (quality < 50) {
+			sf = Math.floor(5000 / quality);
+		} else {
+			sf = Math.floor(200 - quality*2);
+		}
+		
+		initQuantTables(sf);
+		currentQuality = quality;
+		//console.log('Quality set to: '+quality +'%');
+	}
+	
+	function init(){
+		var time_start = new Date().getTime();
+		if(!quality) quality = 50;
+		// Create tables
+		initCharLookupTable()
+		initHuffmanTbl();
+		initCategoryNumber();
+		initRGBYUVTable();
+		
+		setQuality(quality);
+		var duration = new Date().getTime() - time_start;
+    	//console.log('Initialization '+ duration + 'ms');
+	}
+	
+	init();
+	
+};
+module.exports = encode;
+
+function encode(imgData, qu) {
+  if (typeof qu === 'undefined') qu = 50;
+  var encoder = new JPEGEncoder(qu);
+	var data = encoder.encode(imgData, qu);
+  return {
+    data: data,
+    width: imgData.width,
+    height: imgData.height
+  };
+}
+
+// helper function to get the imageData of an existing image on the current page.
+function getImageDataFromImage(idOrElement){
+	var theImg = (typeof(idOrElement)=='string')? document.getElementById(idOrElement):idOrElement;
+	var cvs = document.createElement('canvas');
+	cvs.width = theImg.width;
+	cvs.height = theImg.height;
+	var ctx = cvs.getContext("2d");
+	ctx.drawImage(theImg,0,0);
+	
+	return (ctx.getImageData(0, 0, cvs.width, cvs.height));
+}
+
+}).call(this,require("buffer").Buffer)
+},{"buffer":205}],298:[function(require,module,exports){
+(function (global){
+/**
+ * marked - a markdown parser
+ * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
+ * https://github.com/chjj/marked
+ */
+
+;(function() {
+
+/**
+ * Block-Level Grammar
+ */
+
+var block = {
+  newline: /^\n+/,
+  code: /^( {4}[^\n]+\n*)+/,
+  fences: noop,
+  hr: /^( *[-*_]){3,} *(?:\n+|$)/,
+  heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
+  nptable: noop,
+  lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
+  blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,
+  list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
+  html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,
+  def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
+  table: noop,
+  paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
+  text: /^[^\n]+/
+};
+
+block.bullet = /(?:[*+-]|\d+\.)/;
+block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
+block.item = replace(block.item, 'gm')
+  (/bull/g, block.bullet)
+  ();
+
+block.list = replace(block.list)
+  (/bull/g, block.bullet)
+  ('hr', '\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))')
+  ('def', '\\n+(?=' + block.def.source + ')')
+  ();
+
+block.blockquote = replace(block.blockquote)
+  ('def', block.def)
+  ();
+
+block._tag = '(?!(?:'
+  + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'
+  + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'
+  + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b';
+
+block.html = replace(block.html)
+  ('comment', /<!--[\s\S]*?-->/)
+  ('closed', /<(tag)[\s\S]+?<\/\1>/)
+  ('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)
+  (/tag/g, block._tag)
+  ();
+
+block.paragraph = replace(block.paragraph)
+  ('hr', block.hr)
+  ('heading', block.heading)
+  ('lheading', block.lheading)
+  ('blockquote', block.blockquote)
+  ('tag', '<' + block._tag)
+  ('def', block.def)
+  ();
+
+/**
+ * Normal Block Grammar
+ */
+
+block.normal = merge({}, block);
+
+/**
+ * GFM Block Grammar
+ */
+
+block.gfm = merge({}, block.normal, {
+  fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,
+  paragraph: /^/,
+  heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/
+});
+
+block.gfm.paragraph = replace(block.paragraph)
+  ('(?!', '(?!'
+    + block.gfm.fences.source.replace('\\1', '\\2') + '|'
+    + block.list.source.replace('\\1', '\\3') + '|')
+  ();
+
+/**
+ * GFM + Tables Block Grammar
+ */
+
+block.tables = merge({}, block.gfm, {
+  nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
+  table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
+});
+
+/**
+ * Block Lexer
+ */
+
+function Lexer(options) {
+  this.tokens = [];
+  this.tokens.links = {};
+  this.options = options || marked.defaults;
+  this.rules = block.normal;
+
+  if (this.options.gfm) {
+    if (this.options.tables) {
+      this.rules = block.tables;
+    } else {
+      this.rules = block.gfm;
+    }
+  }
+}
+
+/**
+ * Expose Block Rules
+ */
+
+Lexer.rules = block;
+
+/**
+ * Static Lex Method
+ */
+
+Lexer.lex = function(src, options) {
+  var lexer = new Lexer(options);
+  return lexer.lex(src);
+};
+
+/**
+ * Preprocessing
+ */
+
+Lexer.prototype.lex = function(src) {
+  src = src
+    .replace(/\r\n|\r/g, '\n')
+    .replace(/\t/g, '    ')
+    .replace(/\u00a0/g, ' ')
+    .replace(/\u2424/g, '\n');
+
+  return this.token(src, true);
+};
+
+/**
+ * Lexing
+ */
+
+Lexer.prototype.token = function(src, top, bq) {
+  var src = src.replace(/^ +$/gm, '')
+    , next
+    , loose
+    , cap
+    , bull
+    , b
+    , item
+    , space
+    , i
+    , l;
+
+  while (src) {
+    // newline
+    if (cap = this.rules.newline.exec(src)) {
+      src = src.substring(cap[0].length);
+      if (cap[0].length > 1) {
+        this.tokens.push({
+          type: 'space'
+        });
+      }
+    }
+
+    // code
+    if (cap = this.rules.code.exec(src)) {
+      src = src.substring(cap[0].length);
+      cap = cap[0].replace(/^ {4}/gm, '');
+      this.tokens.push({
+        type: 'code',
+        text: !this.options.pedantic
+          ? cap.replace(/\n+$/, '')
+          : cap
+      });
+      continue;
+    }
+
+    // fences (gfm)
+    if (cap = this.rules.fences.exec(src)) {
+      src = src.substring(cap[0].length);
+      this.tokens.push({
+        type: 'code',
+        lang: cap[2],
+        text: cap[3] || ''
+      });
+      continue;
+    }
+
+    // heading
+    if (cap = this.rules.heading.exec(src)) {
+      src = src.substring(cap[0].length);
+      this.tokens.push({
+        type: 'heading',
+        depth: cap[1].length,
+        text: cap[2]
+      });
+      continue;
+    }
+
+    // table no leading pipe (gfm)
+    if (top && (cap = this.rules.nptable.exec(src))) {
+      src = src.substring(cap[0].length);
+
+      item = {
+        type: 'table',
+        header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
+        align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
+        cells: cap[3].replace(/\n$/, '').split('\n')
+      };
+
+      for (i = 0; i < item.align.length; i++) {
+        if (/^ *-+: *$/.test(item.align[i])) {
+          item.align[i] = 'right';
+        } else if (/^ *:-+: *$/.test(item.align[i])) {
+          item.align[i] = 'center';
+        } else if (/^ *:-+ *$/.test(item.align[i])) {
+          item.align[i] = 'left';
+        } else {
+          item.align[i] = null;
+        }
+      }
+
+      for (i = 0; i < item.cells.length; i++) {
+        item.cells[i] = item.cells[i].split(/ *\| */);
+      }
+
+      this.tokens.push(item);
+
+      continue;
+    }
+
+    // lheading
+    if (cap = this.rules.lheading.exec(src)) {
+      src = src.substring(cap[0].length);
+      this.tokens.push({
+        type: 'heading',
+        depth: cap[2] === '=' ? 1 : 2,
+        text: cap[1]
+      });
+      continue;
+    }
+
+    // hr
+    if (cap = this.rules.hr.exec(src)) {
+      src = src.substring(cap[0].length);
+      this.tokens.push({
+        type: 'hr'
+      });
+      continue;
+    }
+
+    // blockquote
+    if (cap = this.rules.blockquote.exec(src)) {
+      src = src.substring(cap[0].length);
+
+      this.tokens.push({
+        type: 'blockquote_start'
+      });
+
+      cap = cap[0].replace(/^ *> ?/gm, '');
+
+      // Pass `top` to keep the current
+      // "toplevel" state. This is exactly
+      // how markdown.pl works.
+      this.token(cap, top, true);
+
+      this.tokens.push({
+        type: 'blockquote_end'
+      });
+
+      continue;
+    }
+
+    // list
+    if (cap = this.rules.list.exec(src)) {
+      src = src.substring(cap[0].length);
+      bull = cap[2];
+
+      this.tokens.push({
+        type: 'list_start',
+        ordered: bull.length > 1
+      });
+
+      // Get each top-level item.
+      cap = cap[0].match(this.rules.item);
+
+      next = false;
+      l = cap.length;
+      i = 0;
+
+      for (; i < l; i++) {
+        item = cap[i];
+
+        // Remove the list item's bullet
+        // so it is seen as the next token.
+        space = item.length;
+        item = item.replace(/^ *([*+-]|\d+\.) +/, '');
+
+        // Outdent whatever the
+        // list item contains. Hacky.
+        if (~item.indexOf('\n ')) {
+          space -= item.length;
+          item = !this.options.pedantic
+            ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
+            : item.replace(/^ {1,4}/gm, '');
+        }
+
+        // Determine whether the next list item belongs here.
+        // Backpedal if it does not belong in this list.
+        if (this.options.smartLists && i !== l - 1) {
+          b = block.bullet.exec(cap[i + 1])[0];
+          if (bull !== b && !(bull.length > 1 && b.length > 1)) {
+            src = cap.slice(i + 1).join('\n') + src;
+            i = l - 1;
+          }
+        }
+
+        // Determine whether item is loose or not.
+        // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
+        // for discount behavior.
+        loose = next || /\n\n(?!\s*$)/.test(item);
+        if (i !== l - 1) {
+          next = item.charAt(item.length - 1) === '\n';
+          if (!loose) loose = next;
+        }
+
+        this.tokens.push({
+          type: loose
+            ? 'loose_item_start'
+            : 'list_item_start'
+        });
+
+        // Recurse.
+        this.token(item, false, bq);
+
+        this.tokens.push({
+          type: 'list_item_end'
+        });
+      }
+
+      this.tokens.push({
+        type: 'list_end'
+      });
+
+      continue;
+    }
+
+    // html
+    if (cap = this.rules.html.exec(src)) {
+      src = src.substring(cap[0].length);
+      this.tokens.push({
+        type: this.options.sanitize
+          ? 'paragraph'
+          : 'html',
+        pre: !this.options.sanitizer
+          && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'),
+        text: cap[0]
+      });
+      continue;
+    }
+
+    // def
+    if ((!bq && top) && (cap = this.rules.def.exec(src))) {
+      src = src.substring(cap[0].length);
+      this.tokens.links[cap[1].toLowerCase()] = {
+        href: cap[2],
+        title: cap[3]
+      };
+      continue;
+    }
+
+    // table (gfm)
+    if (top && (cap = this.rules.table.exec(src))) {
+      src = src.substring(cap[0].length);
+
+      item = {
+        type: 'table',
+        header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
+        align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
+        cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n')
+      };
+
+      for (i = 0; i < item.align.length; i++) {
+        if (/^ *-+: *$/.test(item.align[i])) {
+          item.align[i] = 'right';
+        } else if (/^ *:-+: *$/.test(item.align[i])) {
+          item.align[i] = 'center';
+        } else if (/^ *:-+ *$/.test(item.align[i])) {
+          item.align[i] = 'left';
+        } else {
+          item.align[i] = null;
+        }
+      }
+
+      for (i = 0; i < item.cells.length; i++) {
+        item.cells[i] = item.cells[i]
+          .replace(/^ *\| *| *\| *$/g, '')
+          .split(/ *\| */);
+      }
+
+      this.tokens.push(item);
+
+      continue;
+    }
+
+    // top-level paragraph
+    if (top && (cap = this.rules.paragraph.exec(src))) {
+      src = src.substring(cap[0].length);
+      this.tokens.push({
+        type: 'paragraph',
+        text: cap[1].charAt(cap[1].length - 1) === '\n'
+          ? cap[1].slice(0, -1)
+          : cap[1]
+      });
+      continue;
+    }
+
+    // text
+    if (cap = this.rules.text.exec(src)) {
+      // Top-level should never reach here.
+      src = src.substring(cap[0].length);
+      this.tokens.push({
+        type: 'text',
+        text: cap[0]
+      });
+      continue;
+    }
+
+    if (src) {
+      throw new
+        Error('Infinite loop on byte: ' + src.charCodeAt(0));
+    }
+  }
+
+  return this.tokens;
+};
+
+/**
+ * Inline-Level Grammar
+ */
+
+var inline = {
+  escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
+  autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
+  url: noop,
+  tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
+  link: /^!?\[(inside)\]\(href\)/,
+  reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
+  nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
+  strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
+  em: /^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
+  code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,
+  br: /^ {2,}\n(?!\s*$)/,
+  del: noop,
+  text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/
+};
+
+inline._inside = /(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/;
+inline._href = /\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;
+
+inline.link = replace(inline.link)
+  ('inside', inline._inside)
+  ('href', inline._href)
+  ();
+
+inline.reflink = replace(inline.reflink)
+  ('inside', inline._inside)
+  ();
+
+/**
+ * Normal Inline Grammar
+ */
+
+inline.normal = merge({}, inline);
+
+/**
+ * Pedantic Inline Grammar
+ */
+
+inline.pedantic = merge({}, inline.normal, {
+  strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
+  em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/
+});
+
+/**
+ * GFM Inline Grammar
+ */
+
+inline.gfm = merge({}, inline.normal, {
+  escape: replace(inline.escape)('])', '~|])')(),
+  url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
+  del: /^~~(?=\S)([\s\S]*?\S)~~/,
+  text: replace(inline.text)
+    (']|', '~]|')
+    ('|', '|https?://|')
+    ()
+});
+
+/**
+ * GFM + Line Breaks Inline Grammar
+ */
+
+inline.breaks = merge({}, inline.gfm, {
+  br: replace(inline.br)('{2,}', '*')(),
+  text: replace(inline.gfm.text)('{2,}', '*')()
+});
+
+/**
+ * Inline Lexer & Compiler
+ */
+
+function InlineLexer(links, options) {
+  this.options = options || marked.defaults;
+  this.links = links;
+  this.rules = inline.normal;
+  this.renderer = this.options.renderer || new Renderer;
+  this.renderer.options = this.options;
+
+  if (!this.links) {
+    throw new
+      Error('Tokens array requires a `links` property.');
+  }
+
+  if (this.options.gfm) {
+    if (this.options.breaks) {
+      this.rules = inline.breaks;
+    } else {
+      this.rules = inline.gfm;
+    }
+  } else if (this.options.pedantic) {
+    this.rules = inline.pedantic;
+  }
+}
+
+/**
+ * Expose Inline Rules
+ */
+
+InlineLexer.rules = inline;
+
+/**
+ * Static Lexing/Compiling Method
+ */
+
+InlineLexer.output = function(src, links, options) {
+  var inline = new InlineLexer(links, options);
+  return inline.output(src);
+};
+
+/**
+ * Lexing/Compiling
+ */
+
+InlineLexer.prototype.output = function(src) {
+  var out = ''
+    , link
+    , text
+    , href
+    , cap;
+
+  while (src) {
+    // escape
+    if (cap = this.rules.escape.exec(src)) {
+      src = src.substring(cap[0].length);
+      out += cap[1];
+      continue;
+    }
+
+    // autolink
+    if (cap = this.rules.autolink.exec(src)) {
+      src = src.substring(cap[0].length);
+      if (cap[2] === '@') {
+        text = cap[1].charAt(6) === ':'
+          ? this.mangle(cap[1].substring(7))
+          : this.mangle(cap[1]);
+        href = this.mangle('mailto:') + text;
+      } else {
+        text = escape(cap[1]);
+        href = text;
+      }
+      out += this.renderer.link(href, null, text);
+      continue;
+    }
+
+    // url (gfm)
+    if (!this.inLink && (cap = this.rules.url.exec(src))) {
+      src = src.substring(cap[0].length);
+      text = escape(cap[1]);
+      href = text;
+      out += this.renderer.link(href, null, text);
+      continue;
+    }
+
+    // tag
+    if (cap = this.rules.tag.exec(src)) {
+      if (!this.inLink && /^<a /i.test(cap[0])) {
+        this.inLink = true;
+      } else if (this.inLink && /^<\/a>/i.test(cap[0])) {
+        this.inLink = false;
+      }
+      src = src.substring(cap[0].length);
+      out += this.options.sanitize
+        ? this.options.sanitizer
+          ? this.options.sanitizer(cap[0])
+          : escape(cap[0])
+        : cap[0]
+      continue;
+    }
+
+    // link
+    if (cap = this.rules.link.exec(src)) {
+      src = src.substring(cap[0].length);
+      this.inLink = true;
+      out += this.outputLink(cap, {
+        href: cap[2],
+        title: cap[3]
+      });
+      this.inLink = false;
+      continue;
+    }
+
+    // reflink, nolink
+    if ((cap = this.rules.reflink.exec(src))
+        || (cap = this.rules.nolink.exec(src))) {
+      src = src.substring(cap[0].length);
+      link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
+      link = this.links[link.toLowerCase()];
+      if (!link || !link.href) {
+        out += cap[0].charAt(0);
+        src = cap[0].substring(1) + src;
+        continue;
+      }
+      this.inLink = true;
+      out += this.outputLink(cap, link);
+      this.inLink = false;
+      continue;
+    }
+
+    // strong
+    if (cap = this.rules.strong.exec(src)) {
+      src = src.substring(cap[0].length);
+      out += this.renderer.strong(this.output(cap[2] || cap[1]));
+      continue;
+    }
+
+    // em
+    if (cap = this.rules.em.exec(src)) {
+      src = src.substring(cap[0].length);
+      out += this.renderer.em(this.output(cap[2] || cap[1]));
+      continue;
+    }
+
+    // code
+    if (cap = this.rules.code.exec(src)) {
+      src = src.substring(cap[0].length);
+      out += this.renderer.codespan(escape(cap[2], true));
+      continue;
+    }
+
+    // br
+    if (cap = this.rules.br.exec(src)) {
+      src = src.substring(cap[0].length);
+      out += this.renderer.br();
+      continue;
+    }
+
+    // del (gfm)
+    if (cap = this.rules.del.exec(src)) {
+      src = src.substring(cap[0].length);
+      out += this.renderer.del(this.output(cap[1]));
+      continue;
+    }
+
+    // text
+    if (cap = this.rules.text.exec(src)) {
+      src = src.substring(cap[0].length);
+      out += this.renderer.text(escape(this.smartypants(cap[0])));
+      continue;
+    }
+
+    if (src) {
+      throw new
+        Error('Infinite loop on byte: ' + src.charCodeAt(0));
+    }
+  }
+
+  return out;
+};
+
+/**
+ * Compile Link
+ */
+
+InlineLexer.prototype.outputLink = function(cap, link) {
+  var href = escape(link.href)
+    , title = link.title ? escape(link.title) : null;
+
+  return cap[0].charAt(0) !== '!'
+    ? this.renderer.link(href, title, this.output(cap[1]))
+    : this.renderer.image(href, title, escape(cap[1]));
+};
+
+/**
+ * Smartypants Transformations
+ */
+
+InlineLexer.prototype.smartypants = function(text) {
+  if (!this.options.smartypants) return text;
+  return text
+    // em-dashes
+    .replace(/---/g, '\u2014')
+    // en-dashes
+    .replace(/--/g, '\u2013')
+    // opening singles
+    .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
+    // closing singles & apostrophes
+    .replace(/'/g, '\u2019')
+    // opening doubles
+    .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
+    // closing doubles
+    .replace(/"/g, '\u201d')
+    // ellipses
+    .replace(/\.{3}/g, '\u2026');
+};
+
+/**
+ * Mangle Links
+ */
+
+InlineLexer.prototype.mangle = function(text) {
+  if (!this.options.mangle) return text;
+  var out = ''
+    , l = text.length
+    , i = 0
+    , ch;
+
+  for (; i < l; i++) {
+    ch = text.charCodeAt(i);
+    if (Math.random() > 0.5) {
+      ch = 'x' + ch.toString(16);
+    }
+    out += '&#' + ch + ';';
+  }
+
+  return out;
+};
+
+/**
+ * Renderer
+ */
+
+function Renderer(options) {
+  this.options = options || {};
+}
+
+Renderer.prototype.code = function(code, lang, escaped) {
+  if (this.options.highlight) {
+    var out = this.options.highlight(code, lang);
+    if (out != null && out !== code) {
+      escaped = true;
+      code = out;
+    }
+  }
+
+  if (!lang) {
+    return '<pre><code>'
+      + (escaped ? code : escape(code, true))
+      + '\n</code></pre>';
+  }
+
+  return '<pre><code class="'
+    + this.options.langPrefix
+    + escape(lang, true)
+    + '">'
+    + (escaped ? code : escape(code, true))
+    + '\n</code></pre>\n';
+};
+
+Renderer.prototype.blockquote = function(quote) {
+  return '<blockquote>\n' + quote + '</blockquote>\n';
+};
+
+Renderer.prototype.html = function(html) {
+  return html;
+};
+
+Renderer.prototype.heading = function(text, level, raw) {
+  return '<h'
+    + level
+    + ' id="'
+    + this.options.headerPrefix
+    + raw.toLowerCase().replace(/[^\w]+/g, '-')
+    + '">'
+    + text
+    + '</h'
+    + level
+    + '>\n';
+};
+
+Renderer.prototype.hr = function() {
+  return this.options.xhtml ? '<hr/>\n' : '<hr>\n';
+};
+
+Renderer.prototype.list = function(body, ordered) {
+  var type = ordered ? 'ol' : 'ul';
+  return '<' + type + '>\n' + body + '</' + type + '>\n';
+};
+
+Renderer.prototype.listitem = function(text) {
+  return '<li>' + text + '</li>\n';
+};
+
+Renderer.prototype.paragraph = function(text) {
+  return '<p>' + text + '</p>\n';
+};
+
+Renderer.prototype.table = function(header, body) {
+  return '<table>\n'
+    + '<thead>\n'
+    + header
+    + '</thead>\n'
+    + '<tbody>\n'
+    + body
+    + '</tbody>\n'
+    + '</table>\n';
+};
+
+Renderer.prototype.tablerow = function(content) {
+  return '<tr>\n' + content + '</tr>\n';
+};
+
+Renderer.prototype.tablecell = function(content, flags) {
+  var type = flags.header ? 'th' : 'td';
+  var tag = flags.align
+    ? '<' + type + ' style="text-align:' + flags.align + '">'
+    : '<' + type + '>';
+  return tag + content + '</' + type + '>\n';
+};
+
+// span level renderer
+Renderer.prototype.strong = function(text) {
+  return '<strong>' + text + '</strong>';
+};
+
+Renderer.prototype.em = function(text) {
+  return '<em>' + text + '</em>';
+};
+
+Renderer.prototype.codespan = function(text) {
+  return '<code>' + text + '</code>';
+};
+
+Renderer.prototype.br = function() {
+  return this.options.xhtml ? '<br/>' : '<br>';
+};
+
+Renderer.prototype.del = function(text) {
+  return '<del>' + text + '</del>';
+};
+
+Renderer.prototype.link = function(href, title, text) {
+  if (this.options.sanitize) {
+    try {
+      var prot = decodeURIComponent(unescape(href))
+        .replace(/[^\w:]/g, '')
+        .toLowerCase();
+    } catch (e) {
+      return '';
+    }
+    if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0) {
+      return '';
+    }
+  }
+  var out = '<a href="' + href + '"';
+  if (title) {
+    out += ' title="' + title + '"';
+  }
+  out += '>' + text + '</a>';
+  return out;
+};
+
+Renderer.prototype.image = function(href, title, text) {
+  var out = '<img src="' + href + '" alt="' + text + '"';
+  if (title) {
+    out += ' title="' + title + '"';
+  }
+  out += this.options.xhtml ? '/>' : '>';
+  return out;
+};
+
+Renderer.prototype.text = function(text) {
+  return text;
+};
+
+/**
+ * Parsing & Compiling
+ */
+
+function Parser(options) {
+  this.tokens = [];
+  this.token = null;
+  this.options = options || marked.defaults;
+  this.options.renderer = this.options.renderer || new Renderer;
+  this.renderer = this.options.renderer;
+  this.renderer.options = this.options;
+}
+
+/**
+ * Static Parse Method
+ */
+
+Parser.parse = function(src, options, renderer) {
+  var parser = new Parser(options, renderer);
+  return parser.parse(src);
+};
+
+/**
+ * Parse Loop
+ */
+
+Parser.prototype.parse = function(src) {
+  this.inline = new InlineLexer(src.links, this.options, this.renderer);
+  this.tokens = src.reverse();
+
+  var out = '';
+  while (this.next()) {
+    out += this.tok();
+  }
+
+  return out;
+};
+
+/**
+ * Next Token
+ */
+
+Parser.prototype.next = function() {
+  return this.token = this.tokens.pop();
+};
+
+/**
+ * Preview Next Token
+ */
+
+Parser.prototype.peek = function() {
+  return this.tokens[this.tokens.length - 1] || 0;
+};
+
+/**
+ * Parse Text Tokens
+ */
+
+Parser.prototype.parseText = function() {
+  var body = this.token.text;
+
+  while (this.peek().type === 'text') {
+    body += '\n' + this.next().text;
+  }
+
+  return this.inline.output(body);
+};
+
+/**
+ * Parse Current Token
+ */
+
+Parser.prototype.tok = function() {
+  switch (this.token.type) {
+    case 'space': {
+      return '';
+    }
+    case 'hr': {
+      return this.renderer.hr();
+    }
+    case 'heading': {
+      return this.renderer.heading(
+        this.inline.output(this.token.text),
+        this.token.depth,
+        this.token.text);
+    }
+    case 'code': {
+      return this.renderer.code(this.token.text,
+        this.token.lang,
+        this.token.escaped);
+    }
+    case 'table': {
+      var header = ''
+        , body = ''
+        , i
+        , row
+        , cell
+        , flags
+        , j;
+
+      // header
+      cell = '';
+      for (i = 0; i < this.token.header.length; i++) {
+        flags = { header: true, align: this.token.align[i] };
+        cell += this.renderer.tablecell(
+          this.inline.output(this.token.header[i]),
+          { header: true, align: this.token.align[i] }
+        );
+      }
+      header += this.renderer.tablerow(cell);
+
+      for (i = 0; i < this.token.cells.length; i++) {
+        row = this.token.cells[i];
+
+        cell = '';
+        for (j = 0; j < row.length; j++) {
+          cell += this.renderer.tablecell(
+            this.inline.output(row[j]),
+            { header: false, align: this.token.align[j] }
+          );
+        }
+
+        body += this.renderer.tablerow(cell);
+      }
+      return this.renderer.table(header, body);
+    }
+    case 'blockquote_start': {
+      var body = '';
+
+      while (this.next().type !== 'blockquote_end') {
+        body += this.tok();
+      }
+
+      return this.renderer.blockquote(body);
+    }
+    case 'list_start': {
+      var body = ''
+        , ordered = this.token.ordered;
+
+      while (this.next().type !== 'list_end') {
+        body += this.tok();
+      }
+
+      return this.renderer.list(body, ordered);
+    }
+    case 'list_item_start': {
+      var body = '';
+
+      while (this.next().type !== 'list_item_end') {
+        body += this.token.type === 'text'
+          ? this.parseText()
+          : this.tok();
+      }
+
+      return this.renderer.listitem(body);
+    }
+    case 'loose_item_start': {
+      var body = '';
+
+      while (this.next().type !== 'list_item_end') {
+        body += this.tok();
+      }
+
+      return this.renderer.listitem(body);
+    }
+    case 'html': {
+      var html = !this.token.pre && !this.options.pedantic
+        ? this.inline.output(this.token.text)
+        : this.token.text;
+      return this.renderer.html(html);
+    }
+    case 'paragraph': {
+      return this.renderer.paragraph(this.inline.output(this.token.text));
+    }
+    case 'text': {
+      return this.renderer.paragraph(this.parseText());
+    }
+  }
+};
+
+/**
+ * Helpers
+ */
+
+function escape(html, encode) {
+  return html
+    .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
+    .replace(/</g, '&lt;')
+    .replace(/>/g, '&gt;')
+    .replace(/"/g, '&quot;')
+    .replace(/'/g, '&#39;');
+}
+
+function unescape(html) {
+	// explicitly match decimal, hex, and named HTML entities 
+  return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/g, function(_, n) {
+    n = n.toLowerCase();
+    if (n === 'colon') return ':';
+    if (n.charAt(0) === '#') {
+      return n.charAt(1) === 'x'
+        ? String.fromCharCode(parseInt(n.substring(2), 16))
+        : String.fromCharCode(+n.substring(1));
+    }
+    return '';
+  });
+}
+
+function replace(regex, opt) {
+  regex = regex.source;
+  opt = opt || '';
+  return function self(name, val) {
+    if (!name) return new RegExp(regex, opt);
+    val = val.source || val;
+    val = val.replace(/(^|[^\[])\^/g, '$1');
+    regex = regex.replace(name, val);
+    return self;
+  };
+}
+
+function noop() {}
+noop.exec = noop;
+
+function merge(obj) {
+  var i = 1
+    , target
+    , key;
+
+  for (; i < arguments.length; i++) {
+    target = arguments[i];
+    for (key in target) {
+      if (Object.prototype.hasOwnProperty.call(target, key)) {
+        obj[key] = target[key];
+      }
+    }
+  }
+
+  return obj;
+}
+
+
+/**
+ * Marked
+ */
+
+function marked(src, opt, callback) {
+  if (callback || typeof opt === 'function') {
+    if (!callback) {
+      callback = opt;
+      opt = null;
+    }
+
+    opt = merge({}, marked.defaults, opt || {});
+
+    var highlight = opt.highlight
+      , tokens
+      , pending
+      , i = 0;
+
+    try {
+      tokens = Lexer.lex(src, opt)
+    } catch (e) {
+      return callback(e);
+    }
+
+    pending = tokens.length;
+
+    var done = function(err) {
+      if (err) {
+        opt.highlight = highlight;
+        return callback(err);
+      }
+
+      var out;
+
+      try {
+        out = Parser.parse(tokens, opt);
+      } catch (e) {
+        err = e;
+      }
+
+      opt.highlight = highlight;
+
+      return err
+        ? callback(err)
+        : callback(null, out);
+    };
+
+    if (!highlight || highlight.length < 3) {
+      return done();
+    }
+
+    delete opt.highlight;
+
+    if (!pending) return done();
+
+    for (; i < tokens.length; i++) {
+      (function(token) {
+        if (token.type !== 'code') {
+          return --pending || done();
+        }
+        return highlight(token.text, token.lang, function(err, code) {
+          if (err) return done(err);
+          if (code == null || code === token.text) {
+            return --pending || done();
+          }
+          token.text = code;
+          token.escaped = true;
+          --pending || done();
+        });
+      })(tokens[i]);
+    }
+
+    return;
+  }
+  try {
+    if (opt) opt = merge({}, marked.defaults, opt);
+    return Parser.parse(Lexer.lex(src, opt), opt);
+  } catch (e) {
+    e.message += '\nPlease report this to https://github.com/chjj/marked.';
+    if ((opt || marked.defaults).silent) {
+      return '<p>An error occured:</p><pre>'
+        + escape(e.message + '', true)
+        + '</pre>';
+    }
+    throw e;
+  }
+}
+
+/**
+ * Options
+ */
+
+marked.options =
+marked.setOptions = function(opt) {
+  merge(marked.defaults, opt);
+  return marked;
+};
+
+marked.defaults = {
+  gfm: true,
+  tables: true,
+  breaks: false,
+  pedantic: false,
+  sanitize: false,
+  sanitizer: null,
+  mangle: true,
+  smartLists: false,
+  silent: false,
+  highlight: null,
+  langPrefix: 'lang-',
+  smartypants: false,
+  headerPrefix: '',
+  renderer: new Renderer,
+  xhtml: false
+};
+
+/**
+ * Expose
+ */
+
+marked.Parser = Parser;
+marked.parser = Parser.parse;
+
+marked.Renderer = Renderer;
+
+marked.Lexer = Lexer;
+marked.lexer = Lexer.lex;
+
+marked.InlineLexer = InlineLexer;
+marked.inlineLexer = InlineLexer.output;
+
+marked.parse = marked;
+
+if (typeof module !== 'undefined' && typeof exports === 'object') {
+  module.exports = marked;
+} else if (typeof define === 'function' && define.amd) {
+  define(function() { return marked; });
+} else {
+  this.marked = marked;
+}
+
+}).call(function() {
+  return this || (typeof window !== 'undefined' ? window : global);
+}());
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],299:[function(require,module,exports){
+exports.getRenderingDataFromViewport = function (viewportProperties, uaDeviceWidth, uaDeviceHeight, uaMaxZoom, uaMinZoom) {
+
+    var vw = uaDeviceWidth / 100;
+    var vh = uaDeviceHeight / 100;
+
+    // Following http://dev.w3.org/csswg/css-device-adapt/#translation-into-atviewport-descriptors
+    // 'auto' is mapped to null by convention
+    var maxZoom = null;
+    var minZoom = null;
+    var zoom = null;
+    var minWidth = null;
+    var minHeight = null;
+    var maxWidth = null;
+    var maxHeight = null;
+    var width = null, height = null;
+    var initialWidth = uaDeviceWidth;
+    var initialHeight = uaDeviceHeight;
+    var userZoom = "zoom";
+    if (viewportProperties["maximum-scale"] !== undefined) {
+        maxZoom = translateZoomProperty(viewportProperties["maximum-scale"]);
+    }
+    if (viewportProperties["minimum-scale"] !== undefined) {
+        minZoom = translateZoomProperty(viewportProperties["minimum-scale"]);
+    }
+    if (viewportProperties["initial-scale"] !== undefined) {
+        zoom = translateZoomProperty(viewportProperties["initial-scale"]);
+    }
+
+
+    /* For a viewport META element that translates into an @viewport rule
+       with no ‘max-zoom’ declaration and a non-‘auto’ ‘min-zoom’ value
+       that is larger than the ‘max-zoom’ value of the UA stylesheet,
+       the ‘min-zoom’ declaration value is clamped to the UA stylesheet
+       ‘max-zoom’ value.  */
+    if (minZoom !== null && maxZoom === null) {
+        minZoom = min(uaMaxZoom, translateZoomProperty(viewportProperties["minimum-scale"]));
+    }
+
+    if (viewportProperties["width"] !== undefined) {
+        minWidth = "extend-to-zoom";
+        maxWidth = translateLengthProperty(viewportProperties["width"], vw, vh);
+    }
+
+    if (viewportProperties["height"] !== undefined) {
+        minHeight = "extend-to-zoom";
+        maxHeight = translateLengthProperty(viewportProperties["height"], vw, vh);
+    }
+
+    // Following http://dev.w3.org/csswg/css-device-adapt/#user-scalable0
+    if (viewportProperties["user-scalable"] !== undefined) {
+        userZoom = viewportProperties["user-scalable"];
+        if (typeof userZoom === "number") {
+            if (userZoom >=1 || userZoom <= -1) {
+                userZoom = "zoom";
+            } else {
+                userZoom = "fixed";
+            }
+        } else {
+            switch(userZoom) {
+            case "yes":
+            case "device-width":
+            case "device-height":
+                userZoom = "zoom";
+                break;
+            case "no":
+            default:
+                userZoom = "fixed";
+                break;
+            }
+        }
+    }
+
+    /* For a viewport META element that translates into an @viewport rule
+       with a non-‘auto’ ‘zoom’ declaration and no ‘width’ declaration: */
+    if (zoom !== null &&
+        (viewportProperties["width"] === undefined || width === undefined)) {
+        if (viewportProperties["height"] !== undefined) {
+            // If it adds a ‘height’ descriptor, add: width: auto;
+            minWidth = null;
+            maxWidth = null;
+        } else {
+            // Otherwise, add: width: extend-to-zoom;
+            minWidth = "extend-to-zoom";
+            maxWidth = "extend-to-zoom";
+        }
+    }
+
+
+    // Following http://dev.w3.org/csswg/css-device-adapt/#constraining-procedure
+
+    // If min-zoom is not ‘auto’ and max-zoom is not ‘auto’,
+    // set max-zoom = MAX(min-zoom, max-zoom)
+    if (minZoom !== null && maxZoom !== null) {
+        maxZoom = max(minZoom, maxZoom);
+    }
+
+    // If zoom is not ‘auto’, set zoom = MAX(min-zoom, MIN(max-zoom, zoom))
+    if (zoom !== null) {
+        zoom = clamp(zoom, minZoom, maxZoom);
+    }
+
+    // from "Resolving ‘extend-to-zoom’"
+    var extendZoom = (zoom === null && maxZoom === null ? null : min(zoom, maxZoom));
+    var extendWidth, extendHeight;
+    if (extendZoom === null) {
+        if (maxWidth === "extend-to-zoom") {
+            maxWidth = null;
+        }
+        if (maxHeight === "extend-to-zoom") {
+            maxHeight = null;
+        }
+        if (minWidth === "extend-to-zoom") {
+            minWidth = maxWidth;
+        }
+        if (minHeight === "extend-to-zoom") {
+            minHeight = maxHeight;
+        }
+    } else {
+        extendWidth = initialWidth / extendZoom;
+        extendHeight = initialHeight / extendZoom;
+
+        if (maxWidth === "extend-to-zoom") {
+            maxWidth = extendWidth;
+        }
+        if (maxHeight === "extend-to-zoom") {
+            maxHeight = extendHeight;
+        }
+        if (minWidth === "extend-to-zoom") {
+            minWidth = max(extendWidth, maxWidth);
+        }
+        if (minHeight === "extend-to-zoom") {
+            minHeight = max(extendHeight, maxHeight);
+        }
+    }
+
+    // Resolve initial width and height from min/max descriptors
+    if (minWidth !== null || maxWidth !== null) {
+        width = max(minWidth, min(maxWidth, initialWidth));
+    }
+    if (minHeight !== null || maxHeight !== null) {
+        height = max(minHeight, min(maxHeight, initialHeight));
+    }
+
+    // Resolve width value
+    if (width === null) {
+        if (height === null) {
+            width = initialWidth;
+        } else {
+            if (initialHeight !== 0) {
+                width = Math.round(height * (initialWidth / initialHeight));
+            } else {
+                width = initialWidth;
+            }
+        }
+    }
+    if (height === null) {
+        if (initialWidth !== 0) {
+            height = Math.round(width * (initialHeight / initialWidth));
+        } else {
+            height = initialHeight;
+        }
+    }
+
+    return { zoom: zoom, width: width, height: height, userZoom: userZoom};
+};
+
+function min(a, b) {
+    if (a === null) return b;
+    if (b === null) return a;
+    return Math.min(a,b);
+}
+
+function max(a, b) {
+    if (a === null) return b;
+    if (b === null) return a;
+    return Math.max(a,b);
+}
+
+
+function translateLengthProperty(prop, vw, vh) {
+    // based on http://dev.w3.org/csswg/css-device-adapt/#width2
+    if (typeof prop === "number") {
+        if (prop >= 0) {
+            // Non-negative number values are translated to pixel lengths, clamped to the range: [1px, 10000px]
+            return clamp(prop, 1, 10000);
+        } else {
+            return undefined;
+        }
+    }
+    if (prop === "device-width") {
+        return 100*vw;
+    }
+    if (prop === "device-height") {
+        return 100*vh;
+    }
+    return 1;
+}
+
+function translateZoomProperty(prop) {
+    // based on http://dev.w3.org/csswg/css-device-adapt/#initial-scale0
+    if (typeof prop === "number") {
+        if (prop >= 0) {
+            // Non-negative number values are translated to <number> values, clamped to the range [0.1, 10]
+            return clamp(prop, 0.1, 10);
+        } else {
+            return undefined;
+        }
+    }
+    if (prop === "yes") {
+        return 1;
+    }
+    if (prop === "device-width" || prop === "device-height") {
+        return 10;
+    }
+    if (prop === "no" || prop === null) {
+        return 0.1;
+    }
+}
+
+// return value if min <= value <= max, or the closest from min or max
+function clamp(value, minv, maxv) {
+    return max(min(value, maxv), minv);
+}
+
+/*
+from http://dev.w3.org/csswg/css-device-adapt/#viewport-meta
+ Parse-Content(S)
+i ← 1
+while i ≤ length[S]
+    do while i ≤ length[S] and S[i] in [whitespace, separator, '=']
+        do i ← i + 1
+    if i ≤ length[S]
+        then i ← Parse-Property(S, i)
+
+Parse-Property(S, i)
+start ← i
+while i ≤ length[S] and S[i] not in [whitespace, separator, '=']
+    do i ← i + 1
+if i > length[S] or S[i] in [separator]
+    then return i
+property-name ← S[start .. (i - 1)]
+while i ≤ length[S] and S[i] not in [separator, '=']
+    do i ← i + 1
+if i > length[S] or S[i] in [separator]
+    then return i
+while i ≤ length[S] and S[i] in [whitespace, '=']
+    do i ← i + 1
+if i > length[S] or S[i] in [separator]
+    then return i
+start ← i
+while i ≤ length[S] and S[i] not in [whitespace, separator, '=']
+    do i ← i + 1
+property-value ← S[start .. (i - 1)]
+Set-Property(property-name, property-value)
+return i */
+exports.parseMetaViewPortContent = function (S) {
+    var parsedContent = {
+        validProperties : {},
+        unknownProperties: {},
+        invalidValues : {}
+    };
+    var i = 1;
+    while (i <= S.length) {
+        while (i <= S.length && RegExp(' |\x0A|\x09|\0d|,|;|=').test(S[i-1])) {
+            i++;
+        }
+        if (i <= S.length) {
+            i = parseProperty(parsedContent, S, i);
+        }
+    }
+    return parsedContent;
+};
+
+var propertyNames = ["width", "height", "initial-scale", "minimum-scale", "maximum-scale", "user-scalable"];
+
+function parseProperty(parsedContent, S, i) {
+    var start = i;
+    while (i <= S.length && !RegExp(' |\x0A|\x09|\0d|,|;|=').test(S[i-1])) {
+        i++;
+    }
+    if (i > S.length || RegExp(',|;').test(S[i-1])) {
+        return i;
+    }
+    var propertyName = S.slice(start - 1, i-1);
+    while (i <= S.length && !RegExp(',|;|=').test(S[i-1])) {
+        i++;
+    }
+    if (i > S.length || RegExp(',|;').test(S[i-1])) {
+        return i;
+    }
+    while (i <= S.length && RegExp(' |\x0A|\x09|\0d|=').test(S[i-1])) {
+        i++;
+    }
+    if (i > S.length || RegExp(',|;').test(S[i-1])) {
+        return i;
+    }
+    start = i;
+    while (i <= S.length && !RegExp(' |\x0A|\x09|\0d|,|;|=').test(S[i-1])) {
+        i++;
+    }
+    var propertyValue = S.slice(start - 1, i-1);
+    setProperty(parsedContent, propertyName, propertyValue);
+    return i;
+}
+
+function setProperty(parsedContent, name, value) {
+    if (propertyNames.indexOf(name) >= 0) {
+        var number = parseFloat(value);
+        if (!isNaN(number)) {
+            parsedContent.validProperties[name] = number;
+            return;
+        }
+        var string = value.toLowerCase();
+        if (string === "yes" || string === "no" || string === "device-width" || string === "device-height") {
+            parsedContent.validProperties[name] = string;
+            return;
+        }
+        parsedContent.validProperties[name] = null;
+        parsedContent.invalidValues[name] = value;
+    } else {
+        parsedContent.unknownProperties[name] = value;
+    }
+}
+
+exports.expectedValues = {
+    "width": ["device-width", "device-height", "a positive number"],
+    "height": ["device-width", "device-height", "a positive number"],
+    "initial-scale": ["a positive number"],
+    "minimum-scale": ["a positive number"],
+    "maximum-scale": ["a positive number"],
+    "user-scalable": ["yes", "no", "0", "1"]
+};
+},{}],300:[function(require,module,exports){
+/**
+ * Helpers.
+ */
+
+var s = 1000;
+var m = s * 60;
+var h = m * 60;
+var d = h * 24;
+var y = d * 365.25;
+
+/**
+ * Parse or format the given `val`.
+ *
+ * Options:
+ *
+ *  - `long` verbose formatting [false]
+ *
+ * @param {String|Number} val
+ * @param {Object} options
+ * @return {String|Number}
+ * @api public
+ */
+
+module.exports = function(val, options){
+  options = options || {};
+  if ('string' == typeof val) return parse(val);
+  return options.long
+    ? long(val)
+    : short(val);
+};
+
+/**
+ * Parse the given `str` and return milliseconds.
+ *
+ * @param {String} str
+ * @return {Number}
+ * @api private
+ */
+
+function parse(str) {
+  str = '' + str;
+  if (str.length > 10000) return;
+  var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(str);
+  if (!match) return;
+  var n = parseFloat(match[1]);
+  var type = (match[2] || 'ms').toLowerCase();
+  switch (type) {
+    case 'years':
+    case 'year':
+    case 'yrs':
+    case 'yr':
+    case 'y':
+      return n * y;
+    case 'days':
+    case 'day':
+    case 'd':
+      return n * d;
+    case 'hours':
+    case 'hour':
+    case 'hrs':
+    case 'hr':
+    case 'h':
+      return n * h;
+    case 'minutes':
+    case 'minute':
+    case 'mins':
+    case 'min':
+    case 'm':
+      return n * m;
+    case 'seconds':
+    case 'second':
+    case 'secs':
+    case 'sec':
+    case 's':
+      return n * s;
+    case 'milliseconds':
+    case 'millisecond':
+    case 'msecs':
+    case 'msec':
+    case 'ms':
+      return n;
+  }
+}
+
+/**
+ * Short format for `ms`.
+ *
+ * @param {Number} ms
+ * @return {String}
+ * @api private
+ */
+
+function short(ms) {
+  if (ms >= d) return Math.round(ms / d) + 'd';
+  if (ms >= h) return Math.round(ms / h) + 'h';
+  if (ms >= m) return Math.round(ms / m) + 'm';
+  if (ms >= s) return Math.round(ms / s) + 's';
+  return ms + 'ms';
+}
+
+/**
+ * Long format for `ms`.
+ *
+ * @param {Number} ms
+ * @return {String}
+ * @api private
+ */
+
+function long(ms) {
+  return plural(ms, d, 'day')
+    || plural(ms, h, 'hour')
+    || plural(ms, m, 'minute')
+    || plural(ms, s, 'second')
+    || ms + ' ms';
+}
+
+/**
+ * Pluralization helper.
+ */
+
+function plural(ms, n, name) {
+  if (ms < n) return;
+  if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name;
+  return Math.ceil(ms / n) + ' ' + name + 's';
+}
+
+},{}],301:[function(require,module,exports){
+(function (Buffer){
 'use strict';
 
 
-const jpeg=require('jpeg-js');
+const jpeg = require('jpeg-js');
 
-function getPixel(x,y,channel,width,buff){
-return buff[(x+y*width)*4+channel];
+function getPixel(x, y, channel, width, buff) {
+	return buff[(x + y * width) * 4 + channel];
 }
 
-function convertPixelsToHistogram(img){
-const createHistogramArray=function(){
-const ret=new Array(256);
-for(let i=0;i<ret.length;i++){
-ret[i]=0;
+function convertPixelsToHistogram(img) {
+	const createHistogramArray = function () {
+		const ret = new Array(256);
+		for (let i = 0; i < ret.length; i++) {
+			ret[i] = 0;
+		}
+		return ret;
+	};
+
+	const width = img.width;
+	const height = img.height;
+
+	const histograms = [
+		createHistogramArray(),
+		createHistogramArray(),
+		createHistogramArray()
+	];
+
+	for (let channel = 0; channel < histograms.length; channel++) {
+		for (let i = 0; i < width; i++) {
+			for (let j = 0; j < height; j++) {
+				const pixelValue = getPixel(i, j, channel, width, img.data);
+
+				// Erase pixels considered as white
+				if (getPixel(i, j, 0, width, img.data) < 249 &&
+						getPixel(i, j, 1, width, img.data) < 249 &&
+						getPixel(i, j, 2, width, img.data) < 249) {
+					histograms[channel][pixelValue]++;
+				}
+			}
+		}
+	}
+
+	return histograms;
 }
-return ret;
+
+function synthesizeWhiteFrame(frames) {
+	const firstImageData = jpeg.decode(frames[0].getImage());
+	const width = firstImageData.width;
+	const height = firstImageData.height;
+
+	const frameData = new Buffer(width * height * 4);
+	let i = 0;
+	while (i < frameData.length) {
+		frameData[i++] = 0xFF; // red
+		frameData[i++] = 0xFF; // green
+		frameData[i++] = 0xFF; // blue
+		frameData[i++] = 0xFF; // alpha - ignored in JPEGs
+	}
+
+	var jpegImageData = jpeg.encode({
+		data: frameData,
+		width: width,
+		height: height
+	});
+	return jpegImageData.data;
+}
+
+const screenshotTraceCategory = 'disabled-by-default-devtools.screenshot';
+function extractFramesFromTimeline(timeline) {
+	let trace;
+	trace = typeof timeline === 'string' ? fs.readFileSync(timeline, 'utf-8') : timeline;
+	try {
+		trace = typeof trace === 'string' ? JSON.parse(trace) : trace;
+	} catch (e) {
+		throw new Error('Speedline: Invalid JSON' + e.message);
+	}
+	let events = trace.traceEvents || trace;
+	events = events.sort((a, b) => a.ts - b.ts).filter(e => e.ts !== 0);
+
+	const startTs = events[0].ts / 1000;
+	const endTs = events[events.length - 1].ts / 1000;
+
+	const rawScreenshots = events.filter(e => e.cat.includes(screenshotTraceCategory));
+	const frames = rawScreenshots.map(function (evt) {
+		const base64img = evt.args && evt.args.snapshot;
+		const timestamp = evt.ts / 1000;
+
+		const imgBuff = new Buffer(base64img, 'base64');
+		return frame(imgBuff, timestamp);
+	});
+
+	if (frames.length === 0) {
+		return Promise.reject(new Error('No screenshots found in trace'));
+	}
+	// add white frame to beginning of trace
+	const fakeWhiteFrame = frame(synthesizeWhiteFrame(frames), startTs);
+	frames.unshift(fakeWhiteFrame);
+
+	const data = {
+		startTs,
+		endTs,
+		frames: frames
+	};
+	return Promise.resolve(data);
+}
+
+function frame(imgBuff, ts) {
+	let _histogram = null;
+	let _progress = null;
+	let _perceptualProgress = null;
+	let _parsedImage = null;
+
+	return {
+		getHistogram: function () {
+			if (_histogram) {
+				return _histogram;
+			}
+
+			const pixels = this.getParsedImage();
+			_histogram = convertPixelsToHistogram(pixels);
+			return _histogram;
+		},
+
+		getTimeStamp: function () {
+			return ts;
+		},
+
+		setProgress: function (progress) {
+			_progress = progress;
+		},
+
+		setPerceptualProgress: function (progress) {
+			_perceptualProgress = progress;
+		},
+
+		getImage: function () {
+			return imgBuff;
+		},
+
+		getParsedImage: function () {
+			if (!_parsedImage) {
+				_parsedImage = jpeg.decode(imgBuff);
+			}
+			return _parsedImage;
+		},
+
+		getProgress: function () {
+			return _progress;
+		},
+
+		getPerceptualProgress: function () {
+			return _perceptualProgress;
+		}
+	};
+}
+
+module.exports = {
+	extractFramesFromTimeline,
+	create: frame
 };
 
-const width=img.width;
-const height=img.height;
-
-const histograms=[
-createHistogramArray(),
-createHistogramArray(),
-createHistogramArray()];
-
-
-for(let channel=0;channel<histograms.length;channel++){
-for(let i=0;i<width;i++){
-for(let j=0;j<height;j++){
-const pixelValue=getPixel(i,j,channel,width,img.data);
-
-
-if(getPixel(i,j,0,width,img.data)<249&&
-getPixel(i,j,1,width,img.data)<249&&
-getPixel(i,j,2,width,img.data)<249){
-histograms[channel][pixelValue]++;
-}
-}
-}
-}
-
-return histograms;
-}
-
-function synthesizeWhiteFrame(frames){
-const firstImageData=jpeg.decode(frames[0].getImage());
-const width=firstImageData.width;
-const height=firstImageData.height;
-
-const frameData=new Buffer(width*height*4);
-let i=0;
-while(i<frameData.length){
-frameData[i++]=0xFF;
-frameData[i++]=0xFF;
-frameData[i++]=0xFF;
-frameData[i++]=0xFF;
-}
-
-var jpegImageData=jpeg.encode({
-data:frameData,
-width:width,
-height:height});
-
-return jpegImageData.data;
-}
-
-const screenshotTraceCategory='disabled-by-default-devtools.screenshot';
-function extractFramesFromTimeline(timeline){
-let trace;
-trace=typeof timeline==='string'?fs.readFileSync(timeline,'utf-8'):timeline;
-try{
-trace=typeof trace==='string'?JSON.parse(trace):trace;
-}catch(e){
-throw new Error('Speedline: Invalid JSON'+e.message);
-}
-let events=trace.traceEvents||trace;
-events=events.sort((a,b)=>a.ts-b.ts).filter(e=>e.ts!==0);
-
-const startTs=events[0].ts/1000;
-const endTs=events[events.length-1].ts/1000;
-
-const rawScreenshots=events.filter(e=>e.cat.includes(screenshotTraceCategory));
-const frames=rawScreenshots.map(function(evt){
-const base64img=evt.args&&evt.args.snapshot;
-const timestamp=evt.ts/1000;
-
-const imgBuff=new Buffer(base64img,'base64');
-return frame(imgBuff,timestamp);
-});
-
-if(frames.length===0){
-return Promise.reject(new Error('No screenshots found in trace'));
-}
-
-const fakeWhiteFrame=frame(synthesizeWhiteFrame(frames),startTs);
-frames.unshift(fakeWhiteFrame);
-
-const data={
-startTs,
-endTs,
-frames:frames};
-
-return Promise.resolve(data);
-}
-
-function frame(imgBuff,ts){
-let _histogram=null;
-let _progress=null;
-let _perceptualProgress=null;
-let _parsedImage=null;
-
-return{
-getHistogram:function(){
-if(_histogram){
-return _histogram;
-}
-
-const pixels=this.getParsedImage();
-_histogram=convertPixelsToHistogram(pixels);
-return _histogram;
-},
-
-getTimeStamp:function(){
-return ts;
-},
-
-setProgress:function(progress){
-_progress=progress;
-},
-
-setPerceptualProgress:function(progress){
-_perceptualProgress=progress;
-},
-
-getImage:function(){
-return imgBuff;
-},
-
-getParsedImage:function(){
-if(!_parsedImage){
-_parsedImage=jpeg.decode(imgBuff);
-}
-return _parsedImage;
-},
-
-getProgress:function(){
-return _progress;
-},
-
-getPerceptualProgress:function(){
-return _perceptualProgress;
-}};
-
-}
-
-module.exports={
-extractFramesFromTimeline,
-create:frame};
-
-
-}).call(this,require("buffer").Buffer);
-},{"buffer":199,"jpeg-js":266}],273:[function(require,module,exports){
+}).call(this,require("buffer").Buffer)
+},{"buffer":205,"jpeg-js":295}],302:[function(require,module,exports){
 'use strict';
 
-const frame=require('./frame');
-const speedIndex=require('./speed-index');
+const frame = require('./frame');
+const speedIndex = require('./speed-index');
 
-function calculateValues(frames,data){
-const indexes=speedIndex.calculateSpeedIndexes(frames,data);
-const duration=Math.floor(data.endTs-data.startTs);
-const first=Math.floor(indexes.firstPaintTs-data.startTs);
-const complete=Math.floor(indexes.visuallyCompleteTs-data.startTs);
+function calculateValues(frames, data) {
+	const indexes = speedIndex.calculateSpeedIndexes(frames, data);
+	const duration = Math.floor(data.endTs - data.startTs);
+	const first = Math.floor(indexes.firstPaintTs - data.startTs);
+	const complete = Math.floor(indexes.visuallyCompleteTs - data.startTs);
 
-return{
-beginning:data.startTs,
-end:data.endTs,
-frames,
-first,
-complete,
-duration,
-speedIndex:indexes.speedIndex,
-perceptualSpeedIndex:indexes.perceptualSpeedIndex};
-
+	return {
+		beginning: data.startTs,
+		end: data.endTs,
+		frames,
+		first,
+		complete,
+		duration,
+		speedIndex: indexes.speedIndex,
+		perceptualSpeedIndex: indexes.perceptualSpeedIndex
+	};
 }
 
-
-
-
-
-
-module.exports=function(timeline){
-return frame.extractFramesFromTimeline(timeline).then(function(data){
-const frames=data.frames;
-speedIndex.calculateVisualProgress(frames);
-speedIndex.calculatePerceptualProgress(frames);
-return calculateValues(frames,data);
-});
+/**
+ * Retrieve speed index informations
+ * @param  {string|Array|DevtoolsTimelineModel} timeline
+ * @return {Promise} resolving with an object containing the speed index informations
+ */
+module.exports = function (timeline) {
+	return frame.extractFramesFromTimeline(timeline).then(function (data) {
+		const frames = data.frames;
+		speedIndex.calculateVisualProgress(frames);
+		speedIndex.calculatePerceptualProgress(frames);
+		return calculateValues(frames, data);
+	});
 };
 
-},{"./frame":272,"./speed-index":274}],274:[function(require,module,exports){
+},{"./frame":301,"./speed-index":303}],303:[function(require,module,exports){
 'use strict';
 
-const imageSSIM=require('image-ssim');
+const imageSSIM = require('image-ssim');
 
-function calculateFrameProgress(current,initial,target){
-let total=0;
-let match=0;
+function calculateFrameProgress(current, initial, target) {
+	let total = 0;
+	let match = 0;
 
-const currentHist=current.getHistogram();
-const initialHist=initial.getHistogram();
-const targetHist=target.getHistogram();
+	const currentHist = current.getHistogram();
+	const initialHist = initial.getHistogram();
+	const targetHist = target.getHistogram();
 
-for(let channel=0;channel<3;channel++){
-for(let pixelVal=0;pixelVal<256;pixelVal++){
-const currentCount=currentHist[channel][pixelVal];
-const initialCount=initialHist[channel][pixelVal];
-const targetCount=targetHist[channel][pixelVal];
+	for (let channel = 0; channel < 3; channel++) {
+		for (let pixelVal = 0; pixelVal < 256; pixelVal++) {
+			const currentCount = currentHist[channel][pixelVal];
+			const initialCount = initialHist[channel][pixelVal];
+			const targetCount = targetHist[channel][pixelVal];
 
-const currentDiff=Math.abs(currentCount-initialCount);
-const targetDiff=Math.abs(targetCount-initialCount);
+			const currentDiff = Math.abs(currentCount - initialCount);
+			const targetDiff = Math.abs(targetCount - initialCount);
 
-match+=Math.min(currentDiff,targetDiff);
-total+=targetDiff;
-}
+			match += Math.min(currentDiff, targetDiff);
+			total += targetDiff;
+		}
+	}
+
+	let progress;
+	if (match === 0 && total === 0) {	// All images are the same
+		progress = 100;
+	} else {													// When images differs
+		progress = Math.floor(match / total * 100);
+	}
+	return progress;
 }
 
-let progress;
-if(match===0&&total===0){
-progress=100;
-}else{
-progress=Math.floor(match/total*100);
-}
-return progress;
+function calculateVisualProgress(frames) {
+	const initial = frames[0];
+	const target = frames[frames.length - 1];
+
+	frames.forEach(function (frame) {
+		const progress = calculateFrameProgress(frame, initial, target);
+		frame.setProgress(progress);
+	});
+
+	return frames;
 }
 
-function calculateVisualProgress(frames){
-const initial=frames[0];
-const target=frames[frames.length-1];
+function calculateFrameSimilarity(frame, target) {
+	const defaultImageConfig = {
+		channels: 3
+	};
 
-frames.forEach(function(frame){
-const progress=calculateFrameProgress(frame,initial,target);
-frame.setProgress(progress);
-});
+	const frameData = Object.assign(frame.getParsedImage(), defaultImageConfig);
+	const targetData = Object.assign(target.getParsedImage(), defaultImageConfig);
 
-return frames;
+	const diff = imageSSIM.compare(frameData, targetData);
+	return diff.ssim;
 }
 
-function calculateFrameSimilarity(frame,target){
-const defaultImageConfig={
-channels:3};
+function calculatePerceptualProgress(frames) {
+	const target = frames[frames.length - 1];
 
+	// Calculate frames simliarity between each frames and the final
+	const framesSimilarity = frames
+		.map(frame => calculateFrameSimilarity(frame, target));
 
-const frameData=Object.assign(frame.getParsedImage(),defaultImageConfig);
-const targetData=Object.assign(target.getParsedImage(),defaultImageConfig);
+	// Get the min frame similarity value
+	const minPreceptualProgress = framesSimilarity
+		.reduce((min, progress) => Math.min(min, progress), Infinity);
 
-const diff=imageSSIM.compare(frameData,targetData);
-return diff.ssim;
+	// Remap the values from [minPreceptualProgress, 1], to [0, 100] interval
+	// to be consistent with the standard visual progress
+	const normalizedSimilarity = framesSimilarity
+		.map(progress => {
+			if (progress === minPreceptualProgress) { // Images are the same
+				return 0;
+			}
+			const oldRange = 1 - minPreceptualProgress;
+			return ((progress - minPreceptualProgress) * 100) / oldRange;
+		});
+
+	normalizedSimilarity
+		.forEach((progress, index) => frames[index].setPerceptualProgress(progress));
+
+	return frames;
 }
 
-function calculatePerceptualProgress(frames){
-const target=frames[frames.length-1];
+function calculateSpeedIndexes(frames, data) {
+	const startTs = data.startTs;
+	let visuallyCompleteTs;
+	let firstPaintTs;
 
+	// find first paint
+	for (let i = 0; i < frames.length && !firstPaintTs; i++) {
+		if (frames[i].getProgress() > 0) {
+			firstPaintTs = frames[i].getTimeStamp();
+		}
+	}
 
-const framesSimilarity=frames.
-map(frame=>calculateFrameSimilarity(frame,target));
+	// find visually complete
+	for (let i = 0; i < frames.length && !visuallyCompleteTs; i++) {
+		if (frames[i].getProgress() >= 100) {
+			visuallyCompleteTs = frames[i].getTimeStamp();
+		}
+	}
 
+	let prevFrameTs = frames[0].getTimeStamp();
+	let prevProgress = frames[0].getProgress();
+	let prevPerceptualProgress = frames[0].getPerceptualProgress();
 
-const minPreceptualProgress=framesSimilarity.
-reduce((min,progress)=>Math.min(min,progress),Infinity);
+	// SI = firstPaint + sum(fP to VC){1-VC%}
+	//     github.com/pmdartus/speedline/issues/28#issuecomment-244127192
+	let speedIndex = firstPaintTs - startTs;
+	let perceptualSpeedIndex = firstPaintTs - startTs;
 
+	frames.forEach(function (frame) {
+		// skip frames from 0 to fP
+		if (frame.getTimeStamp() > firstPaintTs) {
+			const elapsed = frame.getTimeStamp() - prevFrameTs;
+			speedIndex += elapsed * (1 - prevProgress);
+			perceptualSpeedIndex += elapsed * (1 - prevPerceptualProgress);
+		}
 
+		prevFrameTs = frame.getTimeStamp();
+		prevProgress = frame.getProgress() / 100;
+		prevPerceptualProgress = frame.getPerceptualProgress() / 100;
+	});
 
-const normalizedSimilarity=framesSimilarity.
-map(progress=>{
-if(progress===minPreceptualProgress){
-return 0;
-}
-const oldRange=1-minPreceptualProgress;
-return(progress-minPreceptualProgress)*100/oldRange;
-});
-
-normalizedSimilarity.
-forEach((progress,index)=>frames[index].setPerceptualProgress(progress));
-
-return frames;
+	return {
+		firstPaintTs,
+		visuallyCompleteTs,
+		speedIndex,
+		perceptualSpeedIndex
+	};
 }
 
-function calculateSpeedIndexes(frames,data){
-const startTs=data.startTs;
-let visuallyCompleteTs;
-let firstPaintTs;
+module.exports = {
+	calculateVisualProgress,
+	calculatePerceptualProgress,
+	calculateSpeedIndexes
+};
 
-
-for(let i=0;i<frames.length&&!firstPaintTs;i++){
-if(frames[i].getProgress()>0){
-firstPaintTs=frames[i].getTimeStamp();
-}
-}
-
-
-for(let i=0;i<frames.length&&!visuallyCompleteTs;i++){
-if(frames[i].getProgress()>=100){
-visuallyCompleteTs=frames[i].getTimeStamp();
-}
-}
-
-let prevFrameTs=frames[0].getTimeStamp();
-let prevProgress=frames[0].getProgress();
-let prevPerceptualProgress=frames[0].getPerceptualProgress();
-
-
-
-let speedIndex=firstPaintTs-startTs;
-let perceptualSpeedIndex=firstPaintTs-startTs;
-
-frames.forEach(function(frame){
-
-if(frame.getTimeStamp()>firstPaintTs){
-const elapsed=frame.getTimeStamp()-prevFrameTs;
-speedIndex+=elapsed*(1-prevProgress);
-perceptualSpeedIndex+=elapsed*(1-prevPerceptualProgress);
-}
-
-prevFrameTs=frame.getTimeStamp();
-prevProgress=frame.getProgress()/100;
-prevPerceptualProgress=frame.getPerceptualProgress()/100;
-});
-
-return{
-firstPaintTs,
-visuallyCompleteTs,
-speedIndex,
-perceptualSpeedIndex};
-
-}
-
+},{"image-ssim":294}],304:[function(require,module,exports){
 module.exports={
-calculateVisualProgress,
-calculatePerceptualProgress,
-calculateSpeedIndexes};
-
-
-},{"image-ssim":265}],275:[function(require,module,exports){
-module.exports={
-"version":"1.6.0"};
-
-},{}]},{},[196]);
\ No newline at end of file
+  "version": "1.7.0-alpha.1"
+}
+},{}]},{},[197]);
diff --git a/third_party/WebKit/Source/devtools/front_end/bindings/CompilerScriptMapping.js b/third_party/WebKit/Source/devtools/front_end/bindings/CompilerScriptMapping.js
index dead587..8ff4c7f 100644
--- a/third_party/WebKit/Source/devtools/front_end/bindings/CompilerScriptMapping.js
+++ b/third_party/WebKit/Source/devtools/front_end/bindings/CompilerScriptMapping.js
@@ -244,8 +244,7 @@
    * @param {!SDK.SourceMap} sourceMap
    */
   _populateSourceMapSources(script, sourceMap) {
-    var executionContext = script.executionContext();
-    var frameId = executionContext ? executionContext.frameId || '' : '';
+    var frameId = Bindings.frameIdForScript(script);
     script[Bindings.CompilerScriptMapping._frameIdSymbol] = frameId;
     for (var sourceURL of sourceMap.sourceURLs()) {
       var contentProvider = sourceMap.sourceContentProvider(sourceURL, Common.resourceTypes.SourceMapScript);
diff --git a/third_party/WebKit/Source/devtools/front_end/bindings/NetworkProject.js b/third_party/WebKit/Source/devtools/front_end/bindings/NetworkProject.js
index 36f4242..dba062e 100644
--- a/third_party/WebKit/Source/devtools/front_end/bindings/NetworkProject.js
+++ b/third_party/WebKit/Source/devtools/front_end/bindings/NetworkProject.js
@@ -127,19 +127,12 @@
   }
 
   /**
-   * @param {!Workspace.Project} project
-   * @return {?SDK.Target} target
+   * @param {!Workspace.UISourceCode} uiSourceCode
+   * @return {?Set<string>}
    */
-  static targetForProject(project) {
-    return project[Bindings.NetworkProject._targetSymbol] || null;
-  }
-
-  /**
-   * @param {!Workspace.Project} project
-   * @return {?SDK.ResourceTreeFrame}
-   */
-  static frameForProject(project) {
-    return project[Bindings.NetworkProject._frameSymbol] || null;
+  static frameAttribution(uiSourceCode) {
+    var frameId = uiSourceCode[Bindings.NetworkProject._frameAttributionSymbol];
+    return frameId ? new Set([frameId]) : null;
   }
 
   /**
@@ -147,7 +140,21 @@
    * @return {?SDK.Target} target
    */
   static targetForUISourceCode(uiSourceCode) {
-    return uiSourceCode[Bindings.NetworkProject._targetSymbol] || null;
+    return uiSourceCode.project()[Bindings.NetworkProject._targetSymbol] || null;
+  }
+
+  /**
+   * @param {!Workspace.UISourceCode} uiSourceCode
+   * @return {!Array<!SDK.ResourceTreeFrame>}
+   */
+  static framesForUISourceCode(uiSourceCode) {
+    var target = Bindings.NetworkProject.targetForUISourceCode(uiSourceCode);
+    var resourceTreeModel = target && target.model(SDK.ResourceTreeModel);
+    var frameIds = Bindings.NetworkProject.frameAttribution(uiSourceCode);
+    if (!resourceTreeModel || !frameIds)
+      return [];
+    var frames = Array.from(frameIds).map(frameId => resourceTreeModel.frameForId(frameId));
+    return frames.filter(frame => !!frame);
   }
 
   /**
@@ -181,8 +188,6 @@
     project = new Bindings.ContentProviderBasedProject(
         this._workspace, projectId, projectType, '', false /* isServiceProject */);
     project[Bindings.NetworkProject._targetSymbol] = this._target;
-    project[Bindings.NetworkProject._frameSymbol] =
-        frameId && this._resourceTreeModel ? this._resourceTreeModel.frameForId(frameId) : null;
     this._workspaceProjects.set(projectId, project);
     return project;
   }
@@ -277,13 +282,12 @@
     if (!this._acceptsScript(script))
       return;
     var originalContentProvider = script.originalContentProvider();
-    var executionContext = script.executionContext();
-    var frameId = executionContext ? executionContext.frameId || '' : '';
+    var frameId = Bindings.frameIdForScript(script);
     script[Bindings.NetworkProject._frameIdSymbol] = frameId;
     var uiSourceCode = this._createFile(originalContentProvider, frameId, script.isContentScript());
     uiSourceCode[Bindings.NetworkProject._scriptSymbol] = script;
-    var resource = SDK.ResourceTreeModel.resourceForURL(uiSourceCode.url());
-    this._addUISourceCodeWithProvider(uiSourceCode, originalContentProvider, this._resourceMetadata(resource));
+    var metadata = this._fetchMetadata(frameId, uiSourceCode.url());
+    this._addUISourceCodeWithProvider(uiSourceCode, originalContentProvider, metadata);
   }
 
   /**
@@ -322,8 +326,8 @@
     var originalContentProvider = header.originalContentProvider();
     var uiSourceCode = this._createFile(originalContentProvider, header.frameId, false);
     uiSourceCode[Bindings.NetworkProject._styleSheetSymbol] = header;
-    var resource = SDK.ResourceTreeModel.resourceForURL(uiSourceCode.url());
-    this._addUISourceCodeWithProvider(uiSourceCode, originalContentProvider, this._resourceMetadata(resource));
+    var metadata = this._fetchMetadata(header.frameId, uiSourceCode.url());
+    this._addUISourceCodeWithProvider(uiSourceCode, originalContentProvider, metadata);
   }
 
   /**
@@ -371,7 +375,7 @@
 
     var uiSourceCode = this._createFile(resource, resource.frameId, false);
     uiSourceCode[Bindings.NetworkProject._resourceSymbol] = resource;
-    this._addUISourceCodeWithProvider(uiSourceCode, resource, this._resourceMetadata(resource));
+    this._addUISourceCodeWithProvider(uiSourceCode, resource, Bindings.resourceMetadata(resource));
   }
 
   /**
@@ -426,18 +430,22 @@
     var url = contentProvider.contentURL();
     var project = this._workspaceProject(frameId, isContentScript);
     var uiSourceCode = project.createUISourceCode(url, contentProvider.contentType());
-    uiSourceCode[Bindings.NetworkProject._targetSymbol] = this._target;
+    uiSourceCode[Bindings.NetworkProject._frameAttributionSymbol] = frameId;
     return uiSourceCode;
   }
 
   /**
-   * @param {?SDK.Resource} resource
+   * @param {string} frameId
+   * @param {string} url
    * @return {?Workspace.UISourceCodeMetadata}
    */
-  _resourceMetadata(resource) {
-    if (!resource || (typeof resource.contentSize() !== 'number' && !resource.lastModified()))
+  _fetchMetadata(frameId, url) {
+    if (!this._resourceTreeModel)
       return null;
-    return new Workspace.UISourceCodeMetadata(resource.lastModified(), resource.contentSize());
+    var frame = this._resourceTreeModel.frameForId(frameId);
+    if (!frame)
+      return null;
+    return Bindings.resourceMetadata(frame.resourceForURL(url));
   }
 
   _dispose() {
@@ -491,5 +499,6 @@
 Bindings.NetworkProject._scriptSymbol = Symbol('script');
 Bindings.NetworkProject._styleSheetSymbol = Symbol('styleSheet');
 Bindings.NetworkProject._targetSymbol = Symbol('target');
-Bindings.NetworkProject._frameSymbol = Symbol('frame');
-Bindings.NetworkProject._frameIdSymbol = Symbol('frameid');
\ No newline at end of file
+Bindings.NetworkProject._frameIdSymbol = Symbol('frameid');
+
+Bindings.NetworkProject._frameAttributionSymbol = Symbol('Bindings.NetworkProject._frameAttributionSymbol');
\ No newline at end of file
diff --git a/third_party/WebKit/Source/devtools/front_end/bindings/ResourceUtils.js b/third_party/WebKit/Source/devtools/front_end/bindings/ResourceUtils.js
index dec3afbe..006a493 100644
--- a/third_party/WebKit/Source/devtools/front_end/bindings/ResourceUtils.js
+++ b/third_party/WebKit/Source/devtools/front_end/bindings/ResourceUtils.js
@@ -84,3 +84,28 @@
   var displayName = url.trimURL(parsedURL.host);
   return displayName === '/' ? parsedURL.host + '/' : displayName;
 };
+
+/**
+ * @param {?SDK.Resource} resource
+ * @return {?Workspace.UISourceCodeMetadata}
+ */
+Bindings.resourceMetadata = function(resource) {
+  if (!resource || (typeof resource.contentSize() !== 'number' && !resource.lastModified()))
+    return null;
+  return new Workspace.UISourceCodeMetadata(resource.lastModified(), resource.contentSize());
+};
+
+/**
+ * @param {!SDK.Script} script
+ * @return {string}
+ */
+Bindings.frameIdForScript = function(script) {
+  var executionContext = script.executionContext();
+  if (executionContext)
+    return executionContext.frameId || '';
+  // This is to overcome compilation cache which doesn't get reset.
+  var resourceTreeModel = script.debuggerModel.target().model(SDK.ResourceTreeModel);
+  if (!resourceTreeModel || !resourceTreeModel.mainFrame)
+    return '';
+  return resourceTreeModel.mainFrame.id;
+};
diff --git a/third_party/WebKit/Source/devtools/front_end/components/DOMPresentationUtils.js b/third_party/WebKit/Source/devtools/front_end/components/DOMPresentationUtils.js
index 679710f..1e369ace 100644
--- a/third_party/WebKit/Source/devtools/front_end/components/DOMPresentationUtils.js
+++ b/third_party/WebKit/Source/devtools/front_end/components/DOMPresentationUtils.js
@@ -117,7 +117,7 @@
 
   link.addEventListener('click', Common.Revealer.reveal.bind(Common.Revealer, node, undefined), false);
   link.addEventListener('mouseover', node.highlight.bind(node, undefined, undefined), false);
-  link.addEventListener('mouseleave', SDK.DOMModel.hideDOMNodeHighlight.bind(SDK.DOMModel), false);
+  link.addEventListener('mouseleave', () => SDK.OverlayModel.hideDOMNodeHighlight(), false);
 
   return root;
 };
diff --git a/third_party/WebKit/Source/devtools/front_end/elements/ElementsBreadcrumbs.js b/third_party/WebKit/Source/devtools/front_end/elements/ElementsBreadcrumbs.js
index 67bca2f..24a45c6 100644
--- a/third_party/WebKit/Source/devtools/front_end/elements/ElementsBreadcrumbs.js
+++ b/third_party/WebKit/Source/devtools/front_end/elements/ElementsBreadcrumbs.js
@@ -56,7 +56,7 @@
 
   _mouseMovedOutOfCrumbs(event) {
     if (this._currentDOMNode)
-      SDK.DOMModel.hideDOMNodeHighlight();
+      SDK.OverlayModel.hideDOMNodeHighlight();
   }
 
 
diff --git a/third_party/WebKit/Source/devtools/front_end/elements/ElementsPanel.js b/third_party/WebKit/Source/devtools/front_end/elements/ElementsPanel.js
index aba07ef..3f3c33d 100644
--- a/third_party/WebKit/Source/devtools/front_end/elements/ElementsPanel.js
+++ b/third_party/WebKit/Source/devtools/front_end/elements/ElementsPanel.js
@@ -330,7 +330,7 @@
   willHide() {
     UI.context.setFlavor(Elements.ElementsPanel, null);
 
-    SDK.DOMModel.hideDOMNodeHighlight();
+    SDK.OverlayModel.hideDOMNodeHighlight();
     for (var i = 0; i < this._treeOutlines.length; ++i) {
       var treeOutline = this._treeOutlines[i];
       treeOutline.setVisible(false);
diff --git a/third_party/WebKit/Source/devtools/front_end/elements/ElementsTreeElementHighlighter.js b/third_party/WebKit/Source/devtools/front_end/elements/ElementsTreeElementHighlighter.js
index f8d7034..7b7e454 100644
--- a/third_party/WebKit/Source/devtools/front_end/elements/ElementsTreeElementHighlighter.js
+++ b/third_party/WebKit/Source/devtools/front_end/elements/ElementsTreeElementHighlighter.js
@@ -15,8 +15,9 @@
     this._treeOutline.addEventListener(UI.TreeOutline.Events.ElementCollapsed, this._clearState, this);
     this._treeOutline.addEventListener(Elements.ElementsTreeOutline.Events.SelectedNodeChanged, this._clearState, this);
     SDK.targetManager.addModelListener(
-        SDK.DOMModel, SDK.DOMModel.Events.NodeHighlightedInOverlay, this._highlightNode, this);
-    this._treeOutline.domModel().addEventListener(SDK.DOMModel.Events.InspectModeWillBeToggled, this._clearState, this);
+        SDK.OverlayModel, SDK.OverlayModel.Events.HighlightNodeRequested, this._highlightNode, this);
+    this._treeOutline.domModel().overlayModel().addEventListener(
+        SDK.OverlayModel.Events.InspectModeWillBeToggled, this._clearState, this);
   }
 
   /**
diff --git a/third_party/WebKit/Source/devtools/front_end/elements/ElementsTreeOutline.js b/third_party/WebKit/Source/devtools/front_end/elements/ElementsTreeOutline.js
index ad4fbca..a0c0471 100644
--- a/third_party/WebKit/Source/devtools/front_end/elements/ElementsTreeOutline.js
+++ b/third_party/WebKit/Source/devtools/front_end/elements/ElementsTreeOutline.js
@@ -620,20 +620,20 @@
     this.setHoverEffect(element);
 
     if (element instanceof Elements.ElementsTreeElement) {
-      this._domModel.highlightDOMNodeWithConfig(
+      this._domModel.overlayModel().highlightDOMNodeWithConfig(
           element.node().id, {mode: 'all', showInfo: !UI.KeyboardShortcut.eventHasCtrlOrMeta(event)});
       return;
     }
 
     if (element instanceof Elements.ElementsTreeOutline.ShortcutTreeElement) {
-      this._domModel.highlightDOMNodeWithConfig(
+      this._domModel.overlayModel().highlightDOMNodeWithConfig(
           undefined, {mode: 'all', showInfo: !UI.KeyboardShortcut.eventHasCtrlOrMeta(event)}, element.backendNodeId());
     }
   }
 
   _onmouseleave(event) {
     this.setHoverEffect(null);
-    SDK.DOMModel.hideDOMNodeHighlight();
+    SDK.OverlayModel.hideDOMNodeHighlight();
   }
 
   _ondragstart(event) {
@@ -653,7 +653,7 @@
     event.dataTransfer.effectAllowed = 'copyMove';
     this._treeElementBeingDragged = treeElement;
 
-    SDK.DOMModel.hideDOMNodeHighlight();
+    SDK.OverlayModel.hideDOMNodeHighlight();
 
     return true;
   }
@@ -971,7 +971,7 @@
     this.selectDOMNode(null, false);
     this._popoverHelper.hidePopover();
     delete this._clipboardNodeData;
-    SDK.DOMModel.hideDOMNodeHighlight();
+    SDK.OverlayModel.hideDOMNodeHighlight();
     this._updateRecords.clear();
   }
 
diff --git a/third_party/WebKit/Source/devtools/front_end/elements/InspectElementModeController.js b/third_party/WebKit/Source/devtools/front_end/elements/InspectElementModeController.js
index 63fe1ce..0eee986 100644
--- a/third_party/WebKit/Source/devtools/front_end/elements/InspectElementModeController.js
+++ b/third_party/WebKit/Source/devtools/front_end/elements/InspectElementModeController.js
@@ -26,46 +26,46 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 /**
- * @implements {SDK.SDKModelObserver<!SDK.DOMModel>}
+ * @implements {SDK.SDKModelObserver<!SDK.OverlayModel>}
  * @unrestricted
  */
 Elements.InspectElementModeController = class {
   constructor() {
     this._toggleSearchAction = UI.actionRegistry.action('elements.toggle-element-search');
-    this._mode = Protocol.DOM.InspectMode.None;
+    this._mode = Protocol.Overlay.InspectMode.None;
     SDK.targetManager.addEventListener(SDK.TargetManager.Events.SuspendStateChanged, this._suspendStateChanged, this);
-    SDK.targetManager.observeModels(SDK.DOMModel, this);
+    SDK.targetManager.observeModels(SDK.OverlayModel, this);
   }
 
   /**
    * @override
-   * @param {!SDK.DOMModel} domModel
+   * @param {!SDK.OverlayModel} overlayModel
    */
-  modelAdded(domModel) {
+  modelAdded(overlayModel) {
     // When DevTools are opening in the inspect element mode, the first target comes in
     // much later than the InspectorFrontendAPI.enterInspectElementMode event.
-    if (this._mode === Protocol.DOM.InspectMode.None)
+    if (this._mode === Protocol.Overlay.InspectMode.None)
       return;
-    domModel.setInspectMode(this._mode);
+    overlayModel.setInspectMode(this._mode);
   }
 
   /**
    * @override
-   * @param {!SDK.DOMModel} domModel
+   * @param {!SDK.OverlayModel} overlayModel
    */
-  modelRemoved(domModel) {
+  modelRemoved(overlayModel) {
   }
 
   /**
    * @return {boolean}
    */
   isInInspectElementMode() {
-    return this._mode === Protocol.DOM.InspectMode.SearchForNode ||
-        this._mode === Protocol.DOM.InspectMode.SearchForUAShadowDOM;
+    return this._mode === Protocol.Overlay.InspectMode.SearchForNode ||
+        this._mode === Protocol.Overlay.InspectMode.SearchForUAShadowDOM;
   }
 
   stopInspection() {
-    if (this._mode && this._mode !== Protocol.DOM.InspectMode.None)
+    if (this._mode && this._mode !== Protocol.Overlay.InspectMode.None)
       this._toggleInspectMode();
   }
 
@@ -75,22 +75,22 @@
 
     var mode;
     if (this.isInInspectElementMode()) {
-      mode = Protocol.DOM.InspectMode.None;
+      mode = Protocol.Overlay.InspectMode.None;
     } else {
-      mode = Common.moduleSetting('showUAShadowDOM').get() ? Protocol.DOM.InspectMode.SearchForUAShadowDOM :
-                                                             Protocol.DOM.InspectMode.SearchForNode;
+      mode = Common.moduleSetting('showUAShadowDOM').get() ? Protocol.Overlay.InspectMode.SearchForUAShadowDOM :
+                                                             Protocol.Overlay.InspectMode.SearchForNode;
     }
 
     this._setMode(mode);
   }
 
   /**
-   * @param {!Protocol.DOM.InspectMode} mode
+   * @param {!Protocol.Overlay.InspectMode} mode
    */
   _setMode(mode) {
     this._mode = mode;
-    for (var domModel of SDK.targetManager.models(SDK.DOMModel))
-      domModel.setInspectMode(mode);
+    for (var overlayModel of SDK.targetManager.models(SDK.OverlayModel))
+      overlayModel.setInspectMode(mode);
     this._toggleSearchAction.setToggled(this.isInInspectElementMode());
   }
 
@@ -98,7 +98,7 @@
     if (!SDK.targetManager.allTargetsSuspended())
       return;
 
-    this._mode = Protocol.DOM.InspectMode.None;
+    this._mode = Protocol.Overlay.InspectMode.None;
     this._toggleSearchAction.setToggled(false);
   }
 };
diff --git a/third_party/WebKit/Source/devtools/front_end/elements/MetricsSidebarPane.js b/third_party/WebKit/Source/devtools/front_end/elements/MetricsSidebarPane.js
index d9cba702..8e3a2b2 100644
--- a/third_party/WebKit/Source/devtools/front_end/elements/MetricsSidebarPane.js
+++ b/third_party/WebKit/Source/devtools/front_end/elements/MetricsSidebarPane.js
@@ -124,7 +124,7 @@
       this.node().highlight(mode);
     } else {
       delete this._highlightMode;
-      SDK.DOMModel.hideDOMNodeHighlight();
+      SDK.OverlayModel.hideDOMNodeHighlight();
     }
 
     for (var i = 0; this._boxElements && i < this._boxElements.length; ++i) {
diff --git a/third_party/WebKit/Source/devtools/front_end/elements/StylesSidebarPane.js b/third_party/WebKit/Source/devtools/front_end/elements/StylesSidebarPane.js
index b8c5d73..a8b2810df 100644
--- a/third_party/WebKit/Source/devtools/front_end/elements/StylesSidebarPane.js
+++ b/third_party/WebKit/Source/devtools/front_end/elements/StylesSidebarPane.js
@@ -836,7 +836,7 @@
   _onMouseOutSelector() {
     if (this._hoverTimer)
       clearTimeout(this._hoverTimer);
-    SDK.DOMModel.hideDOMNodeHighlight();
+    SDK.OverlayModel.hideDOMNodeHighlight();
   }
 
   _onMouseEnterSelector() {
@@ -846,11 +846,11 @@
   }
 
   _highlight() {
-    SDK.DOMModel.hideDOMNodeHighlight();
+    SDK.OverlayModel.hideDOMNodeHighlight();
     var node = this._parentPane.node();
-    var domModel = node.domModel();
     var selectors = this._style.parentRule ? this._style.parentRule.selectorText() : undefined;
-    domModel.highlightDOMNodeWithConfig(node.id, {mode: 'all', showInfo: undefined, selectors: selectors});
+    node.domModel().overlayModel().highlightDOMNodeWithConfig(
+        node.id, {mode: 'all', showInfo: undefined, selectors: selectors});
   }
 
   /**
diff --git a/third_party/WebKit/Source/devtools/front_end/emulation/DeviceModeModel.js b/third_party/WebKit/Source/devtools/front_end/emulation/DeviceModeModel.js
index 8f3b059..4ae82272 100644
--- a/third_party/WebKit/Source/devtools/front_end/emulation/DeviceModeModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/emulation/DeviceModeModel.js
@@ -472,8 +472,9 @@
               this._uaSetting.get() === Emulation.DeviceModeModel.UA.Mobile,
           this._uaSetting.get() === Emulation.DeviceModeModel.UA.Mobile);
     }
-    if (this._target)
-      this._target.renderingAgent().setShowViewportSizeOnResize(this._type === Emulation.DeviceModeModel.Type.None);
+    var overlayModel = this._target ? this._target.model(SDK.OverlayModel) : null;
+    if (overlayModel)
+      overlayModel.setShowViewportSizeOnResize(this._type === Emulation.DeviceModeModel.Type.None);
     this._updateCallback.call(null);
   }
 
@@ -598,8 +599,7 @@
         allPromises.push(this._target.emulationAgent().resetPageScaleFactor());
       var setDevicePromise;
       if (clear) {
-        setDevicePromise = this._target.emulationAgent().clearDeviceMetricsOverride(
-            this._deviceMetricsOverrideAppliedForTest.bind(this));
+        setDevicePromise = this._target.emulationAgent().clearDeviceMetricsOverride();
       } else {
         var params = {
           width: pageWidth,
@@ -615,8 +615,7 @@
         };
         if (screenOrientation)
           params.screenOrientation = {type: screenOrientation, angle: screenOrientationAngle};
-        setDevicePromise = this._target.emulationAgent().invoke_setDeviceMetricsOverride(
-            params, this._deviceMetricsOverrideAppliedForTest.bind(this));
+        setDevicePromise = this._target.emulationAgent().invoke_setDeviceMetricsOverride(params);
       }
       allPromises.push(setDevicePromise);
       return Promise.all(allPromises);
@@ -636,6 +635,10 @@
     if (!metrics)
       return null;
 
+    if (!this._emulatedPageSize)
+      this._calculateAndEmulate(false);
+    this._target.renderingAgent().setShowViewportSizeOnResize(false);
+
     var pageSize = fullSize ? new UI.Size(metrics.contentWidth, metrics.contentHeight) : this._emulatedPageSize;
     var promises = [];
     promises.push(
@@ -665,10 +668,6 @@
     return screenshot;
   }
 
-  _deviceMetricsOverrideAppliedForTest() {
-    // Used for sniffing in tests.
-  }
-
   /**
    * @param {boolean} touchEnabled
    * @param {boolean} mobile
diff --git a/third_party/WebKit/Source/devtools/front_end/emulation/DeviceModeView.js b/third_party/WebKit/Source/devtools/front_end/emulation/DeviceModeView.js
index 04e9011..d196701 100644
--- a/third_party/WebKit/Source/devtools/front_end/emulation/DeviceModeView.js
+++ b/third_party/WebKit/Source/devtools/front_end/emulation/DeviceModeView.js
@@ -307,6 +307,18 @@
     element.classList.toggle('hidden', !success);
   }
 
+  /**
+   * @param {!Element} element
+   */
+  setNonEmulatedAvailableSize(element) {
+    if (this._model.type() !== Emulation.DeviceModeModel.Type.None)
+      return;
+    var zoomFactor = UI.zoomManager.zoomFactor();
+    var rect = element.getBoundingClientRect();
+    var availableSize = new UI.Size(Math.max(rect.width * zoomFactor, 1), Math.max(rect.height * zoomFactor, 1));
+    this._model.setAvailableSize(availableSize, availableSize);
+  }
+
   _contentAreaResized() {
     var zoomFactor = UI.zoomManager.zoomFactor();
     var rect = this._contentArea.getBoundingClientRect();
@@ -363,16 +375,16 @@
    * @return {!Promise}
    */
   async captureScreenshot() {
-    SDK.DOMModel.muteHighlight();
+    SDK.OverlayModel.muteHighlight();
     var screenshot = await this._model.captureScreenshot(false);
-    SDK.DOMModel.unmuteHighlight();
+    SDK.OverlayModel.unmuteHighlight();
     if (screenshot === null)
       return;
 
     var pageImage = new Image();
     pageImage.src = 'data:image/png;base64,' + screenshot;
     pageImage.onload = async () => {
-      var scale = window.devicePixelRatio / this._model.scale();
+      var scale = window.devicePixelRatio / UI.zoomManager.zoomFactor() / this._model.scale();
       var outlineRect = this._model.outlineRect().scale(scale);
       var screenRect = this._model.screenRect().scale(scale);
       var visiblePageRect = this._model.visiblePageRect().scale(scale);
@@ -398,9 +410,9 @@
    * @return {!Promise}
    */
   async captureFullSizeScreenshot() {
-    SDK.DOMModel.muteHighlight();
+    SDK.OverlayModel.muteHighlight();
     var screenshot = await this._model.captureScreenshot(true);
-    SDK.DOMModel.unmuteHighlight();
+    SDK.OverlayModel.unmuteHighlight();
     if (screenshot === null)
       return;
 
diff --git a/third_party/WebKit/Source/devtools/front_end/emulation/DeviceModeWrapper.js b/third_party/WebKit/Source/devtools/front_end/emulation/DeviceModeWrapper.js
index 54df762..0144678d7 100644
--- a/third_party/WebKit/Source/devtools/front_end/emulation/DeviceModeWrapper.js
+++ b/third_party/WebKit/Source/devtools/front_end/emulation/DeviceModeWrapper.js
@@ -25,22 +25,17 @@
   }
 
   /**
+   * @param {boolean=} fullSize
    * @return {boolean}
    */
-  _captureScreenshot() {
+  _captureScreenshot(fullSize) {
     if (!this._deviceModeView)
-      return false;
-    this._deviceModeView.captureScreenshot();
-    return true;
-  }
-
-  /**
-   * @return {boolean}
-   */
-  _captureFullSizeScreenshot() {
-    if (!this._deviceModeView)
-      return false;
-    this._deviceModeView.captureFullSizeScreenshot();
+      this._deviceModeView = new Emulation.DeviceModeView();
+    this._deviceModeView.setNonEmulatedAvailableSize(this._inspectedPagePlaceholder.element);
+    if (fullSize)
+      this._deviceModeView.captureFullSizeScreenshot();
+    else
+      this._deviceModeView.captureScreenshot();
     return true;
   }
 
@@ -91,7 +86,7 @@
           return Emulation.DeviceModeView._wrapperInstance._captureScreenshot();
 
         case 'emulation.capture-full-height-screenshot':
-          return Emulation.DeviceModeView._wrapperInstance._captureFullSizeScreenshot();
+          return Emulation.DeviceModeView._wrapperInstance._captureScreenshot(true);
 
         case 'emulation.toggle-device-mode':
           Emulation.DeviceModeView._wrapperInstance._toggleDeviceMode();
diff --git a/third_party/WebKit/Source/devtools/front_end/emulation/TouchModel.js b/third_party/WebKit/Source/devtools/front_end/emulation/TouchModel.js
index ce997a4..883ccf6 100644
--- a/third_party/WebKit/Source/devtools/front_end/emulation/TouchModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/emulation/TouchModel.js
@@ -54,8 +54,8 @@
     if (this._customTouchEnabled)
       current = {enabled: true, configuration: 'mobile'};
 
-    var domModel = target.model(SDK.DOMModel);
-    var inspectModeEnabled = domModel ? domModel.inspectModeEnabled() : false;
+    var overlayModel = target.model(SDK.OverlayModel);
+    var inspectModeEnabled = overlayModel ? overlayModel.inspectModeEnabled() : false;
     if (inspectModeEnabled)
       current = {enabled: false, configuration: 'mobile'};
 
@@ -107,8 +107,8 @@
    * @param {!Common.Event} event
    */
   _inspectModeToggled(event) {
-    var domModel = /** @type {!SDK.DOMModel} */ (event.data);
-    this._applyToTarget(domModel.target());
+    var overlayModel = /** @type {!SDK.OverlayModel} */ (event.data);
+    this._applyToTarget(overlayModel.target());
   }
 
   /**
@@ -116,9 +116,9 @@
    * @param {!SDK.Target} target
    */
   targetAdded(target) {
-    var domModel = target.model(SDK.DOMModel);
-    if (domModel)
-      domModel.addEventListener(SDK.DOMModel.Events.InspectModeWillBeToggled, this._inspectModeToggled, this);
+    var overlayModel = target.model(SDK.OverlayModel);
+    if (overlayModel)
+      overlayModel.addEventListener(SDK.OverlayModel.Events.InspectModeWillBeToggled, this._inspectModeToggled, this);
     this._applyToTarget(target);
   }
 
@@ -127,9 +127,11 @@
    * @param {!SDK.Target} target
    */
   targetRemoved(target) {
-    var domModel = target.model(SDK.DOMModel);
-    if (domModel)
-      domModel.removeEventListener(SDK.DOMModel.Events.InspectModeWillBeToggled, this._inspectModeToggled, this);
+    var overlayModel = target.model(SDK.OverlayModel);
+    if (overlayModel) {
+      overlayModel.removeEventListener(
+          SDK.OverlayModel.Events.InspectModeWillBeToggled, this._inspectModeToggled, this);
+    }
   }
 };
 
diff --git a/third_party/WebKit/Source/devtools/front_end/externs.js b/third_party/WebKit/Source/devtools/front_end/externs.js
index 907bd8a..a05f31f 100644
--- a/third_party/WebKit/Source/devtools/front_end/externs.js
+++ b/third_party/WebKit/Source/devtools/front_end/externs.js
@@ -345,6 +345,9 @@
 /** @typedef {!Object<string, !Adb.DevicePortForwardingStatus>} */
 Adb.PortForwardingStatus;
 
+/** @const */
+var module = {};
+
 /**
  * @constructor
  */
diff --git a/third_party/WebKit/Source/devtools/front_end/layer_viewer/LayerViewHost.js b/third_party/WebKit/Source/devtools/front_end/layer_viewer/LayerViewHost.js
index ff3ac68..7ea875e9 100644
--- a/third_party/WebKit/Source/devtools/front_end/layer_viewer/LayerViewHost.js
+++ b/third_party/WebKit/Source/devtools/front_end/layer_viewer/LayerViewHost.js
@@ -255,6 +255,6 @@
       node.highlightForTwoSeconds();
       return;
     }
-    SDK.DOMModel.hideDOMNodeHighlight();
+    SDK.OverlayModel.hideDOMNodeHighlight();
   }
 };
diff --git a/third_party/WebKit/Source/devtools/front_end/main/Main.js b/third_party/WebKit/Source/devtools/front_end/main/Main.js
index eacf169..2aa82f7 100644
--- a/third_party/WebKit/Source/devtools/front_end/main/Main.js
+++ b/third_party/WebKit/Source/devtools/front_end/main/Main.js
@@ -169,7 +169,7 @@
     ConsoleModel.consoleModel = new ConsoleModel.ConsoleModel();
     SDK.multitargetNetworkManager = new SDK.MultitargetNetworkManager();
     NetworkLog.networkLog = new NetworkLog.NetworkLog();
-    SDK.xhrBreakpointManager = new SDK.XHRBreakpointManager();
+    SDK.domDebuggerManager = new SDK.DOMDebuggerManager();
     SDK.targetManager.addEventListener(
         SDK.TargetManager.Events.SuspendStateChanged, this._onSuspendStateChanged.bind(this));
 
@@ -197,7 +197,6 @@
     Persistence.persistence =
         new Persistence.Persistence(Workspace.workspace, Bindings.breakpointManager, Workspace.fileSystemMapping);
 
-    new Main.OverlayController();
     new Main.ExecutionContextSelector(SDK.targetManager, UI.context);
     Bindings.blackboxManager = new Bindings.BlackboxManager(Bindings.debuggerWorkspaceBinding);
 
@@ -797,7 +796,8 @@
  */
 Main.Main.InspectedNodeRevealer = class {
   constructor() {
-    SDK.targetManager.addModelListener(SDK.DOMModel, SDK.DOMModel.Events.NodeInspected, this._inspectNode, this);
+    SDK.targetManager.addModelListener(
+        SDK.OverlayModel, SDK.OverlayModel.Events.InspectNodeRequested, this._inspectNode, this);
   }
 
   /**
@@ -933,7 +933,6 @@
    */
   targetAdded(target) {
     this._updateTarget(target);
-    target.renderingAgent().setShowViewportSizeOnResize(true);
   }
 
   /**
diff --git a/third_party/WebKit/Source/devtools/front_end/main/OverlayController.js b/third_party/WebKit/Source/devtools/front_end/main/OverlayController.js
deleted file mode 100644
index e6800ce..0000000
--- a/third_party/WebKit/Source/devtools/front_end/main/OverlayController.js
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-/**
- * @unrestricted
- */
-Main.OverlayController = class {
-  constructor() {
-    Common.moduleSetting('disablePausedStateOverlay').addChangeListener(this._updateAllOverlays, this);
-    SDK.targetManager.addModelListener(
-        SDK.DebuggerModel, SDK.DebuggerModel.Events.DebuggerPaused, this._updateOverlay, this);
-    SDK.targetManager.addModelListener(
-        SDK.DebuggerModel, SDK.DebuggerModel.Events.DebuggerResumed, this._updateOverlay, this);
-    // TODO(dgozman): we should get DebuggerResumed on navigations instead of listening to GlobalObjectCleared.
-    SDK.targetManager.addModelListener(
-        SDK.DebuggerModel, SDK.DebuggerModel.Events.GlobalObjectCleared, this._updateOverlay, this);
-    SDK.targetManager.addEventListener(SDK.TargetManager.Events.SuspendStateChanged, this._updateAllOverlays, this);
-  }
-
-  _updateAllOverlays() {
-    for (var debuggerModel of SDK.targetManager.models(SDK.DebuggerModel))
-      this._updateTargetOverlay(debuggerModel);
-  }
-
-  /**
-   * @param {!Common.Event} event
-   */
-  _updateOverlay(event) {
-    this._updateTargetOverlay(/** @type {!SDK.DebuggerModel} */ (event.data));
-  }
-
-  /**
-   * @param {!SDK.DebuggerModel} debuggerModel
-   */
-  _updateTargetOverlay(debuggerModel) {
-    if (!debuggerModel.target().hasBrowserCapability())
-      return;
-    var message = debuggerModel.isPaused() && !Common.moduleSetting('disablePausedStateOverlay').get() ?
-        Common.UIString('Paused in debugger') :
-        undefined;
-    debuggerModel.target().pageAgent().configureOverlay(SDK.targetManager.allTargetsSuspended(), message);
-  }
-};
diff --git a/third_party/WebKit/Source/devtools/front_end/main/RenderingOptions.js b/third_party/WebKit/Source/devtools/front_end/main/RenderingOptions.js
index 8613dec..521a8a8 100644
--- a/third_party/WebKit/Source/devtools/front_end/main/RenderingOptions.js
+++ b/third_party/WebKit/Source/devtools/front_end/main/RenderingOptions.js
@@ -36,35 +36,22 @@
     super(true);
     this.registerRequiredCSS('main/renderingOptions.css');
 
-    /** @type {!Map.<string, !Element>} */
-    this._settings = new Map();
-
-    var options = [
-      {
-        label: Common.UIString('Paint Flashing'),
-        subtitle: Common.UIString('Highlights areas of the page (green) that need to be repainted'),
-        setterName: 'setShowPaintRects'
-      },
-      {
-        label: Common.UIString('Layer Borders'),
-        subtitle: Common.UIString('Shows layer borders (orange/olive) and tiles (cyan)'),
-        setterName: 'setShowDebugBorders'
-      },
-      {
-        label: Common.UIString('FPS Meter'),
-        subtitle: Common.UIString('Plots frames per second, frame rate distribution, and GPU memory'),
-        setterName: 'setShowFPSCounter'
-      },
-      {
-        label: Common.UIString('Scrolling Performance Issues'),
-        subtitle: Common.UIString(
+    this._appendCheckbox(
+        Common.UIString('Paint Flashing'),
+        Common.UIString('Highlights areas of the page (green) that need to be repainted'),
+        Common.moduleSetting('showPaintRects'));
+    this._appendCheckbox(
+        Common.UIString('Layer Borders'), Common.UIString('Shows layer borders (orange/olive) and tiles (cyan)'),
+        Common.moduleSetting('showDebugBorders'));
+    this._appendCheckbox(
+        Common.UIString('FPS Meter'),
+        Common.UIString('Plots frames per second, frame rate distribution, and GPU memory'),
+        Common.moduleSetting('showFPSCounter'));
+    this._appendCheckbox(
+        Common.UIString('Scrolling Performance Issues'),
+        Common.UIString(
             'Highlights elements (teal) that can slow down scrolling, including touch & wheel event handlers and other main-thread scrolling situations.'),
-        setterName: 'setShowScrollBottleneckRects'
-      }
-    ];
-    for (var i = 0; i < options.length; i++)
-      this._appendCheckbox(options[i].label, options[i].setterName, options[i].subtitle);
-
+        Common.moduleSetting('showScrollBottleneckRects'));
     this.contentElement.createChild('div').classList.add('panel-section-separator');
 
     var cssMediaSubtitle = Common.UIString('Forces media type for testing print and screen styles');
@@ -80,7 +67,7 @@
     this._mediaSelect.addEventListener('change', this._mediaToggled.bind(this), false);
     this._mediaSelect.disabled = true;
 
-    SDK.targetManager.observeTargets(this, SDK.Target.Capability.Browser);
+    SDK.targetManager.observeTargets(this);
   }
 
   /**
@@ -94,35 +81,21 @@
 
   /**
    * @param {string} label
-   * @param {string} setterName
-   * @param {string=} subtitle
+   * @param {string} subtitle
+   * @param {!Common.Setting} setting
    */
-  _appendCheckbox(label, setterName, subtitle) {
+  _appendCheckbox(label, subtitle, setting) {
     var checkboxLabel = UI.CheckboxLabel.create(label, false, subtitle);
-    this._settings.set(setterName, checkboxLabel.checkboxElement);
-    checkboxLabel.checkboxElement.addEventListener('click', this._settingToggled.bind(this, setterName));
+    UI.SettingsUI.bindCheckbox(checkboxLabel.checkboxElement, setting);
     this.contentElement.appendChild(checkboxLabel);
   }
 
   /**
-   * @param {string} setterName
-   */
-  _settingToggled(setterName) {
-    var enabled = this._settings.get(setterName).checked;
-    for (var target of SDK.targetManager.targets(SDK.Target.Capability.Browser))
-      target.renderingAgent()[setterName](enabled);
-  }
-
-  /**
    * @override
    * @param {!SDK.Target} target
    */
   targetAdded(target) {
-    for (var setterName of this._settings.keysArray()) {
-      if (this._settings.get(setterName).checked)
-        target.renderingAgent()[setterName](true);
-    }
-    if (this._mediaCheckbox.checked)
+    if (this._mediaCheckbox.checked && target.hasBrowserCapability())
       this._applyPrintMediaOverride(target);
   }
 
diff --git a/third_party/WebKit/Source/devtools/front_end/main/module.json b/third_party/WebKit/Source/devtools/front_end/main/module.json
index 9e5a909..c07180d6 100644
--- a/third_party/WebKit/Source/devtools/front_end/main/module.json
+++ b/third_party/WebKit/Source/devtools/front_end/main/module.json
@@ -450,7 +450,6 @@
     "scripts": [
         "RenderingOptions.js",
         "SimpleApp.js",
-        "OverlayController.js",
         "GCActionDelegate.js",
         "RequestAppBannerActionDelegate.js",
         "ExecutionContextSelector.js",
diff --git a/third_party/WebKit/Source/devtools/front_end/object_ui/ObjectPopoverHelper.js b/third_party/WebKit/Source/devtools/front_end/object_ui/ObjectPopoverHelper.js
index 29c1533..40ed2d4 100644
--- a/third_party/WebKit/Source/devtools/front_end/object_ui/ObjectPopoverHelper.js
+++ b/third_party/WebKit/Source/devtools/front_end/object_ui/ObjectPopoverHelper.js
@@ -40,7 +40,7 @@
 
   dispose() {
     if (this._resultHighlightedAsDOM)
-      SDK.DOMModel.hideDOMNodeHighlight();
+      SDK.OverlayModel.hideDOMNodeHighlight();
     if (this._linkifier)
       this._linkifier.dispose();
   }
@@ -131,7 +131,7 @@
       var linkifier = null;
       var resultHighlightedAsDOM = false;
       if (result.subtype === 'node') {
-        SDK.DOMModel.highlightObjectAsDOMNode(result);
+        SDK.OverlayModel.highlightObjectAsDOMNode(result);
         resultHighlightedAsDOM = true;
       }
 
diff --git a/third_party/WebKit/Source/devtools/front_end/object_ui/ObjectPropertiesSection.js b/third_party/WebKit/Source/devtools/front_end/object_ui/ObjectPropertiesSection.js
index 4efb4dc..c5fa5aa 100644
--- a/third_party/WebKit/Source/devtools/front_end/object_ui/ObjectPropertiesSection.js
+++ b/third_party/WebKit/Source/devtools/front_end/object_ui/ObjectPropertiesSection.js
@@ -306,8 +306,8 @@
         Common.Revealer.reveal(value);
         event.consume(true);
       }, false);
-      valueElement.addEventListener('mousemove', () => SDK.DOMModel.highlightObjectAsDOMNode(value), false);
-      valueElement.addEventListener('mouseleave', () => SDK.DOMModel.hideDOMNodeHighlight(), false);
+      valueElement.addEventListener('mousemove', () => SDK.OverlayModel.highlightObjectAsDOMNode(value), false);
+      valueElement.addEventListener('mouseleave', () => SDK.OverlayModel.hideDOMNodeHighlight(), false);
       return valueElement;
     }
 
diff --git a/third_party/WebKit/Source/devtools/front_end/protocol/InspectorBackend.js b/third_party/WebKit/Source/devtools/front_end/protocol/InspectorBackend.js
index e444f2d3..8dffb45 100644
--- a/third_party/WebKit/Source/devtools/front_end/protocol/InspectorBackend.js
+++ b/third_party/WebKit/Source/devtools/front_end/protocol/InspectorBackend.js
@@ -29,7 +29,7 @@
  */
 
 /** @typedef {string} */
-Protocol.Error;
+Protocol.Error = Symbol('Protocol.Error');
 
 /**
  * @unrestricted
@@ -513,12 +513,12 @@
     this[methodName] = sendMessagePromise;
 
     /**
-     * @param {...*} vararg
+     * @param {!Object} request
+     * @return {!Promise}
      * @this {Protocol.InspectorBackend._AgentPrototype}
      */
-    function invoke(vararg) {
-      var params = [domainAndMethod].concat(Array.prototype.slice.call(arguments));
-      Protocol.InspectorBackend._AgentPrototype.prototype._invoke.apply(this, params);
+    function invoke(request) {
+      return this._invoke(domainAndMethod, request);
     }
 
     this['invoke_' + methodName] = invoke;
@@ -584,9 +584,9 @@
 
   /**
    * @param {string} method
-   * @param {!Array.<!Object>} signature
-   * @param {!Array.<*>} args
-   * @return {!Promise.<*>}
+   * @param {!Array<!Object>} signature
+   * @param {!Array<*>} args
+   * @return {!Promise<*>}
    */
   _sendMessageToBackendPromise(method, signature, args) {
     var errorMessage;
@@ -599,47 +599,62 @@
     }
     var userCallback = (args.length && typeof args.peekLast() === 'function') ? args.pop() : null;
     var params = this._prepareParameters(method, signature, args, !userCallback, onError);
-    var responseArguments;
-    if (errorMessage) {
-      responseArguments = [errorMessage];
-      return Promise.resolve().then(runUserCallback);
-    }
+    if (errorMessage)
+      return Promise.resolve([errorMessage]).then(runUserCallback);
     return new Promise(promiseAction.bind(this)).then(runUserCallback);
 
     /**
-     * @param {function()} resolve
-     * @param {function(!Error)} reject
+     * @param {function(!Array<*>)} resolve
      * @this {Protocol.InspectorBackend._AgentPrototype}
      */
-    function promiseAction(resolve, reject) {
+    function promiseAction(resolve) {
       /**
-       * @param {...*} vararg
+       * @param {?Protocol.Error} error
+       * @param {?Object} result
+       * @this {Protocol.InspectorBackend._AgentPrototype}
        */
-      function callback(vararg) {
-        responseArguments = arguments;
-        resolve();
+      function prepareArgsAndResolve(error, result) {
+        var argumentsArray = [error ? error.message : null];
+        if (this._hasErrorData[method])
+          argumentsArray.push(error ? error.data : null);
+        if (result) {
+          for (var paramName of this._replyArgs[method] || [])
+            argumentsArray.push(result[paramName]);
+        }
+        resolve(argumentsArray);
       }
-      this._target._wrapCallbackAndSendMessageObject(this._domain, method, params, callback);
+      this._target._wrapCallbackAndSendMessageObject(this._domain, method, params, prepareArgsAndResolve.bind(this));
     }
 
-    function runUserCallback() {
-      return userCallback ? userCallback.apply(null, responseArguments) : undefined;
+    /**
+     * @param {!Array<*>} args
+     */
+    function runUserCallback(args) {
+      return userCallback ? userCallback.apply(null, args) : undefined;
     }
   }
 
   /**
    * @param {string} method
-   * @param {?Object} args
-   * @param {?function(*)} callback
+   * @param {?Object} request
+   * @return {!Promise<!Object>}
    */
-  _invoke(method, args, callback) {
-    this._target._wrapCallbackAndSendMessageObject(this._domain, method, args, callback);
+  _invoke(method, request) {
+    return new Promise(fulfill => {
+      this._target._wrapCallbackAndSendMessageObject(this._domain, method, request, (error, result) => {
+        if (!result)
+          result = {};
+        if (error)
+          result[Protocol.Error] = error.message;
+        fulfill(result);
+      });
+    });
   }
 
   /**
    * @param {!Object} messageObject
    * @param {string} methodName
-   * @param {function(*)|function(?Protocol.Error, ?Object)} callback
+   * @param {function(?Protocol.Error, ?Object)} callback
    */
   dispatchResponse(messageObject, methodName, callback) {
     if (messageObject.error && messageObject.error.code !== Protocol.InspectorBackend._ConnectionClosedErrorCode &&
@@ -648,20 +663,7 @@
       var id = Protocol.InspectorBackend.Options.dumpInspectorProtocolMessages ? ' with id = ' + messageObject.id : '';
       console.error('Request ' + methodName + id + ' failed. ' + JSON.stringify(messageObject.error));
     }
-
-    var argumentsArray = [];
-    argumentsArray[0] = messageObject.error ? messageObject.error.message : null;
-
-    if (this._hasErrorData[methodName])
-      argumentsArray[1] = messageObject.error ? messageObject.error.data : null;
-
-    if (messageObject.result) {
-      var paramNames = this._replyArgs[methodName] || [];
-      for (var i = 0; i < paramNames.length; ++i)
-        argumentsArray.push(messageObject.result[paramNames[i]]);
-    }
-
-    callback.apply(null, argumentsArray);
+    callback(messageObject.error, messageObject.result);
   }
 };
 
diff --git a/third_party/WebKit/Source/devtools/front_end/resources/ResourcesSection.js b/third_party/WebKit/Source/devtools/front_end/resources/ResourcesSection.js
index 047cb83..2ffe0362 100644
--- a/third_party/WebKit/Source/devtools/front_end/resources/ResourcesSection.js
+++ b/third_party/WebKit/Source/devtools/front_end/resources/ResourcesSection.js
@@ -155,18 +155,17 @@
     this._panel.showCategoryView(this.titleAsText());
 
     this.listItemElement.classList.remove('hovered');
-    SDK.DOMModel.hideDOMNodeHighlight();
+    SDK.OverlayModel.hideDOMNodeHighlight();
     return false;
   }
 
   set hovered(hovered) {
     if (hovered) {
       this.listItemElement.classList.add('hovered');
-      var domModel = this._frame.resourceTreeModel().domModel();
-      domModel.highlightFrame(this._frameId);
+      this._frame.resourceTreeModel().domModel().overlayModel().highlightFrame(this._frameId);
     } else {
       this.listItemElement.classList.remove('hovered');
-      SDK.DOMModel.hideDOMNodeHighlight();
+      SDK.OverlayModel.hideDOMNodeHighlight();
     }
   }
 
diff --git a/third_party/WebKit/Source/devtools/front_end/screencast/ScreencastView.js b/third_party/WebKit/Source/devtools/front_end/screencast/ScreencastView.js
index 91bbe66..a1250e9 100644
--- a/third_party/WebKit/Source/devtools/front_end/screencast/ScreencastView.js
+++ b/third_party/WebKit/Source/devtools/front_end/screencast/ScreencastView.js
@@ -28,7 +28,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 /**
- * @implements {SDK.DOMNodeHighlighter}
+ * @implements {SDK.OverlayModel.Highlighter}
  * @unrestricted
  */
 Screencast.ScreencastView = class extends UI.VBox {
@@ -39,6 +39,7 @@
     super();
     this._screenCaptureModel = screenCaptureModel;
     this._domModel = screenCaptureModel.target().model(SDK.DOMModel);
+    this._overlayModel = screenCaptureModel.target().model(SDK.OverlayModel);
     this._resourceTreeModel = screenCaptureModel.target().model(SDK.ResourceTreeModel);
     this._networkManager = screenCaptureModel.target().model(SDK.NetworkManager);
     this._inputModel = screenCaptureModel.target().model(Screencast.InputModel);
@@ -130,8 +131,8 @@
         Math.floor(Math.min(maxImageDimension, dimensions.height)), undefined, this._screencastFrame.bind(this),
         this._screencastVisibilityChanged.bind(this));
     Emulation.MultitargetTouchModel.instance().setCustomTouchEnabled(true);
-    if (this._domModel)
-      this._domModel.setHighlighter(this);
+    if (this._overlayModel)
+      this._overlayModel.setHighlighter(this);
   }
 
   _stopCasting() {
@@ -140,8 +141,8 @@
     this._isCasting = false;
     this._screenCaptureModel.stopScreencast();
     Emulation.MultitargetTouchModel.instance().setCustomTouchEnabled(false);
-    if (this._domModel)
-      this._domModel.setHighlighter(null);
+    if (this._overlayModel)
+      this._overlayModel.setHighlighter(null);
   }
 
   /**
@@ -245,7 +246,7 @@
         return;
       if (event.type === 'mousemove') {
         this.highlightDOMNode(node, this._inspectModeConfig);
-        this._domModel.nodeHighlightRequested(node.id);
+        this._domModel.overlayModel().nodeHighlightRequested(node.id);
       } else if (event.type === 'click') {
         Common.Revealer.reveal(node);
       }
@@ -316,7 +317,7 @@
   /**
    * @override
    * @param {?SDK.DOMNode} node
-   * @param {?Protocol.DOM.HighlightConfig} config
+   * @param {?Protocol.Overlay.HighlightConfig} config
    * @param {!Protocol.DOM.BackendNodeId=} backendNodeId
    * @param {!Protocol.Runtime.RemoteObjectId=} objectId
    */
@@ -569,14 +570,13 @@
 
   /**
    * @override
-   * @param {!Protocol.DOM.InspectMode} mode
-   * @param {!Protocol.DOM.HighlightConfig} config
-   * @param {function(?Protocol.Error)=} callback
+   * @param {!Protocol.Overlay.InspectMode} mode
+   * @param {!Protocol.Overlay.HighlightConfig} config
+   * @return {!Promise}
    */
-  setInspectMode(mode, config, callback) {
-    this._inspectModeConfig = mode !== Protocol.DOM.InspectMode.None ? config : null;
-    if (callback)
-      callback(null);
+  setInspectMode(mode, config) {
+    this._inspectModeConfig = mode !== Protocol.Overlay.InspectMode.None ? config : null;
+    return Promise.resolve();
   }
 
   /**
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/DOMDebuggerModel.js b/third_party/WebKit/Source/devtools/front_end/sdk/DOMDebuggerModel.js
index 42b836a0..a87bea1 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/DOMDebuggerModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/DOMDebuggerModel.js
@@ -260,72 +260,332 @@
   FrameworkUser: 'FrameworkUser'
 };
 
+SDK.DOMDebuggerModel.EventListenerBreakpoint = class {
+  /**
+   * @param {string} instrumentationName
+   * @param {string} eventName
+   * @param {!Array<string>} eventTargetNames
+   * @param {string} category
+   * @param {string} title
+   */
+  constructor(instrumentationName, eventName, eventTargetNames, category, title) {
+    this._instrumentationName = instrumentationName;
+    this._eventName = eventName;
+    this._eventTargetNames = eventTargetNames;
+    this._category = category;
+    this._title = title;
+    this._enabled = false;
+  }
+
+  /**
+   * @return {string}
+   */
+  category() {
+    return this._category;
+  }
+
+  /**
+   * @return {boolean}
+   */
+  enabled() {
+    return this._enabled;
+  }
+
+  /**
+   * @param {boolean} enabled
+   */
+  setEnabled(enabled) {
+    if (this._enabled === enabled)
+      return;
+    this._enabled = enabled;
+    for (var model of SDK.targetManager.models(SDK.DOMDebuggerModel))
+      this._updateOnModel(model);
+  }
+
+  /**
+   * @param {!SDK.DOMDebuggerModel} model
+   */
+  _updateOnModel(model) {
+    if (this._instrumentationName) {
+      if (this._enabled)
+        model._agent.setInstrumentationBreakpoint(this._instrumentationName);
+      else
+        model._agent.removeInstrumentationBreakpoint(this._instrumentationName);
+    } else {
+      for (var eventTargetName of this._eventTargetNames) {
+        if (this._enabled)
+          model._agent.setEventListenerBreakpoint(this._eventName, eventTargetName);
+        else
+          model._agent.removeEventListenerBreakpoint(this._eventName, eventTargetName);
+      }
+    }
+  }
+
+  /**
+   * @return {string}
+   */
+  title() {
+    return this._title;
+  }
+};
+
+SDK.DOMDebuggerModel.EventListenerBreakpoint._listener = 'listener:';
+SDK.DOMDebuggerModel.EventListenerBreakpoint._instrumentation = 'instrumentation:';
+
 /**
  * @implements {SDK.SDKModelObserver<!SDK.DOMDebuggerModel>}
  */
-SDK.XHRBreakpointManager = class {
+SDK.DOMDebuggerManager = class {
   constructor() {
-    this._setting = Common.settings.createLocalSetting('xhrBreakpoints', []);
+    this._xhrBreakpointsSetting = Common.settings.createLocalSetting('xhrBreakpoints', []);
     /** @type {!Map<string, boolean>} */
-    this._breakpoints = new Map();
-    for (var breakpoint of this._setting.get())
-      this._breakpoints.set(breakpoint.url, breakpoint.enabled);
+    this._xhrBreakpoints = new Map();
+    for (var breakpoint of this._xhrBreakpointsSetting.get())
+      this._xhrBreakpoints.set(breakpoint.url, breakpoint.enabled);
+
+    /** @type {!Array<!SDK.DOMDebuggerModel.EventListenerBreakpoint>} */
+    this._eventListenerBreakpoints = [];
+    this._createInstrumentationBreakpoints(
+        Common.UIString('Animation'),
+        ['requestAnimationFrame', 'cancelAnimationFrame', 'requestAnimationFrame.callback']);
+    this._createInstrumentationBreakpoints(
+        Common.UIString('Canvas'), ['canvasContextCreated', 'webglErrorFired', 'webglWarningFired']);
+    this._createInstrumentationBreakpoints(
+        Common.UIString('Geolocation'), ['Geolocation.getCurrentPosition', 'Geolocation.watchPosition']);
+    this._createInstrumentationBreakpoints(Common.UIString('Notification'), ['Notification.requestPermission']);
+    this._createInstrumentationBreakpoints(Common.UIString('Parse'), ['Element.setInnerHTML', 'Document.write']);
+    this._createInstrumentationBreakpoints(Common.UIString('Script'), ['scriptFirstStatement', 'scriptBlockedByCSP']);
+    this._createInstrumentationBreakpoints(
+        Common.UIString('Timer'),
+        ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'setTimeout.callback', 'setInterval.callback']);
+    this._createInstrumentationBreakpoints(Common.UIString('Window'), ['DOMWindow.close']);
+
+    this._createEventListenerBreakpoints(
+        Common.UIString('Media'),
+        [
+          'play',      'pause',          'playing',    'canplay',    'canplaythrough', 'seeking',
+          'seeked',    'timeupdate',     'ended',      'ratechange', 'durationchange', 'volumechange',
+          'loadstart', 'progress',       'suspend',    'abort',      'error',          'emptied',
+          'stalled',   'loadedmetadata', 'loadeddata', 'waiting'
+        ],
+        ['audio', 'video']);
+    this._createEventListenerBreakpoints(
+        Common.UIString('Clipboard'), ['copy', 'cut', 'paste', 'beforecopy', 'beforecut', 'beforepaste'], ['*']);
+    this._createEventListenerBreakpoints(
+        Common.UIString('Control'),
+        ['resize', 'scroll', 'zoom', 'focus', 'blur', 'select', 'change', 'submit', 'reset'], ['*']);
+    this._createEventListenerBreakpoints(Common.UIString('Device'), ['deviceorientation', 'devicemotion'], ['*']);
+    this._createEventListenerBreakpoints(
+        Common.UIString('DOM Mutation'),
+        [
+          'DOMActivate', 'DOMFocusIn', 'DOMFocusOut', 'DOMAttrModified', 'DOMCharacterDataModified', 'DOMNodeInserted',
+          'DOMNodeInsertedIntoDocument', 'DOMNodeRemoved', 'DOMNodeRemovedFromDocument', 'DOMSubtreeModified',
+          'DOMContentLoaded'
+        ],
+        ['*']);
+    this._createEventListenerBreakpoints(
+        Common.UIString('Drag / drop'), ['dragenter', 'dragover', 'dragleave', 'drop'], ['*']);
+    this._createEventListenerBreakpoints(Common.UIString('Keyboard'), ['keydown', 'keyup', 'keypress', 'input'], ['*']);
+    this._createEventListenerBreakpoints(
+        Common.UIString('Load'), ['load', 'beforeunload', 'unload', 'abort', 'error', 'hashchange', 'popstate'], ['*']);
+    this._createEventListenerBreakpoints(
+        Common.UIString('Mouse'),
+        [
+          'auxclick', 'click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mousemove', 'mouseout', 'mouseenter',
+          'mouseleave', 'mousewheel', 'wheel', 'contextmenu'
+        ],
+        ['*']);
+    this._createEventListenerBreakpoints(
+        Common.UIString('Pointer'),
+        [
+          'pointerover', 'pointerout', 'pointerenter', 'pointerleave', 'pointerdown', 'pointerup', 'pointermove',
+          'pointercancel', 'gotpointercapture', 'lostpointercapture'
+        ],
+        ['*']);
+    this._createEventListenerBreakpoints(
+        Common.UIString('Touch'), ['touchstart', 'touchmove', 'touchend', 'touchcancel'], ['*']);
+    this._createEventListenerBreakpoints(
+        Common.UIString('XHR'),
+        ['readystatechange', 'load', 'loadstart', 'loadend', 'abort', 'error', 'progress', 'timeout'],
+        ['xmlhttprequest', 'xmlhttprequestupload']);
+
+    this._resolveEventListenerBreakpoint('instrumentation:setTimeout.callback')._title =
+        Common.UIString('setTimeout fired');
+    this._resolveEventListenerBreakpoint('instrumentation:setInterval.callback')._title =
+        Common.UIString('setInterval fired');
+    this._resolveEventListenerBreakpoint('instrumentation:scriptFirstStatement')._title =
+        Common.UIString('Script First Statement');
+    this._resolveEventListenerBreakpoint('instrumentation:scriptBlockedByCSP')._title =
+        Common.UIString('Script Blocked by Content Security Policy');
+    this._resolveEventListenerBreakpoint('instrumentation:requestAnimationFrame')._title =
+        Common.UIString('Request Animation Frame');
+    this._resolveEventListenerBreakpoint('instrumentation:cancelAnimationFrame')._title =
+        Common.UIString('Cancel Animation Frame');
+    this._resolveEventListenerBreakpoint('instrumentation:requestAnimationFrame.callback')._title =
+        Common.UIString('Animation Frame Fired');
+    this._resolveEventListenerBreakpoint('instrumentation:webglErrorFired')._title =
+        Common.UIString('WebGL Error Fired');
+    this._resolveEventListenerBreakpoint('instrumentation:webglWarningFired')._title =
+        Common.UIString('WebGL Warning Fired');
+    this._resolveEventListenerBreakpoint('instrumentation:Element.setInnerHTML')._title =
+        Common.UIString('Set innerHTML');
+    this._resolveEventListenerBreakpoint('instrumentation:canvasContextCreated')._title =
+        Common.UIString('Create canvas context');
+    this._resolveEventListenerBreakpoint('instrumentation:Geolocation.getCurrentPosition')._title =
+        'getCurrentPosition';
+    this._resolveEventListenerBreakpoint('instrumentation:Geolocation.watchPosition')._title = 'watchPosition';
+    this._resolveEventListenerBreakpoint('instrumentation:Notification.requestPermission')._title = 'requestPermission';
+    this._resolveEventListenerBreakpoint('instrumentation:DOMWindow.close')._title = 'window.close';
+    this._resolveEventListenerBreakpoint('instrumentation:Document.write')._title = 'document.write';
+
     SDK.targetManager.observeModels(SDK.DOMDebuggerModel, this);
   }
 
   /**
+   * @param {string} category
+   * @param {!Array<string>} instrumentationNames
+   */
+  _createInstrumentationBreakpoints(category, instrumentationNames) {
+    for (var instrumentationName of instrumentationNames) {
+      this._eventListenerBreakpoints.push(
+          new SDK.DOMDebuggerModel.EventListenerBreakpoint(instrumentationName, '', [], category, instrumentationName));
+    }
+  }
+
+  /**
+   * @param {string} category
+   * @param {!Array<string>} eventNames
+   * @param {!Array<string>} eventTargetNames
+   */
+  _createEventListenerBreakpoints(category, eventNames, eventTargetNames) {
+    for (var eventName of eventNames) {
+      this._eventListenerBreakpoints.push(
+          new SDK.DOMDebuggerModel.EventListenerBreakpoint('', eventName, eventTargetNames, category, eventName));
+    }
+  }
+
+  /**
+   * @param {string} eventName
+   * @param {string=} eventTargetName
+   * @return {?SDK.DOMDebuggerModel.EventListenerBreakpoint}
+   */
+  _resolveEventListenerBreakpoint(eventName, eventTargetName) {
+    var instrumentationPrefix = 'instrumentation:';
+    var listenerPrefix = 'listener:';
+    var instrumentationName = '';
+    if (eventName.startsWith(instrumentationPrefix)) {
+      instrumentationName = eventName.substring(instrumentationPrefix.length);
+      eventName = '';
+    } else if (eventName.startsWith(listenerPrefix)) {
+      eventName = eventName.substring(listenerPrefix.length);
+    } else {
+      return null;
+    }
+    eventTargetName = (eventTargetName || '*').toLowerCase();
+    var result = null;
+    for (var breakpoint of this._eventListenerBreakpoints) {
+      if (instrumentationName && breakpoint._instrumentationName === instrumentationName)
+        result = breakpoint;
+      if (eventName && breakpoint._eventName === eventName &&
+          breakpoint._eventTargetNames.indexOf(eventTargetName) !== -1)
+        result = breakpoint;
+      if (!result && eventName && breakpoint._eventName === eventName &&
+          breakpoint._eventTargetNames.indexOf('*') !== -1)
+        result = breakpoint;
+    }
+    return result;
+  }
+
+  /**
+   * @return {!Array<!SDK.DOMDebuggerModel.EventListenerBreakpoint>}
+   */
+  eventListenerBreakpoints() {
+    return this._eventListenerBreakpoints.slice();
+  }
+
+  /**
+   * @param {!Object} auxData
+   * @return {string}
+   */
+  resolveEventListenerBreakpointTitle(auxData) {
+    var id = auxData['eventName'];
+    if (id === 'instrumentation:webglErrorFired' && auxData['webglErrorName']) {
+      var errorName = auxData['webglErrorName'];
+      // If there is a hex code of the error, display only this.
+      errorName = errorName.replace(/^.*(0x[0-9a-f]+).*$/i, '$1');
+      return Common.UIString('WebGL Error Fired (%s)', errorName);
+    }
+    if (id === 'instrumentation:scriptBlockedByCSP' && auxData['directiveText'])
+      return Common.UIString('Script blocked due to Content Security Policy directive: %s', auxData['directiveText']);
+    var breakpoint = this._resolveEventListenerBreakpoint(id, auxData['targetName']);
+    if (!breakpoint)
+      return '';
+    if (auxData['targetName'])
+      return auxData['targetName'] + '.' + breakpoint._title;
+    return breakpoint._title;
+  }
+
+  /**
+   * @param {!Object} auxData
+   * @return {?SDK.DOMDebuggerModel.EventListenerBreakpoint}
+   */
+  resolveEventListenerBreakpoint(auxData) {
+    return this._resolveEventListenerBreakpoint(auxData['eventName'], auxData['targetName']);
+  }
+
+  /**
    * @return {!Map<string, boolean>}
    */
-  breakpoints() {
-    return this._breakpoints;
+  xhrBreakpoints() {
+    return this._xhrBreakpoints;
   }
 
-  _saveBreakpoints() {
+  _saveXHRBreakpoints() {
     var breakpoints = [];
-    for (var url of this._breakpoints.keys())
-      breakpoints.push({url: url, enabled: this._breakpoints.get(url)});
-    this._setting.set(breakpoints);
+    for (var url of this._xhrBreakpoints.keys())
+      breakpoints.push({url: url, enabled: this._xhrBreakpoints.get(url)});
+    this._xhrBreakpointsSetting.set(breakpoints);
   }
 
   /**
    * @param {string} url
    * @param {boolean} enabled
    */
-  addBreakpoint(url, enabled) {
-    this._breakpoints.set(url, enabled);
+  addXHRBreakpoint(url, enabled) {
+    this._xhrBreakpoints.set(url, enabled);
     if (enabled) {
       for (var model of SDK.targetManager.models(SDK.DOMDebuggerModel))
         model._agent.setXHRBreakpoint(url);
     }
-    this._saveBreakpoints();
+    this._saveXHRBreakpoints();
   }
 
   /**
    * @param {string} url
    */
-  removeBreakpoint(url) {
-    var enabled = this._breakpoints.get(url);
-    this._breakpoints.delete(url);
+  removeXHRBreakpoint(url) {
+    var enabled = this._xhrBreakpoints.get(url);
+    this._xhrBreakpoints.delete(url);
     if (enabled) {
       for (var model of SDK.targetManager.models(SDK.DOMDebuggerModel))
         model._agent.removeXHRBreakpoint(url);
     }
-    this._saveBreakpoints();
+    this._saveXHRBreakpoints();
   }
 
   /**
    * @param {string} url
    * @param {boolean} enabled
    */
-  toggleBreakpoint(url, enabled) {
-    this._breakpoints.set(url, enabled);
+  toggleXHRBreakpoint(url, enabled) {
+    this._xhrBreakpoints.set(url, enabled);
     for (var model of SDK.targetManager.models(SDK.DOMDebuggerModel)) {
       if (enabled)
         model._agent.setXHRBreakpoint(url);
       else
         model._agent.removeXHRBreakpoint(url);
     }
-    this._saveBreakpoints();
+    this._saveXHRBreakpoints();
   }
 
   /**
@@ -333,10 +593,14 @@
    * @param {!SDK.DOMDebuggerModel} domDebuggerModel
    */
   modelAdded(domDebuggerModel) {
-    for (var url of this._breakpoints.keys()) {
-      if (this._breakpoints.get(url))
+    for (var url of this._xhrBreakpoints.keys()) {
+      if (this._xhrBreakpoints.get(url))
         domDebuggerModel._agent.setXHRBreakpoint(url);
     }
+    for (var breakpoint of this._eventListenerBreakpoints) {
+      if (breakpoint._enabled)
+        breakpoint._updateOnModel(domDebuggerModel);
+    }
   }
 
   /**
@@ -347,5 +611,5 @@
   }
 };
 
-/** @type {!SDK.XHRBreakpointManager} */
-SDK.xhrBreakpointManager;
+/** @type {!SDK.DOMDebuggerManager} */
+SDK.domDebuggerManager;
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/DOMModel.js b/third_party/WebKit/Source/devtools/front_end/sdk/DOMModel.js
index 7780a00..8457991 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/DOMModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/DOMModel.js
@@ -840,11 +840,11 @@
    * @param {!Protocol.Runtime.RemoteObjectId=} objectId
    */
   highlight(mode, objectId) {
-    this._domModel.highlightDOMNode(this.id, mode, undefined, objectId);
+    this._domModel.overlayModel().highlightDOMNode(this.id, mode, undefined, objectId);
   }
 
   highlightForTwoSeconds() {
-    this._domModel.highlightDOMNodeForTwoSeconds(this.id);
+    this._domModel.overlayModel().highlightDOMNodeForTwoSeconds(this.id);
   }
 
   /**
@@ -1011,7 +1011,7 @@
 
   highlight() {
     if (this._domModel)
-      this._domModel.highlightDOMNode(undefined, undefined, this._backendNodeId);
+      this._domModel.overlayModel().highlightDOMNode(undefined, undefined, this._backendNodeId);
   }
 };
 
@@ -1068,12 +1068,8 @@
     this._attributeLoadNodeIds = {};
     target.registerDOMDispatcher(new SDK.DOMDispatcher(this));
 
-    this._inspectModeEnabled = false;
     this._runtimeModel = /** @type {!SDK.RuntimeModel} */ (target.model(SDK.RuntimeModel));
 
-    this._defaultHighlighter = new SDK.DefaultDOMNodeHighlighter(this._agent);
-    this._highlighter = this._defaultHighlighter;
-
     this._agent.enable();
   }
 
@@ -1092,26 +1088,10 @@
   }
 
   /**
-   * @param {!SDK.RemoteObject} object
+   * @return {!SDK.OverlayModel}
    */
-  static highlightObjectAsDOMNode(object) {
-    var domModel = object.runtimeModel().target().model(SDK.DOMModel);
-    if (domModel)
-      domModel.highlightDOMNode(undefined, undefined, undefined, object.objectId);
-  }
-
-  static hideDOMNodeHighlight() {
-    for (var domModel of SDK.targetManager.models(SDK.DOMModel))
-      domModel.highlightDOMNode(0);
-  }
-
-  static muteHighlight() {
-    SDK.DOMModel.hideDOMNodeHighlight();
-    SDK.DOMModel._highlightDisabled = true;
-  }
-
-  static unmuteHighlight() {
-    SDK.DOMModel._highlightDisabled = false;
+  overlayModel() {
+    return /** @type {!SDK.OverlayModel} */ (this.target().model(SDK.OverlayModel));
   }
 
   static cancelSearch() {
@@ -1539,14 +1519,6 @@
   }
 
   /**
-   * @param {!Protocol.DOM.BackendNodeId} backendNodeId
-   */
-  _inspectNodeRequested(backendNodeId) {
-    var deferredNode = new SDK.DeferredDOMNode(this.target(), backendNodeId);
-    this.dispatchEventToListeners(SDK.DOMModel.Events.NodeInspected, deferredNode);
-  }
-
-  /**
    * @param {string} query
    * @param {boolean} includeUserAgentShadowDOM
    * @param {function(number)} searchCallback
@@ -1677,107 +1649,6 @@
   }
 
   /**
-   * @param {!Protocol.DOM.NodeId=} nodeId
-   * @param {string=} mode
-   * @param {!Protocol.DOM.BackendNodeId=} backendNodeId
-   * @param {!Protocol.Runtime.RemoteObjectId=} objectId
-   */
-  highlightDOMNode(nodeId, mode, backendNodeId, objectId) {
-    this.highlightDOMNodeWithConfig(nodeId, {mode: mode}, backendNodeId, objectId);
-  }
-
-  /**
-   * @param {!Protocol.DOM.NodeId=} nodeId
-   * @param {!{mode: (string|undefined), showInfo: (boolean|undefined), selectors: (string|undefined)}=} config
-   * @param {!Protocol.DOM.BackendNodeId=} backendNodeId
-   * @param {!Protocol.Runtime.RemoteObjectId=} objectId
-   */
-  highlightDOMNodeWithConfig(nodeId, config, backendNodeId, objectId) {
-    if (SDK.DOMModel._highlightDisabled)
-      return;
-    config = config || {mode: 'all', showInfo: undefined, selectors: undefined};
-    if (this._hideDOMNodeHighlightTimeout) {
-      clearTimeout(this._hideDOMNodeHighlightTimeout);
-      delete this._hideDOMNodeHighlightTimeout;
-    }
-    var highlightConfig = this._buildHighlightConfig(config.mode);
-    if (typeof config.showInfo !== 'undefined')
-      highlightConfig.showInfo = config.showInfo;
-    if (typeof config.selectors !== 'undefined')
-      highlightConfig.selectorList = config.selectors;
-    this._highlighter.highlightDOMNode(this.nodeForId(nodeId || 0), highlightConfig, backendNodeId, objectId);
-  }
-
-  /**
-   * @param {!Protocol.DOM.NodeId} nodeId
-   */
-  highlightDOMNodeForTwoSeconds(nodeId) {
-    this.highlightDOMNode(nodeId);
-    this._hideDOMNodeHighlightTimeout = setTimeout(SDK.DOMModel.hideDOMNodeHighlight.bind(SDK.DOMModel), 2000);
-  }
-
-  /**
-   * @param {!Protocol.Page.FrameId} frameId
-   */
-  highlightFrame(frameId) {
-    if (SDK.DOMModel._highlightDisabled)
-      return;
-    this._highlighter.highlightFrame(frameId);
-  }
-
-  /**
-   * @param {!Protocol.DOM.InspectMode} mode
-   * @param {function(?Protocol.Error)=} callback
-   */
-  setInspectMode(mode, callback) {
-    /**
-     * @this {SDK.DOMModel}
-     */
-    function onDocumentAvailable() {
-      this._inspectModeEnabled = mode !== Protocol.DOM.InspectMode.None;
-      this.dispatchEventToListeners(SDK.DOMModel.Events.InspectModeWillBeToggled, this);
-      this._highlighter.setInspectMode(mode, this._buildHighlightConfig(), callback);
-    }
-    this.requestDocument(onDocumentAvailable.bind(this));
-  }
-
-  /**
-   * @return {boolean}
-   */
-  inspectModeEnabled() {
-    return this._inspectModeEnabled;
-  }
-
-  /**
-   * @param {string=} mode
-   * @return {!Protocol.DOM.HighlightConfig}
-   */
-  _buildHighlightConfig(mode) {
-    mode = mode || 'all';
-    var showRulers = Common.moduleSetting('showMetricsRulers').get();
-    var highlightConfig = {showInfo: mode === 'all', showRulers: showRulers, showExtensionLines: showRulers};
-    if (mode === 'all' || mode === 'content')
-      highlightConfig.contentColor = Common.Color.PageHighlight.Content.toProtocolRGBA();
-
-    if (mode === 'all' || mode === 'padding')
-      highlightConfig.paddingColor = Common.Color.PageHighlight.Padding.toProtocolRGBA();
-
-    if (mode === 'all' || mode === 'border')
-      highlightConfig.borderColor = Common.Color.PageHighlight.Border.toProtocolRGBA();
-
-    if (mode === 'all' || mode === 'margin')
-      highlightConfig.marginColor = Common.Color.PageHighlight.Margin.toProtocolRGBA();
-
-    if (mode === 'all') {
-      highlightConfig.eventTargetColor = Common.Color.PageHighlight.EventTarget.toProtocolRGBA();
-      highlightConfig.shapeColor = Common.Color.PageHighlight.Shape.toProtocolRGBA();
-      highlightConfig.shapeMarginColor = Common.Color.PageHighlight.ShapeMargin.toProtocolRGBA();
-      highlightConfig.displayAsMaterial = true;
-    }
-    return highlightConfig;
-  }
-
-  /**
    * @param {!SDK.DOMNode} node
    * @param {function(?Protocol.Error, ...)=} callback
    * @return {function(...)}
@@ -1817,13 +1688,6 @@
   }
 
   /**
-   * @param {?SDK.DOMNodeHighlighter} highlighter
-   */
-  setHighlighter(highlighter) {
-    this._highlighter = highlighter || this._defaultHighlighter;
-  }
-
-  /**
    * @param {number} x
    * @param {number} y
    * @param {boolean} includeUserAgentShadowDOM
@@ -1898,17 +1762,6 @@
       this._agent.enable(fulfill);
     }
   }
-
-  /**
-   * @param {!Protocol.DOM.NodeId} nodeId
-   */
-  nodeHighlightRequested(nodeId) {
-    var node = this.nodeForId(nodeId);
-    if (!node)
-      return;
-
-    this.dispatchEventToListeners(SDK.DOMModel.Events.NodeHighlightedInOverlay, node);
-  }
 };
 
 SDK.SDKModel.register(SDK.DOMModel, SDK.Target.Capability.DOM, true);
@@ -1920,13 +1773,10 @@
   CharacterDataModified: Symbol('CharacterDataModified'),
   DOMMutated: Symbol('DOMMutated'),
   NodeInserted: Symbol('NodeInserted'),
-  NodeInspected: Symbol('NodeInspected'),
-  NodeHighlightedInOverlay: Symbol('NodeHighlightedInOverlay'),
   NodeRemoved: Symbol('NodeRemoved'),
   DocumentUpdated: Symbol('DocumentUpdated'),
   ChildNodeCountUpdated: Symbol('ChildNodeCountUpdated'),
   DistributedNodesChanged: Symbol('DistributedNodesChanged'),
-  InspectModeWillBeToggled: Symbol('InspectModeWillBeToggled'),
   MarkersChanged: Symbol('MarkersChanged')
 };
 
@@ -1953,14 +1803,6 @@
   /**
    * @override
    * @param {!Protocol.DOM.NodeId} nodeId
-   */
-  inspectNodeRequested(nodeId) {
-    this._domModel._inspectNodeRequested(nodeId);
-  }
-
-  /**
-   * @override
-   * @param {!Protocol.DOM.NodeId} nodeId
    * @param {string} name
    * @param {string} value
    */
@@ -2075,86 +1917,4 @@
   distributedNodesUpdated(insertionPointId, distributedNodes) {
     this._domModel._distributedNodesUpdated(insertionPointId, distributedNodes);
   }
-
-  /**
-   * @override
-   * @param {!Protocol.DOM.NodeId} nodeId
-   */
-  nodeHighlightRequested(nodeId) {
-    this._domModel.nodeHighlightRequested(nodeId);
-  }
-};
-
-/**
- * @interface
- */
-SDK.DOMNodeHighlighter = function() {};
-
-SDK.DOMNodeHighlighter.prototype = {
-  /**
-   * @param {?SDK.DOMNode} node
-   * @param {!Protocol.DOM.HighlightConfig} config
-   * @param {!Protocol.DOM.BackendNodeId=} backendNodeId
-   * @param {!Protocol.Runtime.RemoteObjectId=} objectId
-   */
-  highlightDOMNode(node, config, backendNodeId, objectId) {},
-
-  /**
-   * @param {!Protocol.DOM.InspectMode} mode
-   * @param {!Protocol.DOM.HighlightConfig} config
-   * @param {function(?Protocol.Error)=} callback
-   */
-  setInspectMode(mode, config, callback) {},
-
-  /**
-   * @param {!Protocol.Page.FrameId} frameId
-   */
-  highlightFrame(frameId) {}
-};
-
-/**
- * @implements {SDK.DOMNodeHighlighter}
- * @unrestricted
- */
-SDK.DefaultDOMNodeHighlighter = class {
-  /**
-   * @param {!Protocol.DOMAgent} agent
-   */
-  constructor(agent) {
-    this._agent = agent;
-  }
-
-  /**
-   * @override
-   * @param {?SDK.DOMNode} node
-   * @param {!Protocol.DOM.HighlightConfig} config
-   * @param {!Protocol.DOM.BackendNodeId=} backendNodeId
-   * @param {!Protocol.Runtime.RemoteObjectId=} objectId
-   */
-  highlightDOMNode(node, config, backendNodeId, objectId) {
-    if (objectId || node || backendNodeId)
-      this._agent.highlightNode(config, (objectId || backendNodeId) ? undefined : node.id, backendNodeId, objectId);
-    else
-      this._agent.hideHighlight();
-  }
-
-  /**
-   * @override
-   * @param {!Protocol.DOM.InspectMode} mode
-   * @param {!Protocol.DOM.HighlightConfig} config
-   * @param {function(?Protocol.Error)=} callback
-   */
-  setInspectMode(mode, config, callback) {
-    this._agent.setInspectMode(mode, config, callback);
-  }
-
-  /**
-   * @override
-   * @param {!Protocol.Page.FrameId} frameId
-   */
-  highlightFrame(frameId) {
-    this._agent.highlightFrame(
-        frameId, Common.Color.PageHighlight.Content.toProtocolRGBA(),
-        Common.Color.PageHighlight.ContentOutline.toProtocolRGBA());
-  }
 };
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/DebuggerModel.js b/third_party/WebKit/Source/devtools/front_end/sdk/DebuggerModel.js
index fb5ad2e..e80f42e4 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/DebuggerModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/DebuggerModel.js
@@ -302,26 +302,12 @@
    * @param {boolean} restrictToFunction
    * @return {!Promise<!Array<!SDK.DebuggerModel.BreakLocation>>}
    */
-  getPossibleBreakpoints(startLocation, endLocation, restrictToFunction) {
-    var fulfill;
-    var promise = new Promise(resolve => fulfill = resolve);
-    this._agent.invoke_getPossibleBreakpoints(
-        {start: startLocation.payload(), end: endLocation.payload(), restrictToFunction: restrictToFunction},
-        checkErrorAndReturn.bind(this));
-    return promise;
-
-    /**
-     * @this {!SDK.DebuggerModel}
-     * @param {?Protocol.Error} error
-     * @param {?Array<!Protocol.Debugger.BreakLocation>} locations
-     */
-    function checkErrorAndReturn(error, locations) {
-      if (error || !locations) {
-        fulfill([]);
-        return;
-      }
-      fulfill(locations.map(location => SDK.DebuggerModel.BreakLocation.fromPayload(this, location)));
-    }
+  async getPossibleBreakpoints(startLocation, endLocation, restrictToFunction) {
+    var response = await this._agent.invoke_getPossibleBreakpoints(
+        {start: startLocation.payload(), end: endLocation.payload(), restrictToFunction: restrictToFunction});
+    if (response[Protocol.Error] || !response.locations)
+      return [];
+    return response.locations.map(location => SDK.DebuggerModel.BreakLocation.fromPayload(this, location));
   }
 
   /**
@@ -1251,26 +1237,23 @@
    * @param {boolean} generatePreview
    * @param {function(?Protocol.Runtime.RemoteObject, !Protocol.Runtime.ExceptionDetails=, string=)} callback
    */
-  evaluate(code, objectGroup, includeCommandLineAPI, silent, returnByValue, generatePreview, callback) {
-    /**
-     * @param {?Protocol.Error} error
-     * @param {!Protocol.Runtime.RemoteObject} result
-     * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails
-     */
-    function didEvaluateOnCallFrame(error, result, exceptionDetails) {
-      if (error) {
-        console.error(error);
-        callback(null, undefined, error);
-        return;
-      }
-      callback(result, exceptionDetails);
+  async evaluate(code, objectGroup, includeCommandLineAPI, silent, returnByValue, generatePreview, callback) {
+    var response = await this.debuggerModel._agent.invoke_evaluateOnCallFrame({
+      callFrameId: this._payload.callFrameId,
+      expression: code,
+      objectGroup: objectGroup,
+      includeCommandLineAPI: includeCommandLineAPI,
+      silent: silent,
+      returnByValue: returnByValue,
+      generatePreview: generatePreview
+    });
+    var error = response[Protocol.Error];
+    if (error) {
+      console.error(error);
+      callback(null, undefined, error);
+      return;
     }
-    this.debuggerModel._agent.invoke_evaluateOnCallFrame(
-        {
-          callFrameId: this._payload.callFrameId,
-          expression: code, objectGroup, includeCommandLineAPI, silent, returnByValue, generatePreview
-        },
-        didEvaluateOnCallFrame);
+    callback(response.result, response.exceptionDetails);
   }
 
   /**
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/OverlayModel.js b/third_party/WebKit/Source/devtools/front_end/sdk/OverlayModel.js
new file mode 100644
index 0000000..de8d729
--- /dev/null
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/OverlayModel.js
@@ -0,0 +1,320 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @implements {Protocol.OverlayDispatcher}
+ */
+SDK.OverlayModel = class extends SDK.SDKModel {
+  /**
+   * @param {!SDK.Target} target
+   */
+  constructor(target) {
+    super(target);
+    this._domModel = /** @type {!SDK.DOMModel} */ (target.model(SDK.DOMModel));
+
+    target.registerOverlayDispatcher(this);
+    this._overlayAgent = target.overlayAgent();
+    this._overlayAgent.enable();
+    this._overlayAgent.setShowViewportSizeOnResize(true);
+
+    this._debuggerModel = target.model(SDK.DebuggerModel);
+    if (this._debuggerModel) {
+      Common.moduleSetting('disablePausedStateOverlay').addChangeListener(this._updatePausedInDebuggerMessage, this);
+      this._debuggerModel.addEventListener(
+          SDK.DebuggerModel.Events.DebuggerPaused, this._updatePausedInDebuggerMessage, this);
+      this._debuggerModel.addEventListener(
+          SDK.DebuggerModel.Events.DebuggerResumed, this._updatePausedInDebuggerMessage, this);
+      // TODO(dgozman): we should get DebuggerResumed on navigations instead of listening to GlobalObjectCleared.
+      this._debuggerModel.addEventListener(
+          SDK.DebuggerModel.Events.GlobalObjectCleared, this._updatePausedInDebuggerMessage, this);
+    }
+
+    this._inspectModeEnabled = false;
+    this._hideHighlightTimeout = null;
+    this._defaultHighlighter = new SDK.OverlayModel.DefaultHighlighter(this);
+    this._highlighter = this._defaultHighlighter;
+
+    this._showPaintRectsSetting = Common.moduleSetting('showPaintRects');
+    this._showPaintRectsSetting.addChangeListener(
+        () => this._overlayAgent.setShowPaintRects(this._showPaintRectsSetting.get()));
+    if (this._showPaintRectsSetting.get())
+      this._overlayAgent.setShowPaintRects(true);
+
+    this._showDebugBordersSetting = Common.moduleSetting('showDebugBorders');
+    this._showDebugBordersSetting.addChangeListener(
+        () => this._overlayAgent.setShowDebugBorders(this._showDebugBordersSetting.get()));
+    if (this._showDebugBordersSetting.get())
+      this._overlayAgent.setShowDebugBorders(true);
+
+    this._showFPSCounterSetting = Common.moduleSetting('showFPSCounter');
+    this._showFPSCounterSetting.addChangeListener(
+        () => this._overlayAgent.setShowFPSCounter(this._showFPSCounterSetting.get()));
+    if (this._showFPSCounterSetting.get())
+      this._overlayAgent.setShowFPSCounter(true);
+
+    this._showScrollBottleneckRectsSetting = Common.moduleSetting('showScrollBottleneckRects');
+    this._showScrollBottleneckRectsSetting.addChangeListener(
+        () => this._overlayAgent.setShowScrollBottleneckRects(this._showScrollBottleneckRectsSetting.get()));
+    if (this._showScrollBottleneckRectsSetting.get())
+      this._overlayAgent.setShowScrollBottleneckRects(true);
+  }
+
+  /**
+   * @param {!SDK.RemoteObject} object
+   */
+  static highlightObjectAsDOMNode(object) {
+    var domModel = object.runtimeModel().target().model(SDK.DOMModel);
+    if (domModel)
+      domModel.overlayModel().highlightDOMNode(undefined, undefined, undefined, object.objectId);
+  }
+
+  static hideDOMNodeHighlight() {
+    for (var overlayModel of SDK.targetManager.models(SDK.OverlayModel))
+      overlayModel.highlightDOMNode(0);
+  }
+
+  static muteHighlight() {
+    SDK.OverlayModel.hideDOMNodeHighlight();
+    SDK.OverlayModel._highlightDisabled = true;
+  }
+
+  static unmuteHighlight() {
+    SDK.OverlayModel._highlightDisabled = false;
+  }
+
+  /**
+   * @override
+   * @return {!Promise}
+   */
+  suspendModel() {
+    return this._overlayAgent.setSuspended(true);
+  }
+
+  /**
+   * @override
+   * @return {!Promise}
+   */
+  resumeModel() {
+    return this._overlayAgent.setSuspended(false);
+  }
+
+  setShowViewportSizeOnResize(show) {
+    this._overlayAgent.setShowViewportSizeOnResize(show);
+  }
+
+  _updatePausedInDebuggerMessage() {
+    var message = this._debuggerModel.isPaused() && !Common.moduleSetting('disablePausedStateOverlay').get() ?
+        Common.UIString('Paused in debugger') :
+        undefined;
+    this._overlayAgent.setPausedInDebuggerMessage(message);
+  }
+
+  /**
+   * @param {?SDK.OverlayModel.Highlighter} highlighter
+   */
+  setHighlighter(highlighter) {
+    this._highlighter = highlighter || this._defaultHighlighter;
+  }
+
+  /**
+   * @param {!Protocol.Overlay.InspectMode} mode
+   * @return {!Promise}
+   */
+  setInspectMode(mode) {
+    var requestDocumentPromise = new Promise(fulfill => this._domModel.requestDocument(fulfill));
+    return requestDocumentPromise.then(() => {
+      this._inspectModeEnabled = mode !== Protocol.Overlay.InspectMode.None;
+      this.dispatchEventToListeners(SDK.OverlayModel.Events.InspectModeWillBeToggled, this);
+      return this._highlighter.setInspectMode(mode, this._buildHighlightConfig());
+    });
+  }
+
+  /**
+   * @return {boolean}
+   */
+  inspectModeEnabled() {
+    return this._inspectModeEnabled;
+  }
+
+  /**
+   * @param {!Protocol.DOM.NodeId=} nodeId
+   * @param {string=} mode
+   * @param {!Protocol.DOM.BackendNodeId=} backendNodeId
+   * @param {!Protocol.Runtime.RemoteObjectId=} objectId
+   */
+  highlightDOMNode(nodeId, mode, backendNodeId, objectId) {
+    this.highlightDOMNodeWithConfig(nodeId, {mode: mode}, backendNodeId, objectId);
+  }
+
+  /**
+   * @param {!Protocol.DOM.NodeId=} nodeId
+   * @param {!{mode: (string|undefined), showInfo: (boolean|undefined), selectors: (string|undefined)}=} config
+   * @param {!Protocol.DOM.BackendNodeId=} backendNodeId
+   * @param {!Protocol.Runtime.RemoteObjectId=} objectId
+   */
+  highlightDOMNodeWithConfig(nodeId, config, backendNodeId, objectId) {
+    if (SDK.OverlayModel._highlightDisabled)
+      return;
+    config = config || {mode: 'all', showInfo: undefined, selectors: undefined};
+    if (this._hideHighlightTimeout) {
+      clearTimeout(this._hideHighlightTimeout);
+      this._hideHighlightTimeout = null;
+    }
+    var highlightConfig = this._buildHighlightConfig(config.mode);
+    if (typeof config.showInfo !== 'undefined')
+      highlightConfig.showInfo = config.showInfo;
+    if (typeof config.selectors !== 'undefined')
+      highlightConfig.selectorList = config.selectors;
+    this._highlighter.highlightDOMNode(this._domModel.nodeForId(nodeId || 0), highlightConfig, backendNodeId, objectId);
+  }
+
+  /**
+   * @param {!Protocol.DOM.NodeId} nodeId
+   */
+  highlightDOMNodeForTwoSeconds(nodeId) {
+    this.highlightDOMNode(nodeId);
+    this._hideHighlightTimeout = setTimeout(() => this.highlightDOMNode(0), 2000);
+  }
+
+  /**
+   * @param {!Protocol.Page.FrameId} frameId
+   */
+  highlightFrame(frameId) {
+    if (SDK.OverlayModel._highlightDisabled)
+      return;
+    this._highlighter.highlightFrame(frameId);
+  }
+
+  /**
+   * @param {string=} mode
+   * @return {!Protocol.Overlay.HighlightConfig}
+   */
+  _buildHighlightConfig(mode) {
+    mode = mode || 'all';
+    var showRulers = Common.moduleSetting('showMetricsRulers').get();
+    var highlightConfig = {showInfo: mode === 'all', showRulers: showRulers, showExtensionLines: showRulers};
+    if (mode === 'all' || mode === 'content')
+      highlightConfig.contentColor = Common.Color.PageHighlight.Content.toProtocolRGBA();
+
+    if (mode === 'all' || mode === 'padding')
+      highlightConfig.paddingColor = Common.Color.PageHighlight.Padding.toProtocolRGBA();
+
+    if (mode === 'all' || mode === 'border')
+      highlightConfig.borderColor = Common.Color.PageHighlight.Border.toProtocolRGBA();
+
+    if (mode === 'all' || mode === 'margin')
+      highlightConfig.marginColor = Common.Color.PageHighlight.Margin.toProtocolRGBA();
+
+    if (mode === 'all') {
+      highlightConfig.eventTargetColor = Common.Color.PageHighlight.EventTarget.toProtocolRGBA();
+      highlightConfig.shapeColor = Common.Color.PageHighlight.Shape.toProtocolRGBA();
+      highlightConfig.shapeMarginColor = Common.Color.PageHighlight.ShapeMargin.toProtocolRGBA();
+      highlightConfig.displayAsMaterial = true;
+    }
+    return highlightConfig;
+  }
+
+  /**
+   * @override
+   * @param {!Protocol.DOM.NodeId} nodeId
+   */
+  nodeHighlightRequested(nodeId) {
+    var node = this._domModel.nodeForId(nodeId);
+    if (node)
+      this.dispatchEventToListeners(SDK.OverlayModel.Events.HighlightNodeRequested, node);
+  }
+
+  /**
+   * @override
+   * @param {!Protocol.DOM.BackendNodeId} backendNodeId
+   */
+  inspectNodeRequested(backendNodeId) {
+    var deferredNode = new SDK.DeferredDOMNode(this.target(), backendNodeId);
+    this.dispatchEventToListeners(SDK.OverlayModel.Events.InspectNodeRequested, deferredNode);
+  }
+};
+
+SDK.SDKModel.register(SDK.OverlayModel, SDK.Target.Capability.DOM, true);
+
+/** @enum {symbol} */
+SDK.OverlayModel.Events = {
+  InspectModeWillBeToggled: Symbol('InspectModeWillBeToggled'),
+  HighlightNodeRequested: Symbol('HighlightNodeRequested'),
+  InspectNodeRequested: Symbol('InspectNodeRequested'),
+};
+
+/**
+ * @interface
+ */
+SDK.OverlayModel.Highlighter = function() {};
+
+SDK.OverlayModel.Highlighter.prototype = {
+  /**
+   * @param {?SDK.DOMNode} node
+   * @param {!Protocol.Overlay.HighlightConfig} config
+   * @param {!Protocol.DOM.BackendNodeId=} backendNodeId
+   * @param {!Protocol.Runtime.RemoteObjectId=} objectId
+   */
+  highlightDOMNode(node, config, backendNodeId, objectId) {},
+
+  /**
+   * @param {!Protocol.Overlay.InspectMode} mode
+   * @param {!Protocol.Overlay.HighlightConfig} config
+   * @return {!Promise}
+   */
+  setInspectMode(mode, config) {},
+
+  /**
+   * @param {!Protocol.Page.FrameId} frameId
+   */
+  highlightFrame(frameId) {}
+};
+
+/**
+ * @implements {SDK.OverlayModel.Highlighter}
+ */
+SDK.OverlayModel.DefaultHighlighter = class {
+  /**
+   * @param {!SDK.OverlayModel} model
+   */
+  constructor(model) {
+    this._model = model;
+  }
+
+  /**
+   * @override
+   * @param {?SDK.DOMNode} node
+   * @param {!Protocol.Overlay.HighlightConfig} config
+   * @param {!Protocol.DOM.BackendNodeId=} backendNodeId
+   * @param {!Protocol.Runtime.RemoteObjectId=} objectId
+   */
+  highlightDOMNode(node, config, backendNodeId, objectId) {
+    if (objectId || node || backendNodeId) {
+      this._model._overlayAgent.highlightNode(
+          config, (objectId || backendNodeId) ? undefined : node.id, backendNodeId, objectId);
+    } else {
+      this._model._overlayAgent.hideHighlight();
+    }
+  }
+
+  /**
+   * @override
+   * @param {!Protocol.Overlay.InspectMode} mode
+   * @param {!Protocol.Overlay.HighlightConfig} config
+   * @return {!Promise}
+   */
+  setInspectMode(mode, config) {
+    return this._model._overlayAgent.setInspectMode(mode, config);
+  }
+
+  /**
+   * @override
+   * @param {!Protocol.Page.FrameId} frameId
+   */
+  highlightFrame(frameId) {
+    this._model._overlayAgent.highlightFrame(
+        frameId, Common.Color.PageHighlight.Content.toProtocolRGBA(),
+        Common.Color.PageHighlight.ContentOutline.toProtocolRGBA());
+  }
+};
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/RemoteObject.js b/third_party/WebKit/Source/devtools/front_end/sdk/RemoteObject.js
index 5705f5a..3206751 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/RemoteObject.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/RemoteObject.js
@@ -647,28 +647,23 @@
       return;
     }
 
-    this._runtimeAgent.invoke_evaluate({expression: value, silent: true}, evaluatedCallback.bind(this));
-
-    /**
-     * @param {?Protocol.Error} error
-     * @param {!Protocol.Runtime.RemoteObject} result
-     * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails
-     * @this {SDK.RemoteObjectImpl}
-     */
-    function evaluatedCallback(error, result, exceptionDetails) {
-      if (error || !!exceptionDetails) {
-        callback(error || (result.type !== 'string' ? result.description : /** @type {string} */ (result.value)));
+    this._runtimeAgent.invoke_evaluate({expression: value, silent: true}).then(response => {
+      if (response[Protocol.Error] || response.exceptionDetails) {
+        callback(
+            response[Protocol.Error] ||
+            (response.result.type !== 'string' ? response.result.description :
+                                                 /** @type {string} */ (response.result.value)));
         return;
       }
 
       if (typeof name === 'string')
         name = SDK.RemoteObject.toCallArgument(name);
 
-      this.doSetObjectPropertyValue(result, name, callback);
+      this.doSetObjectPropertyValue(response.result, name, callback);
 
-      if (result.objectId)
-        this._runtimeAgent.releaseObject(result.objectId);
-    }
+      if (response.result.objectId)
+        this._runtimeAgent.releaseObject(response.result.objectId);
+    });
   }
 
   /**
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/TargetManager.js b/third_party/WebKit/Source/devtools/front_end/sdk/TargetManager.js
index 7768d20..6353ea2d 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/TargetManager.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/TargetManager.js
@@ -418,10 +418,7 @@
    * @return {!Promise}
    */
   resume() {
-    var fulfill;
-    var promise = new Promise(callback => fulfill = callback);
-    this._targetAgent.invoke_setAutoAttach({autoAttach: true, waitForDebuggerOnStart: true}, fulfill);
-    return promise;
+    return this._targetAgent.invoke_setAutoAttach({autoAttach: true, waitForDebuggerOnStart: true});
   }
 
   dispose() {
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/module.json b/third_party/WebKit/Source/devtools/front_end/sdk/module.json
index 8bb5fab..e4c2be3 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/module.json
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/module.json
@@ -84,6 +84,34 @@
         },
         {
             "type": "setting",
+            "settingName": "showPaintRects",
+            "settingType": "boolean",
+            "storageType": "session",
+            "defaultValue": false
+        },
+        {
+            "type": "setting",
+            "settingName": "showDebugBorders",
+            "settingType": "boolean",
+            "storageType": "session",
+            "defaultValue": false
+        },
+        {
+            "type": "setting",
+            "settingName": "showFPSCounter",
+            "settingType": "boolean",
+            "storageType": "session",
+            "defaultValue": false
+        },
+        {
+            "type": "setting",
+            "settingName": "showScrollBottleneckRects",
+            "settingType": "boolean",
+            "storageType": "session",
+            "defaultValue": false
+        },
+        {
+            "type": "setting",
             "category": "Console",
             "title": "Enable custom formatters",
             "settingName": "customFormatters",
@@ -137,6 +165,7 @@
         "ServiceWorkerManager.js",
         "TracingManager.js",
         "TracingModel.js",
+        "OverlayModel.js",
         "RuntimeModel.js",
         "ScreenCaptureModel.js",
         "Script.js",
diff --git a/third_party/WebKit/Source/devtools/front_end/sources/DebuggerPausedMessage.js b/third_party/WebKit/Source/devtools/front_end/sources/DebuggerPausedMessage.js
index c2e545a..80aecaa 100644
--- a/third_party/WebKit/Source/devtools/front_end/sources/DebuggerPausedMessage.js
+++ b/third_party/WebKit/Source/devtools/front_end/sources/DebuggerPausedMessage.js
@@ -37,8 +37,11 @@
     if (details.reason === SDK.DebuggerModel.BreakReason.DOM) {
       messageWrapper = Components.DOMBreakpointsSidebarPane.createBreakpointHitMessage(details);
     } else if (details.reason === SDK.DebuggerModel.BreakReason.EventListener) {
-      var eventName = details.auxData['eventName'];
-      var eventNameForUI = Sources.EventListenerBreakpointsSidebarPane.eventNameForUI(eventName, details.auxData);
+      var eventNameForUI = '';
+      if (details.auxData) {
+        eventNameForUI =
+            SDK.domDebuggerManager.resolveEventListenerBreakpointTitle(/** @type {!Object} */ (details.auxData));
+      }
       messageWrapper = buildWrapper(Common.UIString('Paused on event listener'), eventNameForUI);
     } else if (details.reason === SDK.DebuggerModel.BreakReason.XHR) {
       messageWrapper = buildWrapper(Common.UIString('Paused on XMLHttpRequest'), details.auxData['url'] || '');
diff --git a/third_party/WebKit/Source/devtools/front_end/sources/EventListenerBreakpointsSidebarPane.js b/third_party/WebKit/Source/devtools/front_end/sources/EventListenerBreakpointsSidebarPane.js
index 20d24b1..22bf79b 100644
--- a/third_party/WebKit/Source/devtools/front_end/sources/EventListenerBreakpointsSidebarPane.js
+++ b/third_party/WebKit/Source/devtools/front_end/sources/EventListenerBreakpointsSidebarPane.js
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 /**
- * @implements {SDK.TargetManager.Observer}
  * @unrestricted
  */
 Sources.EventListenerBreakpointsSidebarPane = class extends UI.VBox {
@@ -10,178 +9,59 @@
     super();
     this.registerRequiredCSS('components/breakpointsList.css');
 
-    this._eventListenerBreakpointsSetting =
-        Common.settings.createSetting('eventListenerBreakpoints', [], Common.SettingStorageType.Session);
-
     this._categoriesTreeOutline = new UI.TreeOutlineInShadow();
     this._categoriesTreeOutline.element.tabIndex = 0;
     this._categoriesTreeOutline.element.classList.add('event-listener-breakpoints');
     this._categoriesTreeOutline.registerRequiredCSS('sources/eventListenerBreakpoints.css');
     this.element.appendChild(this._categoriesTreeOutline.element);
 
-    this._categoryItems = [];
-    // FIXME: uncomment following once inspector stops being drop targer in major ports.
-    // Otherwise, inspector page reacts on drop event and tries to load the event data.
-    // this._createCategory(Common.UIString("Drag"), ["drag", "drop", "dragstart", "dragend", "dragenter", "dragleave", "dragover"]);
-    this._createCategory(
-        Common.UIString('Animation'), ['requestAnimationFrame', 'cancelAnimationFrame', 'animationFrameFired'], true);
-    this._createCategory(
-        Common.UIString('Canvas'), ['canvasContextCreated', 'webglErrorFired', 'webglWarningFired'], true);
-    this._createCategory(
-        Common.UIString('Clipboard'), ['copy', 'cut', 'paste', 'beforecopy', 'beforecut', 'beforepaste']);
-    this._createCategory(
-        Common.UIString('Control'),
-        ['resize', 'scroll', 'zoom', 'focus', 'blur', 'select', 'change', 'submit', 'reset']);
-    this._createCategory(Common.UIString('Device'), ['deviceorientation', 'devicemotion']);
-    this._createCategory(Common.UIString('DOM Mutation'), [
-      'DOMActivate', 'DOMFocusIn', 'DOMFocusOut', 'DOMAttrModified', 'DOMCharacterDataModified', 'DOMNodeInserted',
-      'DOMNodeInsertedIntoDocument', 'DOMNodeRemoved', 'DOMNodeRemovedFromDocument', 'DOMSubtreeModified',
-      'DOMContentLoaded'
-    ]);
-    this._createCategory(
-        Common.UIString('Geolocation'), ['Geolocation.getCurrentPosition', 'Geolocation.watchPosition'], true);
-    this._createCategory(Common.UIString('Drag / drop'), ['dragenter', 'dragover', 'dragleave', 'drop']);
-    this._createCategory(Common.UIString('Keyboard'), ['keydown', 'keyup', 'keypress', 'input']);
-    this._createCategory(
-        Common.UIString('Load'), ['load', 'beforeunload', 'unload', 'abort', 'error', 'hashchange', 'popstate']);
-    this._createCategory(
-        Common.UIString('Media'),
-        [
-          'play',      'pause',          'playing',    'canplay',    'canplaythrough', 'seeking',
-          'seeked',    'timeupdate',     'ended',      'ratechange', 'durationchange', 'volumechange',
-          'loadstart', 'progress',       'suspend',    'abort',      'error',          'emptied',
-          'stalled',   'loadedmetadata', 'loadeddata', 'waiting'
-        ],
-        false, ['audio', 'video']);
-    this._createCategory(Common.UIString('Mouse'), [
-      'auxclick', 'click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mousemove', 'mouseout', 'mouseenter',
-      'mouseleave', 'mousewheel', 'wheel', 'contextmenu'
-    ]);
-    this._createCategory(Common.UIString('Notification'), ['Notification.requestPermission'], true);
-    this._createCategory(Common.UIString('Parse'), ['setInnerHTML', 'Document.write'], true);
-    this._createCategory(Common.UIString('Pointer'), [
-      'pointerover', 'pointerout', 'pointerenter', 'pointerleave', 'pointerdown', 'pointerup', 'pointermove',
-      'pointercancel', 'gotpointercapture', 'lostpointercapture'
-    ]);
-    this._createCategory(Common.UIString('Script'), ['scriptFirstStatement', 'scriptBlockedByCSP'], true);
-    this._createCategory(
-        Common.UIString('Timer'),
-        ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'setTimeout.callback', 'setInterval.callback'],
-        true);
-    this._createCategory(Common.UIString('Touch'), ['touchstart', 'touchmove', 'touchend', 'touchcancel']);
-    this._createCategory(Common.UIString('Window'), ['DOMWindow.close'], true);
-    this._createCategory(
-        Common.UIString('XHR'),
-        ['readystatechange', 'load', 'loadstart', 'loadend', 'abort', 'error', 'progress', 'timeout'], false,
-        ['XMLHttpRequest', 'XMLHttpRequestUpload']);
+    /** @type {!Map<string, !Sources.EventListenerBreakpointsSidebarPane.Item>} */
+    this._categories = new Map();
+    var categories = SDK.domDebuggerManager.eventListenerBreakpoints().map(breakpoint => breakpoint.category());
+    categories.sort();
+    for (var category of categories) {
+      if (!this._categories.has(category))
+        this._createCategory(category);
+    }
 
-    SDK.targetManager.observeTargets(this, SDK.Target.Capability.DOM);
+    /** @type {!Map<!SDK.DOMDebuggerModel.EventListenerBreakpoint, !Sources.EventListenerBreakpointsSidebarPane.Item>} */
+    this._breakpoints = new Map();
+    for (var breakpoint of SDK.domDebuggerManager.eventListenerBreakpoints())
+      this._createBreakpoint(breakpoint);
+
     SDK.targetManager.addModelListener(SDK.DebuggerModel, SDK.DebuggerModel.Events.DebuggerPaused, this._update, this);
     SDK.targetManager.addModelListener(SDK.DebuggerModel, SDK.DebuggerModel.Events.DebuggerResumed, this._update, this);
     UI.context.addFlavorChangeListener(SDK.Target, this._update, this);
   }
 
   /**
-   * @param {string} eventName
-   * @param {!Object=} auxData
-   * @return {string}
-   */
-  static eventNameForUI(eventName, auxData) {
-    if (!Sources.EventListenerBreakpointsSidebarPane._eventNamesForUI) {
-      Sources.EventListenerBreakpointsSidebarPane._eventNamesForUI = {
-        'instrumentation:setTimeout.callback': Common.UIString('setTimeout fired'),
-        'instrumentation:setInterval.callback': Common.UIString('setInterval fired'),
-        'instrumentation:scriptFirstStatement': Common.UIString('Script First Statement'),
-        'instrumentation:scriptBlockedByCSP': Common.UIString('Script Blocked by Content Security Policy'),
-        'instrumentation:requestAnimationFrame': Common.UIString('Request Animation Frame'),
-        'instrumentation:cancelAnimationFrame': Common.UIString('Cancel Animation Frame'),
-        'instrumentation:requestAnimationFrame.callback': Common.UIString('Animation Frame Fired'),
-        'instrumentation:webglErrorFired': Common.UIString('WebGL Error Fired'),
-        'instrumentation:webglWarningFired': Common.UIString('WebGL Warning Fired'),
-        'instrumentation:Element.setInnerHTML': Common.UIString('Set innerHTML'),
-        'instrumentation:canvasContextCreated': Common.UIString('Create canvas context'),
-        'instrumentation:Geolocation.getCurrentPosition': 'getCurrentPosition',
-        'instrumentation:Geolocation.watchPosition': 'watchPosition',
-        'instrumentation:Notification.requestPermission': 'requestPermission',
-        'instrumentation:DOMWindow.close': 'window.close',
-        'instrumentation:Document.write': 'document.write',
-      };
-    }
-    if (auxData) {
-      if (eventName === 'instrumentation:webglErrorFired' && auxData['webglErrorName']) {
-        var errorName = auxData['webglErrorName'];
-        // If there is a hex code of the error, display only this.
-        errorName = errorName.replace(/^.*(0x[0-9a-f]+).*$/i, '$1');
-        return Common.UIString('WebGL Error Fired (%s)', errorName);
-      }
-      if (eventName === 'instrumentation:scriptBlockedByCSP' && auxData['directiveText'])
-        return Common.UIString('Script blocked due to Content Security Policy directive: %s', auxData['directiveText']);
-    }
-    return Sources.EventListenerBreakpointsSidebarPane._eventNamesForUI[eventName] ||
-        eventName.substring(eventName.indexOf(':') + 1);
-  }
-
-  /**
-   * @override
-   * @param {!SDK.Target} target
-   */
-  targetAdded(target) {
-    this._restoreBreakpoints(target);
-  }
-
-  /**
-   * @override
-   * @param {!SDK.Target} target
-   */
-  targetRemoved(target) {
-  }
-
-  /**
    * @param {string} name
-   * @param {!Array.<string>} eventNames
-   * @param {boolean=} isInstrumentationEvent
-   * @param {!Array.<string>=} targetNames
    */
-  _createCategory(name, eventNames, isInstrumentationEvent, targetNames) {
+  _createCategory(name) {
     var labelNode = UI.CheckboxLabel.create(name);
+    labelNode.checkboxElement.addEventListener('click', this._categoryCheckboxClicked.bind(this, name), true);
 
-    var categoryItem = {};
-    categoryItem.element = new UI.TreeElement(labelNode);
-    this._categoriesTreeOutline.appendChild(categoryItem.element);
-    categoryItem.element.selectable = false;
+    var treeElement = new UI.TreeElement(labelNode);
+    treeElement.selectable = false;
+    this._categoriesTreeOutline.appendChild(treeElement);
 
-    categoryItem.checkbox = labelNode.checkboxElement;
-    categoryItem.checkbox.addEventListener('click', this._categoryCheckboxClicked.bind(this, categoryItem), true);
+    this._categories.set(name, {element: treeElement, checkbox: labelNode.checkboxElement});
+  }
 
-    categoryItem.targetNames =
-        this._stringArrayToLowerCase(targetNames || [Sources.EventListenerBreakpointsSidebarPane.eventTargetAny]);
-    categoryItem.children = {};
-    var category =
-        (isInstrumentationEvent ? Sources.EventListenerBreakpointsSidebarPane.categoryInstrumentation :
-                                  Sources.EventListenerBreakpointsSidebarPane.categoryListener);
-    for (var i = 0; i < eventNames.length; ++i) {
-      var eventName = category + eventNames[i];
+  /**
+   * @param {!SDK.DOMDebuggerModel.EventListenerBreakpoint} breakpoint
+   */
+  _createBreakpoint(breakpoint) {
+    var labelNode = UI.CheckboxLabel.create(breakpoint.title());
+    labelNode.classList.add('source-code');
+    labelNode.checkboxElement.addEventListener('click', this._breakpointCheckboxClicked.bind(this, breakpoint), true);
 
-      var breakpointItem = {};
-      var title = Sources.EventListenerBreakpointsSidebarPane.eventNameForUI(eventName);
+    var treeElement = new UI.TreeElement(labelNode);
+    treeElement.listItemElement.createChild('div', 'breakpoint-hit-marker');
+    treeElement.selectable = false;
+    this._categories.get(breakpoint.category()).element.appendChild(treeElement);
 
-      labelNode = UI.CheckboxLabel.create(title);
-      labelNode.classList.add('source-code');
-
-      breakpointItem.element = new UI.TreeElement(labelNode);
-      categoryItem.element.appendChild(breakpointItem.element);
-
-      breakpointItem.element.listItemElement.createChild('div', 'breakpoint-hit-marker');
-      breakpointItem.element.selectable = false;
-
-      breakpointItem.checkbox = labelNode.checkboxElement;
-      breakpointItem.checkbox.addEventListener(
-          'click', this._breakpointCheckboxClicked.bind(this, eventName, categoryItem.targetNames), true);
-      breakpointItem.parent = categoryItem;
-
-      categoryItem.children[eventName] = breakpointItem;
-    }
-    this._categoryItems.push(categoryItem);
+    this._breakpoints.set(breakpoint, {element: treeElement, checkbox: labelNode.checkboxElement});
   }
 
   _update() {
@@ -189,192 +69,61 @@
     var debuggerModel = target ? target.model(SDK.DebuggerModel) : null;
     var details = debuggerModel ? debuggerModel.debuggerPausedDetails() : null;
 
-    if (!details || details.reason !== SDK.DebuggerModel.BreakReason.EventListener) {
+    if (!details || details.reason !== SDK.DebuggerModel.BreakReason.EventListener || !details.auxData) {
       if (this._highlightedElement) {
         this._highlightedElement.classList.remove('breakpoint-hit');
         delete this._highlightedElement;
       }
       return;
     }
-    var eventName = details.auxData['eventName'];
-    var targetName = details.auxData['targetName'];
-    var breakpointItem = this._findBreakpointItem(eventName, targetName);
-    if (!breakpointItem || !breakpointItem.checkbox.checked)
-      breakpointItem = this._findBreakpointItem(eventName, Sources.EventListenerBreakpointsSidebarPane.eventTargetAny);
-    if (!breakpointItem)
+
+    var breakpoint = SDK.domDebuggerManager.resolveEventListenerBreakpoint(/** @type {!Object} */ (details.auxData));
+    if (!breakpoint)
       return;
+
     UI.viewManager.showView('sources.eventListenerBreakpoints');
-    breakpointItem.parent.element.expand();
-    breakpointItem.element.listItemElement.classList.add('breakpoint-hit');
-    this._highlightedElement = breakpointItem.element.listItemElement;
+    this._categories.get(breakpoint.category()).element.expand();
+    this._highlightedElement = this._breakpoints.get(breakpoint).element.listItemElement;
+    this._highlightedElement.classList.add('breakpoint-hit');
   }
 
   /**
-   * @param {!Array.<string>} array
-   * @return {!Array.<string>}
+   * @param {string} category
    */
-  _stringArrayToLowerCase(array) {
-    return array.map(function(value) {
-      return value.toLowerCase();
-    });
-  }
-
-  _categoryCheckboxClicked(categoryItem) {
-    var checked = categoryItem.checkbox.checked;
-    for (var eventName in categoryItem.children) {
-      var breakpointItem = categoryItem.children[eventName];
-      if (breakpointItem.checkbox.checked === checked)
-        continue;
-      if (checked)
-        this._setBreakpoint(eventName, categoryItem.targetNames);
-      else
-        this._removeBreakpoint(eventName, categoryItem.targetNames);
-    }
-    this._saveBreakpoints();
-  }
-
-  /**
-   * @param {string} eventName
-   * @param {!Array.<string>} targetNames
-   * @param {!Event} event
-   */
-  _breakpointCheckboxClicked(eventName, targetNames, event) {
-    if (event.target.checked)
-      this._setBreakpoint(eventName, targetNames);
-    else
-      this._removeBreakpoint(eventName, targetNames);
-    this._saveBreakpoints();
-  }
-
-  /**
-   * @param {string} eventName
-   * @param {?Array.<string>=} eventTargetNames
-   * @param {!SDK.Target=} target
-   */
-  _setBreakpoint(eventName, eventTargetNames, target) {
-    eventTargetNames = eventTargetNames || [Sources.EventListenerBreakpointsSidebarPane.eventTargetAny];
-    for (var i = 0; i < eventTargetNames.length; ++i) {
-      var eventTargetName = eventTargetNames[i];
-      var breakpointItem = this._findBreakpointItem(eventName, eventTargetName);
-      if (!breakpointItem)
-        continue;
-      breakpointItem.checkbox.checked = true;
-      breakpointItem.parent.dirtyCheckbox = true;
-      this._updateBreakpointOnTarget(eventName, eventTargetName, true, target);
-    }
-    this._updateCategoryCheckboxes();
-  }
-
-  /**
-   * @param {string} eventName
-   * @param {?Array.<string>=} eventTargetNames
-   * @param {!SDK.Target=} target
-   */
-  _removeBreakpoint(eventName, eventTargetNames, target) {
-    eventTargetNames = eventTargetNames || [Sources.EventListenerBreakpointsSidebarPane.eventTargetAny];
-    for (var i = 0; i < eventTargetNames.length; ++i) {
-      var eventTargetName = eventTargetNames[i];
-      var breakpointItem = this._findBreakpointItem(eventName, eventTargetName);
-      if (!breakpointItem)
-        continue;
-      breakpointItem.checkbox.checked = false;
-      breakpointItem.parent.dirtyCheckbox = true;
-      this._updateBreakpointOnTarget(eventName, eventTargetName, false, target);
-    }
-    this._updateCategoryCheckboxes();
-  }
-
-  /**
-   * @param {string} eventName
-   * @param {string} eventTargetName
-   * @param {boolean} enable
-   * @param {!SDK.Target=} target
-   */
-  _updateBreakpointOnTarget(eventName, eventTargetName, enable, target) {
-    var targets = target ? [target] : SDK.targetManager.targets(SDK.Target.Capability.DOM);
-    for (target of targets) {
-      if (eventName.startsWith(Sources.EventListenerBreakpointsSidebarPane.categoryListener)) {
-        var protocolEventName =
-            eventName.substring(Sources.EventListenerBreakpointsSidebarPane.categoryListener.length);
-        if (enable)
-          target.domdebuggerAgent().setEventListenerBreakpoint(protocolEventName, eventTargetName);
-        else
-          target.domdebuggerAgent().removeEventListenerBreakpoint(protocolEventName, eventTargetName);
-      } else if (eventName.startsWith(Sources.EventListenerBreakpointsSidebarPane.categoryInstrumentation)) {
-        var protocolEventName =
-            eventName.substring(Sources.EventListenerBreakpointsSidebarPane.categoryInstrumentation.length);
-        if (enable)
-          target.domdebuggerAgent().setInstrumentationBreakpoint(protocolEventName);
-        else
-          target.domdebuggerAgent().removeInstrumentationBreakpoint(protocolEventName);
+  _categoryCheckboxClicked(category) {
+    var item = this._categories.get(category);
+    var enabled = item.checkbox.checked;
+    for (var breakpoint of this._breakpoints.keys()) {
+      if (breakpoint.category() === category) {
+        breakpoint.setEnabled(enabled);
+        this._breakpoints.get(breakpoint).checkbox.checked = enabled;
       }
     }
   }
 
-  _updateCategoryCheckboxes() {
-    for (var i = 0; i < this._categoryItems.length; ++i) {
-      var categoryItem = this._categoryItems[i];
-      if (!categoryItem.dirtyCheckbox)
-        continue;
-      categoryItem.dirtyCheckbox = false;
-      var hasEnabled = false;
-      var hasDisabled = false;
-      for (var eventName in categoryItem.children) {
-        var breakpointItem = categoryItem.children[eventName];
-        if (breakpointItem.checkbox.checked)
+  /**
+   * @param {!SDK.DOMDebuggerModel.EventListenerBreakpoint} breakpoint
+   */
+  _breakpointCheckboxClicked(breakpoint) {
+    var item = this._breakpoints.get(breakpoint);
+    breakpoint.setEnabled(item.checkbox.checked);
+
+    var hasEnabled = false;
+    var hasDisabled = false;
+    for (var other of this._breakpoints.keys()) {
+      if (other.category() === breakpoint.category()) {
+        if (other.enabled())
           hasEnabled = true;
         else
           hasDisabled = true;
       }
-      categoryItem.checkbox.checked = hasEnabled;
-      categoryItem.checkbox.indeterminate = hasEnabled && hasDisabled;
     }
-  }
 
-  /**
-   * @param {string} eventName
-   * @param {string=} targetName
-   * @return {?Object}
-   */
-  _findBreakpointItem(eventName, targetName) {
-    targetName = (targetName || Sources.EventListenerBreakpointsSidebarPane.eventTargetAny).toLowerCase();
-    for (var i = 0; i < this._categoryItems.length; ++i) {
-      var categoryItem = this._categoryItems[i];
-      if (categoryItem.targetNames.indexOf(targetName) === -1)
-        continue;
-      var breakpointItem = categoryItem.children[eventName];
-      if (breakpointItem)
-        return breakpointItem;
-    }
-    return null;
-  }
-
-  _saveBreakpoints() {
-    var breakpoints = [];
-    for (var i = 0; i < this._categoryItems.length; ++i) {
-      var categoryItem = this._categoryItems[i];
-      for (var eventName in categoryItem.children) {
-        var breakpointItem = categoryItem.children[eventName];
-        if (breakpointItem.checkbox.checked)
-          breakpoints.push({eventName: eventName, targetNames: categoryItem.targetNames});
-      }
-    }
-    this._eventListenerBreakpointsSetting.set(breakpoints);
-  }
-
-  /**
-   * @param {!SDK.Target} target
-   */
-  _restoreBreakpoints(target) {
-    var breakpoints = this._eventListenerBreakpointsSetting.get();
-    for (var i = 0; i < breakpoints.length; ++i) {
-      var breakpoint = breakpoints[i];
-      if (breakpoint && typeof breakpoint.eventName === 'string')
-        this._setBreakpoint(breakpoint.eventName, breakpoint.targetNames, target);
-    }
+    var checkbox = this._categories.get(breakpoint.category()).checkbox;
+    checkbox.checked = hasEnabled;
+    checkbox.indeterminate = hasEnabled && hasDisabled;
   }
 };
 
-Sources.EventListenerBreakpointsSidebarPane.categoryListener = 'listener:';
-Sources.EventListenerBreakpointsSidebarPane.categoryInstrumentation = 'instrumentation:';
-Sources.EventListenerBreakpointsSidebarPane.eventTargetAny = '*';
+/** @typedef {!{element: !UI.TreeElement, checkbox: !Element}} */
+Sources.EventListenerBreakpointsSidebarPane.Item;
diff --git a/third_party/WebKit/Source/devtools/front_end/sources/NavigatorView.js b/third_party/WebKit/Source/devtools/front_end/sources/NavigatorView.js
index a1ce9bf7..62a35a08 100644
--- a/third_party/WebKit/Source/devtools/front_end/sources/NavigatorView.js
+++ b/third_party/WebKit/Source/devtools/front_end/sources/NavigatorView.js
@@ -243,20 +243,6 @@
 
   /**
    * @param {!Workspace.UISourceCode} uiSourceCode
-   * @return {?SDK.ResourceTreeFrame}
-   */
-  _uiSourceCodeFrame(uiSourceCode) {
-    var frame = Bindings.NetworkProject.frameForProject(uiSourceCode.project());
-    if (!frame) {
-      var target = Bindings.NetworkProject.targetForProject(uiSourceCode.project());
-      var resourceTreeModel = target && target.model(SDK.ResourceTreeModel);
-      frame = resourceTreeModel && resourceTreeModel.mainFrame;
-    }
-    return frame;
-  }
-
-  /**
-   * @param {!Workspace.UISourceCode} uiSourceCode
    */
   _addUISourceCode(uiSourceCode) {
     if (!this.accept(uiSourceCode))
@@ -275,13 +261,24 @@
 
     var project = uiSourceCode.project();
     var target = Bindings.NetworkProject.targetForUISourceCode(uiSourceCode);
-    var frame = this._uiSourceCodeFrame(uiSourceCode);
-
-    var folderNode =
-        this._folderNode(uiSourceCode, project, target, frame, uiSourceCode.origin(), path, isFromSourceMap);
-    var uiSourceCodeNode = new Sources.NavigatorUISourceCodeTreeNode(this, uiSourceCode);
-    this._uiSourceCodeNodes.set(uiSourceCode, [uiSourceCodeNode]);
-    folderNode.appendChild(uiSourceCodeNode);
+    var frames = Bindings.NetworkProject.framesForUISourceCode(uiSourceCode);
+    var uiSourceCodeNodes = [];
+    if (frames.length) {
+      for (var frame of frames) {
+        var folderNode =
+            this._folderNode(uiSourceCode, project, target, frame, uiSourceCode.origin(), path, isFromSourceMap);
+        var uiSourceCodeNode = new Sources.NavigatorUISourceCodeTreeNode(this, uiSourceCode, frame);
+        folderNode.appendChild(uiSourceCodeNode);
+        uiSourceCodeNodes.push(uiSourceCodeNode);
+      }
+    } else {
+      var folderNode =
+          this._folderNode(uiSourceCode, project, target, null, uiSourceCode.origin(), path, isFromSourceMap);
+      var uiSourceCodeNode = new Sources.NavigatorUISourceCodeTreeNode(this, uiSourceCode, null);
+      folderNode.appendChild(uiSourceCodeNode);
+      uiSourceCodeNodes.push(uiSourceCodeNode);
+    }
+    this._uiSourceCodeNodes.set(uiSourceCode, uiSourceCodeNodes);
     this.uiSourceCodeAdded(uiSourceCode);
   }
 
@@ -436,11 +433,11 @@
      */
     function hoverCallback(hovered) {
       if (hovered) {
-        var domModel = target.model(SDK.DOMModel);
-        if (domModel)
-          domModel.highlightFrame(frame.id);
+        var overlayModel = target.model(SDK.OverlayModel);
+        if (overlayModel)
+          overlayModel.highlightFrame(frame.id);
       } else {
-        SDK.DOMModel.hideDOMNodeHighlight();
+        SDK.OverlayModel.hideDOMNodeHighlight();
       }
     }
     return frameNode;
@@ -530,7 +527,7 @@
 
       var project = uiSourceCode.project();
       var target = Bindings.NetworkProject.targetForUISourceCode(uiSourceCode);
-      var frame = this._uiSourceCodeFrame(uiSourceCode);
+      var frame = node.frame();
 
       var parentNode = node.parent;
       parentNode.removeChild(node);
@@ -1214,13 +1211,22 @@
   /**
    * @param {!Sources.NavigatorView} navigatorView
    * @param {!Workspace.UISourceCode} uiSourceCode
+   * @param {?SDK.ResourceTreeFrame} frame
    */
-  constructor(navigatorView, uiSourceCode) {
+  constructor(navigatorView, uiSourceCode, frame) {
     super(uiSourceCode.project().id() + ':' + uiSourceCode.url(), Sources.NavigatorView.Types.File);
     this._navigatorView = navigatorView;
     this._uiSourceCode = uiSourceCode;
     this._treeElement = null;
     this._eventListeners = [];
+    this._frame = frame;
+  }
+
+  /**
+   * @return {?SDK.ResourceTreeFrame}
+   */
+  frame() {
+    return this._frame;
   }
 
   /**
diff --git a/third_party/WebKit/Source/devtools/front_end/sources/XHRBreakpointsSidebarPane.js b/third_party/WebKit/Source/devtools/front_end/sources/XHRBreakpointsSidebarPane.js
index 6f19bfe..f8dca87 100644
--- a/third_party/WebKit/Source/devtools/front_end/sources/XHRBreakpointsSidebarPane.js
+++ b/third_party/WebKit/Source/devtools/front_end/sources/XHRBreakpointsSidebarPane.js
@@ -53,7 +53,7 @@
     function finishEditing(accept, e, text) {
       this.removeListElement(inputElementContainer);
       if (accept) {
-        SDK.xhrBreakpointManager.addBreakpoint(text, true);
+        SDK.domDebuggerManager.addXHRBreakpoint(text, true);
         this._setBreakpoint(text, true);
       }
     }
@@ -114,7 +114,7 @@
      * @this {Sources.XHRBreakpointsSidebarPane}
      */
     function removeBreakpoint() {
-      SDK.xhrBreakpointManager.removeBreakpoint(url);
+      SDK.domDebuggerManager.removeXHRBreakpoint(url);
       this._removeBreakpoint(url);
     }
 
@@ -123,7 +123,7 @@
      */
     function removeAllBreakpoints() {
       for (var url of this._breakpointElements.keys()) {
-        SDK.xhrBreakpointManager.removeBreakpoint(url);
+        SDK.domDebuggerManager.removeXHRBreakpoint(url);
         this._removeBreakpoint(url);
       }
     }
@@ -136,7 +136,7 @@
   }
 
   _checkboxClicked(url, event) {
-    SDK.xhrBreakpointManager.toggleBreakpoint(url, event.target.checked);
+    SDK.domDebuggerManager.toggleXHRBreakpoint(url, event.target.checked);
   }
 
   _labelClicked(url) {
@@ -155,10 +155,10 @@
     function finishEditing(accept, e, text) {
       this.removeListElement(inputElement);
       if (accept) {
-        SDK.xhrBreakpointManager.removeBreakpoint(url);
+        SDK.domDebuggerManager.removeXHRBreakpoint(url);
         this._removeBreakpoint(url);
         var enabled = element ? element._checkboxElement.checked : true;
-        SDK.xhrBreakpointManager.addBreakpoint(text, enabled);
+        SDK.domDebuggerManager.addXHRBreakpoint(text, enabled);
         this._setBreakpoint(text, enabled);
       } else {
         element.classList.remove('hidden');
@@ -196,7 +196,7 @@
   }
 
   _restoreBreakpoints() {
-    var breakpoints = SDK.xhrBreakpointManager.breakpoints();
+    var breakpoints = SDK.domDebuggerManager.xhrBreakpoints();
     for (var url of breakpoints.keys())
       this._setBreakpoint(url, breakpoints.get(url));
   }
diff --git a/third_party/WebKit/Source/devtools/front_end/timeline/TimelineFlameChartView.js b/third_party/WebKit/Source/devtools/front_end/timeline/TimelineFlameChartView.js
index 8a1df75..81d5220 100644
--- a/third_party/WebKit/Source/devtools/front_end/timeline/TimelineFlameChartView.js
+++ b/third_party/WebKit/Source/devtools/front_end/timeline/TimelineFlameChartView.js
@@ -154,7 +154,7 @@
    * @param {!Common.Event} commonEvent
    */
   _onEntryHighlighted(commonEvent) {
-    SDK.DOMModel.hideDOMNodeHighlight();
+    SDK.OverlayModel.hideDOMNodeHighlight();
     var entryIndex = /** @type {number} */ (commonEvent.data);
     var event = this._mainDataProvider.eventByIndex(entryIndex);
     if (!event)
diff --git a/third_party/WebKit/Source/devtools/scripts/build/generate_protocol_externs.py b/third_party/WebKit/Source/devtools/scripts/build/generate_protocol_externs.py
index bd7e22f..c6d0c3b 100755
--- a/third_party/WebKit/Source/devtools/scripts/build/generate_protocol_externs.py
+++ b/third_party/WebKit/Source/devtools/scripts/build/generate_protocol_externs.py
@@ -126,17 +126,18 @@
             for command in domain["commands"]:
                 output_file.write("\n/**\n")
                 params = []
-                param_to_type = {}
+                in_param_to_type = {}
+                out_param_to_type = {}
                 has_return_value = "returns" in command
                 if "parameters" in command:
                     for in_param in command["parameters"]:
                         in_param_name = param_name(in_param)
                         if "optional" in in_param:
-                            param_to_type[in_param_name] = "(%s|undefined)" % param_type(domain_name, in_param)
+                            in_param_to_type[in_param_name] = "(%s|undefined)" % param_type(domain_name, in_param)
                             params.append("opt_%s" % in_param_name)
                             output_file.write(" * @param {%s=} opt_%s\n" % (param_type(domain_name, in_param), in_param_name))
                         else:
-                            param_to_type[in_param_name] = param_type(domain_name, in_param)
+                            in_param_to_type[in_param_name] = param_type(domain_name, in_param)
                             params.append(in_param_name)
                             output_file.write(" * @param {%s} %s\n" % (param_type(domain_name, in_param), in_param_name))
                 returns = []
@@ -145,10 +146,12 @@
                     returns.append("%s=" % param_type(domain_name, command["error"]))
                 if (has_return_value):
                     for out_param in command["returns"]:
+                        out_param_type = param_type(domain_name, out_param)
+                        out_param_to_type[out_param["name"]] = out_param_type
                         if ("optional" in out_param):
-                            returns.append("%s=" % param_type(domain_name, out_param))
+                            returns.append("%s=" % out_param_type)
                         else:
-                            returns.append("%s" % param_type(domain_name, out_param))
+                            returns.append("%s" % out_param_type)
                 output_file.write(" * @param {function(%s):T=} opt_callback\n" % ", ".join(returns))
                 output_file.write(" * @return {!Promise<T>}\n")
                 output_file.write(" * @template T\n")
@@ -157,20 +160,32 @@
                 output_file.write(" */\n")
                 output_file.write("Protocol.%sAgent.prototype.%s = function(%s) {};\n" %
                                   (domain_name, command["name"], ", ".join(params)))
+
                 request_object_properties = []
-                for param in param_to_type:
-                    request_object_properties.append("%s: %s" % (param, param_to_type[param]))
+                for param in in_param_to_type:
+                    request_object_properties.append("%s: %s" % (param, in_param_to_type[param]))
                 if request_object_properties:
-                    output_file.write("/** @typedef {!{%s}} obj */\n" % (", ".join(request_object_properties)))
+                    output_file.write("/** @typedef {!{%s}} */\n" % (", ".join(request_object_properties)))
                 else:
-                    output_file.write("/** @typedef {Object|undefined} obj */\n")
-                request = "Protocol.%sAgent.prototype.%s.Request" % (domain_name, command["name"])
-                output_file.write("%s;\n" % (request))
+                    output_file.write("/** @typedef {Object|undefined} */\n")
+                request = "Protocol.%sAgent.%sRequest" % (domain_name, to_title_case(command["name"]))
+                output_file.write("%s;\n" % request)
+
+                response_object_properties = []
+                for param in out_param_to_type:
+                    response_object_properties.append("%s: %s" % (param, out_param_to_type[param]))
+                if response_object_properties:
+                    output_file.write("/** @typedef {!{%s}} */\n" % (", ".join(response_object_properties)))
+                else:
+                    output_file.write("/** @typedef {Object|undefined} */\n")
+                response = "Protocol.%sAgent.%sResponse" % (domain_name, to_title_case(command["name"]))
+                output_file.write("%s;\n" % response)
+
                 output_file.write("/**\n")
-                output_file.write(" * @param {!%s} obj\n" % (request))
-                output_file.write(" * @param {function(%s):void=} opt_callback\n" % ", ".join(returns))
+                output_file.write(" * @param {!%s} obj\n" % request)
+                output_file.write(" * @return {!Promise<!%s>}" % response)
                 output_file.write(" */\n")
-                output_file.write("Protocol.%sAgent.prototype.invoke_%s = function(obj, opt_callback) {};\n" %
+                output_file.write("Protocol.%sAgent.prototype.invoke_%s = function(obj) {};\n" %
                                   (domain_name, command["name"]))
 
         if "types" in domain:
diff --git a/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5 b/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5
index 66a85e11..ab53282e 100644
--- a/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5
+++ b/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5
@@ -71,6 +71,10 @@
       status: "test",
     },
     {
+      name: "AllowContentInitiatedDataUrlNavigations",
+      status: "stable",
+    },
+    {
       name: "AudioOutputDevices",
       status: "stable",
     },
diff --git a/third_party/WebKit/Source/platform/network/NetworkUtils.cpp b/third_party/WebKit/Source/platform/network/NetworkUtils.cpp
index 4f13584..060fd65 100644
--- a/third_party/WebKit/Source/platform/network/NetworkUtils.cpp
+++ b/third_party/WebKit/Source/platform/network/NetworkUtils.cpp
@@ -105,6 +105,16 @@
   return data;
 }
 
+bool IsDataURLMimeTypeSupported(const KURL& url) {
+  std::string utf8_mime_type;
+  std::string utf8_charset;
+  if (net::DataURL::Parse(WebStringToGURL(url.GetString()), &utf8_mime_type,
+                          &utf8_charset, nullptr)) {
+    return mime_util::IsSupportedMimeType(utf8_mime_type);
+  }
+  return false;
+}
+
 bool IsRedirectResponseCode(int response_code) {
   return net::HttpResponseHeaders::IsRedirectResponseCode(response_code);
 }
diff --git a/third_party/WebKit/Source/platform/network/NetworkUtils.h b/third_party/WebKit/Source/platform/network/NetworkUtils.h
index 995a99e..18a2879 100644
--- a/third_party/WebKit/Source/platform/network/NetworkUtils.h
+++ b/third_party/WebKit/Source/platform/network/NetworkUtils.h
@@ -34,6 +34,10 @@
     const KURL&,
     ResourceResponse&);
 
+// Returns true if the URL is a data URL and its MIME type is in the list of
+// supported/recognized MIME types.
+PLATFORM_EXPORT bool IsDataURLMimeTypeSupported(const KURL&);
+
 PLATFORM_EXPORT bool IsRedirectResponseCode(int);
 
 }  // NetworkUtils
diff --git a/third_party/WebKit/Source/web/BUILD.gn b/third_party/WebKit/Source/web/BUILD.gn
index aecc65d9..6ee84e75 100644
--- a/third_party/WebKit/Source/web/BUILD.gn
+++ b/third_party/WebKit/Source/web/BUILD.gn
@@ -79,10 +79,8 @@
     "IndexedDBClientImpl.h",
     "InspectorEmulationAgent.cpp",
     "InspectorEmulationAgent.h",
-    "InspectorOverlay.cpp",
-    "InspectorOverlay.h",
-    "InspectorRenderingAgent.cpp",
-    "InspectorRenderingAgent.h",
+    "InspectorOverlayAgent.cpp",
+    "InspectorOverlayAgent.h",
     "LinkHighlightImpl.cpp",
     "LinkHighlightImpl.h",
     "LocalFileSystemClient.cpp",
diff --git a/third_party/WebKit/Source/web/InspectorOverlay.cpp b/third_party/WebKit/Source/web/InspectorOverlay.cpp
deleted file mode 100644
index 850d283..0000000
--- a/third_party/WebKit/Source/web/InspectorOverlay.cpp
+++ /dev/null
@@ -1,827 +0,0 @@
-/*
- * Copyright (C) 2011 Google Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1.  Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer.
- * 2.  Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in the
- *     documentation and/or other materials provided with the distribution.
- * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
- *     its contributors may be used to endorse or promote products derived
- *     from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "web/InspectorOverlay.h"
-
-#include <memory>
-
-#include "bindings/core/v8/ScriptController.h"
-#include "bindings/core/v8/ScriptSourceCode.h"
-#include "bindings/core/v8/V8Binding.h"
-#include "bindings/core/v8/V8InspectorOverlayHost.h"
-#include "core/dom/Node.h"
-#include "core/dom/StaticNodeList.h"
-#include "core/dom/TaskRunnerHelper.h"
-#include "core/frame/FrameView.h"
-#include "core/frame/LocalFrame.h"
-#include "core/frame/LocalFrameClient.h"
-#include "core/frame/Settings.h"
-#include "core/frame/VisualViewport.h"
-#include "core/html/HTMLFrameOwnerElement.h"
-#include "core/input/EventHandler.h"
-#include "core/inspector/InspectorOverlayHost.h"
-#include "core/layout/api/LayoutViewItem.h"
-#include "core/loader/EmptyClients.h"
-#include "core/loader/FrameLoadRequest.h"
-#include "core/page/ChromeClient.h"
-#include "core/page/Page.h"
-#include "platform/ScriptForbiddenScope.h"
-#include "platform/graphics/Color.h"
-#include "platform/graphics/GraphicsContext.h"
-#include "platform/graphics/paint/CullRect.h"
-#include "platform/wtf/AutoReset.h"
-#include "public/platform/Platform.h"
-#include "public/platform/WebData.h"
-#include "v8/include/v8.h"
-#include "web/ChromeClientImpl.h"
-#include "web/PageOverlay.h"
-#include "web/WebInputEventConversion.h"
-#include "web/WebLocalFrameImpl.h"
-
-namespace blink {
-
-namespace {
-
-Node* HoveredNodeForPoint(LocalFrame* frame,
-                          const IntPoint& point_in_root_frame,
-                          bool ignore_pointer_events_none) {
-  HitTestRequest::HitTestRequestType hit_type =
-      HitTestRequest::kMove | HitTestRequest::kReadOnly |
-      HitTestRequest::kAllowChildFrameContent;
-  if (ignore_pointer_events_none)
-    hit_type |= HitTestRequest::kIgnorePointerEventsNone;
-  HitTestRequest request(hit_type);
-  HitTestResult result(request,
-                       frame->View()->RootFrameToContents(point_in_root_frame));
-  frame->ContentLayoutItem().HitTest(result);
-  Node* node = result.InnerPossiblyPseudoNode();
-  while (node && node->getNodeType() == Node::kTextNode)
-    node = node->parentNode();
-  return node;
-}
-
-Node* HoveredNodeForEvent(LocalFrame* frame,
-                          const WebGestureEvent& event,
-                          bool ignore_pointer_events_none) {
-  return HoveredNodeForPoint(frame,
-                             RoundedIntPoint(event.PositionInRootFrame()),
-                             ignore_pointer_events_none);
-}
-
-Node* HoveredNodeForEvent(LocalFrame* frame,
-                          const WebMouseEvent& event,
-                          bool ignore_pointer_events_none) {
-  return HoveredNodeForPoint(frame,
-                             RoundedIntPoint(event.PositionInRootFrame()),
-                             ignore_pointer_events_none);
-}
-
-Node* HoveredNodeForEvent(LocalFrame* frame,
-                          const WebTouchEvent& event,
-                          bool ignore_pointer_events_none) {
-  if (!event.touches_length)
-    return nullptr;
-  WebTouchPoint transformed_point = event.TouchPointInRootFrame(0);
-  return HoveredNodeForPoint(frame, RoundedIntPoint(transformed_point.position),
-                             ignore_pointer_events_none);
-}
-}  // namespace
-
-class InspectorOverlay::InspectorPageOverlayDelegate final
-    : public PageOverlay::Delegate {
- public:
-  explicit InspectorPageOverlayDelegate(InspectorOverlay& overlay)
-      : overlay_(&overlay) {}
-
-  void PaintPageOverlay(const PageOverlay&,
-                        GraphicsContext& graphics_context,
-                        const WebSize& web_view_size) const override {
-    if (overlay_->IsEmpty())
-      return;
-
-    FrameView* view = overlay_->OverlayMainFrame()->View();
-    DCHECK(!view->NeedsLayout());
-    view->Paint(graphics_context,
-                CullRect(IntRect(0, 0, view->Width(), view->Height())));
-  }
-
- private:
-  Persistent<InspectorOverlay> overlay_;
-};
-
-class InspectorOverlay::InspectorOverlayChromeClient final
-    : public EmptyChromeClient {
- public:
-  static InspectorOverlayChromeClient* Create(ChromeClient& client,
-                                              InspectorOverlay& overlay) {
-    return new InspectorOverlayChromeClient(client, overlay);
-  }
-
-  DEFINE_INLINE_VIRTUAL_TRACE() {
-    visitor->Trace(client_);
-    visitor->Trace(overlay_);
-    EmptyChromeClient::Trace(visitor);
-  }
-
-  void SetCursor(const Cursor& cursor, LocalFrame* local_root) override {
-    ToChromeClientImpl(client_)->SetCursorOverridden(false);
-    ToChromeClientImpl(client_)->SetCursor(cursor,
-                                           overlay_->frame_impl_->GetFrame());
-    ToChromeClientImpl(client_)->SetCursorOverridden(false);
-  }
-
-  void SetToolTip(LocalFrame& frame,
-                  const String& tooltip,
-                  TextDirection direction) override {
-    DCHECK_EQ(&frame, overlay_->OverlayMainFrame());
-    client_->SetToolTip(*overlay_->frame_impl_->GetFrame(), tooltip, direction);
-  }
-
-  void InvalidateRect(const IntRect&) override { overlay_->Invalidate(); }
-
-  void ScheduleAnimation(LocalFrame* frame) override {
-    if (overlay_->in_layout_)
-      return;
-
-    client_->ScheduleAnimation(frame);
-  }
-
- private:
-  InspectorOverlayChromeClient(ChromeClient& client, InspectorOverlay& overlay)
-      : client_(&client), overlay_(&overlay) {}
-
-  Member<ChromeClient> client_;
-  Member<InspectorOverlay> overlay_;
-};
-
-InspectorOverlay::InspectorOverlay(WebLocalFrameImpl* frame_impl)
-    : frame_impl_(frame_impl),
-      overlay_host_(InspectorOverlayHost::Create()),
-      draw_view_size_(false),
-      resize_timer_active_(false),
-      omit_tooltip_(false),
-      timer_(TaskRunnerHelper::Get(TaskType::kUnspecedTimer,
-                                   frame_impl->GetFrame()),
-             this,
-             &InspectorOverlay::OnTimer),
-      suspended_(false),
-      show_reloading_blanket_(false),
-      in_layout_(false),
-      needs_update_(false),
-      swallow_next_mouse_up_(false),
-      inspect_mode_(InspectorDOMAgent::kNotSearching) {}
-
-InspectorOverlay::~InspectorOverlay() {
-  DCHECK(!overlay_page_);
-}
-
-DEFINE_TRACE(InspectorOverlay) {
-  visitor->Trace(frame_impl_);
-  visitor->Trace(highlight_node_);
-  visitor->Trace(event_target_node_);
-  visitor->Trace(overlay_page_);
-  visitor->Trace(overlay_chrome_client_);
-  visitor->Trace(overlay_host_);
-  visitor->Trace(dom_agent_);
-  visitor->Trace(hovered_node_for_inspect_mode_);
-}
-
-void InspectorOverlay::Init(v8_inspector::V8InspectorSession* v8_session,
-                            InspectorDOMAgent* dom_agent) {
-  v8_session_ = v8_session;
-  dom_agent_ = dom_agent;
-  overlay_host_->SetListener(this);
-}
-
-void InspectorOverlay::Invalidate() {
-  if (!page_overlay_) {
-    page_overlay_ = PageOverlay::Create(
-        frame_impl_, WTF::WrapUnique(new InspectorPageOverlayDelegate(*this)));
-  }
-
-  page_overlay_->Update();
-}
-
-void InspectorOverlay::UpdateAllLifecyclePhases() {
-  if (IsEmpty())
-    return;
-
-  AutoReset<bool> scoped(&in_layout_, true);
-  if (needs_update_) {
-    needs_update_ = false;
-    RebuildOverlayPage();
-  }
-  OverlayMainFrame()->View()->UpdateAllLifecyclePhases();
-}
-
-bool InspectorOverlay::HandleInputEvent(const WebInputEvent& input_event) {
-  bool handled = false;
-
-  if (IsEmpty())
-    return false;
-
-  if (input_event.GetType() == WebInputEvent::kGestureTap) {
-    // We only have a use for gesture tap.
-    WebGestureEvent transformed_event = TransformWebGestureEvent(
-        frame_impl_->GetFrameView(),
-        static_cast<const WebGestureEvent&>(input_event));
-    handled = HandleGestureEvent(transformed_event);
-    if (handled)
-      return true;
-
-    OverlayMainFrame()->GetEventHandler().HandleGestureEvent(transformed_event);
-  }
-  if (WebInputEvent::IsMouseEventType(input_event.GetType())) {
-    WebMouseEvent mouse_event =
-        TransformWebMouseEvent(frame_impl_->GetFrameView(),
-                               static_cast<const WebMouseEvent&>(input_event));
-
-    if (mouse_event.GetType() == WebInputEvent::kMouseMove)
-      handled = HandleMouseMove(mouse_event);
-    else if (mouse_event.GetType() == WebInputEvent::kMouseDown)
-      handled = HandleMouseDown();
-    else if (mouse_event.GetType() == WebInputEvent::kMouseUp)
-      handled = HandleMouseUp();
-
-    if (handled)
-      return true;
-
-    if (mouse_event.GetType() == WebInputEvent::kMouseMove) {
-      handled = OverlayMainFrame()->GetEventHandler().HandleMouseMoveEvent(
-                    mouse_event, TransformWebMouseEventVector(
-                                     frame_impl_->GetFrameView(),
-                                     std::vector<const WebInputEvent*>())) !=
-                WebInputEventResult::kNotHandled;
-    }
-    if (mouse_event.GetType() == WebInputEvent::kMouseDown)
-      handled = OverlayMainFrame()->GetEventHandler().HandleMousePressEvent(
-                    mouse_event) != WebInputEventResult::kNotHandled;
-    if (mouse_event.GetType() == WebInputEvent::kMouseUp)
-      handled = OverlayMainFrame()->GetEventHandler().HandleMouseReleaseEvent(
-                    mouse_event) != WebInputEventResult::kNotHandled;
-  }
-
-  if (WebInputEvent::IsTouchEventType(input_event.GetType())) {
-    WebTouchEvent transformed_event =
-        TransformWebTouchEvent(frame_impl_->GetFrameView(),
-                               static_cast<const WebTouchEvent&>(input_event));
-    handled = HandleTouchEvent(transformed_event);
-    if (handled)
-      return true;
-    OverlayMainFrame()->GetEventHandler().HandleTouchEvent(
-        transformed_event, Vector<WebTouchEvent>());
-  }
-  if (WebInputEvent::IsKeyboardEventType(input_event.GetType())) {
-    OverlayMainFrame()->GetEventHandler().KeyEvent(
-        static_cast<const WebKeyboardEvent&>(input_event));
-  }
-
-  if (input_event.GetType() == WebInputEvent::kMouseWheel) {
-    WebMouseWheelEvent transformed_event = TransformWebMouseWheelEvent(
-        frame_impl_->GetFrameView(),
-        static_cast<const WebMouseWheelEvent&>(input_event));
-    handled = OverlayMainFrame()->GetEventHandler().HandleWheelEvent(
-                  transformed_event) != WebInputEventResult::kNotHandled;
-  }
-
-  return handled;
-}
-
-void InspectorOverlay::SetPausedInDebuggerMessage(const String& message) {
-  paused_in_debugger_message_ = message;
-  ScheduleUpdate();
-}
-
-void InspectorOverlay::ShowReloadingBlanket() {
-  show_reloading_blanket_ = true;
-  ScheduleUpdate();
-}
-
-void InspectorOverlay::HideReloadingBlanket() {
-  if (!show_reloading_blanket_)
-    return;
-  show_reloading_blanket_ = false;
-  if (suspended_)
-    ClearInternal();
-  else
-    ScheduleUpdate();
-}
-
-void InspectorOverlay::HideHighlight() {
-  highlight_node_.Clear();
-  event_target_node_.Clear();
-  highlight_quad_.reset();
-  ScheduleUpdate();
-}
-
-void InspectorOverlay::HighlightNode(
-    Node* node,
-    const InspectorHighlightConfig& highlight_config,
-    bool omit_tooltip) {
-  HighlightNode(node, nullptr, highlight_config, omit_tooltip);
-}
-
-void InspectorOverlay::HighlightNode(
-    Node* node,
-    Node* event_target,
-    const InspectorHighlightConfig& highlight_config,
-    bool omit_tooltip) {
-  node_highlight_config_ = highlight_config;
-  highlight_node_ = node;
-  event_target_node_ = event_target;
-  omit_tooltip_ = omit_tooltip;
-  ScheduleUpdate();
-}
-
-void InspectorOverlay::SetInspectMode(
-    InspectorDOMAgent::SearchMode search_mode,
-    std::unique_ptr<InspectorHighlightConfig> highlight_config) {
-  inspect_mode_ = search_mode;
-  ScheduleUpdate();
-
-  if (search_mode != InspectorDOMAgent::kNotSearching) {
-    inspect_mode_highlight_config_ = std::move(highlight_config);
-  } else {
-    hovered_node_for_inspect_mode_.Clear();
-    HideHighlight();
-  }
-}
-
-void InspectorOverlay::HighlightQuad(
-    std::unique_ptr<FloatQuad> quad,
-    const InspectorHighlightConfig& highlight_config) {
-  quad_highlight_config_ = highlight_config;
-  highlight_quad_ = std::move(quad);
-  omit_tooltip_ = false;
-  ScheduleUpdate();
-}
-
-bool InspectorOverlay::IsEmpty() {
-  if (show_reloading_blanket_)
-    return false;
-  if (suspended_)
-    return true;
-  bool has_visible_elements = highlight_node_ || event_target_node_ ||
-                              highlight_quad_ ||
-                              (resize_timer_active_ && draw_view_size_) ||
-                              !paused_in_debugger_message_.IsNull();
-  return !has_visible_elements &&
-         inspect_mode_ == InspectorDOMAgent::kNotSearching;
-}
-
-void InspectorOverlay::ScheduleUpdate() {
-  if (IsEmpty()) {
-    if (page_overlay_)
-      page_overlay_.reset();
-    return;
-  }
-  needs_update_ = true;
-  LocalFrame* frame = frame_impl_->GetFrame();
-  if (frame) {
-    frame->GetPage()->GetChromeClient().ScheduleAnimation(frame);
-  }
-}
-
-void InspectorOverlay::RebuildOverlayPage() {
-  FrameView* view = frame_impl_->GetFrameView();
-  LocalFrame* frame = frame_impl_->GetFrame();
-  if (!view || !frame)
-    return;
-
-  IntRect visible_rect_in_document =
-      view->GetScrollableArea()->VisibleContentRect();
-  IntSize viewport_size = frame->GetPage()->GetVisualViewport().Size();
-  OverlayMainFrame()->View()->Resize(viewport_size);
-  OverlayPage()->GetVisualViewport().SetSize(viewport_size);
-  OverlayMainFrame()->SetPageZoomFactor(WindowToViewportScale());
-
-  Reset(viewport_size, visible_rect_in_document.Location());
-
-  if (show_reloading_blanket_) {
-    EvaluateInOverlay("showReloadingBlanket", "");
-    return;
-  }
-  DrawNodeHighlight();
-  DrawQuadHighlight();
-  DrawPausedInDebuggerMessage();
-  DrawViewSize();
-}
-
-static std::unique_ptr<protocol::DictionaryValue> BuildObjectForSize(
-    const IntSize& size) {
-  std::unique_ptr<protocol::DictionaryValue> result =
-      protocol::DictionaryValue::create();
-  result->setInteger("width", size.Width());
-  result->setInteger("height", size.Height());
-  return result;
-}
-
-void InspectorOverlay::DrawNodeHighlight() {
-  if (!highlight_node_)
-    return;
-
-  String selectors = node_highlight_config_.selector_list;
-  StaticElementList* elements = nullptr;
-  DummyExceptionStateForTesting exception_state;
-  ContainerNode* query_base = highlight_node_->ContainingShadowRoot();
-  if (!query_base)
-    query_base = highlight_node_->ownerDocument();
-  if (selectors.length())
-    elements =
-        query_base->QuerySelectorAll(AtomicString(selectors), exception_state);
-  if (elements && !exception_state.HadException()) {
-    for (unsigned i = 0; i < elements->length(); ++i) {
-      Element* element = elements->item(i);
-      InspectorHighlight highlight(element, node_highlight_config_, false);
-      std::unique_ptr<protocol::DictionaryValue> highlight_json =
-          highlight.AsProtocolValue();
-      EvaluateInOverlay("drawHighlight", std::move(highlight_json));
-    }
-  }
-
-  bool append_element_info =
-      highlight_node_->IsElementNode() && !omit_tooltip_ &&
-      node_highlight_config_.show_info && highlight_node_->GetLayoutObject() &&
-      highlight_node_->GetDocument().GetFrame();
-  InspectorHighlight highlight(highlight_node_.Get(), node_highlight_config_,
-                               append_element_info);
-  if (event_target_node_)
-    highlight.AppendEventTargetQuads(event_target_node_.Get(),
-                                     node_highlight_config_);
-
-  std::unique_ptr<protocol::DictionaryValue> highlight_json =
-      highlight.AsProtocolValue();
-  EvaluateInOverlay("drawHighlight", std::move(highlight_json));
-}
-
-void InspectorOverlay::DrawQuadHighlight() {
-  if (!highlight_quad_)
-    return;
-
-  InspectorHighlight highlight(WindowToViewportScale());
-  highlight.AppendQuad(*highlight_quad_, quad_highlight_config_.content,
-                       quad_highlight_config_.content_outline);
-  EvaluateInOverlay("drawHighlight", highlight.AsProtocolValue());
-}
-
-void InspectorOverlay::DrawPausedInDebuggerMessage() {
-  if (inspect_mode_ == InspectorDOMAgent::kNotSearching &&
-      !paused_in_debugger_message_.IsNull())
-    EvaluateInOverlay("drawPausedInDebuggerMessage",
-                      paused_in_debugger_message_);
-}
-
-void InspectorOverlay::DrawViewSize() {
-  if (resize_timer_active_ && draw_view_size_)
-    EvaluateInOverlay("drawViewSize", "");
-}
-
-float InspectorOverlay::WindowToViewportScale() const {
-  LocalFrame* frame = frame_impl_->GetFrame();
-  if (!frame)
-    return 1.0f;
-  return frame->GetPage()->GetChromeClient().WindowToViewportScalar(1.0f);
-}
-
-Page* InspectorOverlay::OverlayPage() {
-  if (overlay_page_)
-    return overlay_page_.Get();
-
-  ScriptForbiddenScope::AllowUserAgentScript allow_script;
-
-  DEFINE_STATIC_LOCAL(LocalFrameClient, dummy_local_frame_client,
-                      (EmptyLocalFrameClient::Create()));
-  Page::PageClients page_clients;
-  FillWithEmptyClients(page_clients);
-  DCHECK(!overlay_chrome_client_);
-  overlay_chrome_client_ = InspectorOverlayChromeClient::Create(
-      frame_impl_->GetFrame()->GetPage()->GetChromeClient(), *this);
-  page_clients.chrome_client = overlay_chrome_client_.Get();
-  overlay_page_ = Page::Create(page_clients);
-
-  Settings& settings = frame_impl_->GetFrame()->GetPage()->GetSettings();
-  Settings& overlay_settings = overlay_page_->GetSettings();
-
-  overlay_settings.GetGenericFontFamilySettings().UpdateStandard(
-      settings.GetGenericFontFamilySettings().Standard());
-  overlay_settings.GetGenericFontFamilySettings().UpdateSerif(
-      settings.GetGenericFontFamilySettings().Serif());
-  overlay_settings.GetGenericFontFamilySettings().UpdateSansSerif(
-      settings.GetGenericFontFamilySettings().SansSerif());
-  overlay_settings.GetGenericFontFamilySettings().UpdateCursive(
-      settings.GetGenericFontFamilySettings().Cursive());
-  overlay_settings.GetGenericFontFamilySettings().UpdateFantasy(
-      settings.GetGenericFontFamilySettings().Fantasy());
-  overlay_settings.GetGenericFontFamilySettings().UpdatePictograph(
-      settings.GetGenericFontFamilySettings().Pictograph());
-  overlay_settings.SetMinimumFontSize(settings.GetMinimumFontSize());
-  overlay_settings.SetMinimumLogicalFontSize(
-      settings.GetMinimumLogicalFontSize());
-  overlay_settings.SetScriptEnabled(true);
-  overlay_settings.SetPluginsEnabled(false);
-  overlay_settings.SetLoadsImagesAutomatically(true);
-  // FIXME: http://crbug.com/363843. Inspector should probably create its
-  // own graphics layers and attach them to the tree rather than going
-  // through some non-composited paint function.
-  overlay_settings.SetAcceleratedCompositingEnabled(false);
-
-  LocalFrame* frame =
-      LocalFrame::Create(&dummy_local_frame_client, *overlay_page_, 0);
-  frame->SetView(FrameView::Create(*frame));
-  frame->Init();
-  FrameLoader& loader = frame->Loader();
-  frame->View()->SetCanHaveScrollbars(false);
-  frame->View()->SetBaseBackgroundColor(Color::kTransparent);
-
-  const WebData& overlay_page_html_resource =
-      Platform::Current()->LoadResource("InspectorOverlayPage.html");
-  loader.Load(
-      FrameLoadRequest(0, ResourceRequest(BlankURL()),
-                       SubstituteData(overlay_page_html_resource, "text/html",
-                                      "UTF-8", KURL(), kForceSynchronousLoad)));
-  v8::Isolate* isolate = ToIsolate(frame);
-  ScriptState* script_state = ToScriptStateForMainWorld(frame);
-  DCHECK(script_state);
-  ScriptState::Scope scope(script_state);
-  v8::Local<v8::Object> global = script_state->GetContext()->Global();
-  v8::Local<v8::Value> overlay_host_obj =
-      ToV8(overlay_host_.Get(), global, isolate);
-  DCHECK(!overlay_host_obj.IsEmpty());
-  global
-      ->Set(script_state->GetContext(),
-            V8AtomicString(isolate, "InspectorOverlayHost"), overlay_host_obj)
-      .ToChecked();
-
-#if OS(WIN)
-  EvaluateInOverlay("setPlatform", "windows");
-#elif OS(MACOSX)
-  EvaluateInOverlay("setPlatform", "mac");
-#elif OS(POSIX)
-  EvaluateInOverlay("setPlatform", "linux");
-#endif
-
-  return overlay_page_.Get();
-}
-
-LocalFrame* InspectorOverlay::OverlayMainFrame() {
-  return ToLocalFrame(OverlayPage()->MainFrame());
-}
-
-void InspectorOverlay::Reset(const IntSize& viewport_size,
-                             const IntPoint& document_scroll_offset) {
-  std::unique_ptr<protocol::DictionaryValue> reset_data =
-      protocol::DictionaryValue::create();
-  reset_data->setDouble(
-      "deviceScaleFactor",
-      frame_impl_->GetFrame()->GetPage()->DeviceScaleFactorDeprecated());
-  reset_data->setDouble(
-      "pageScaleFactor",
-      frame_impl_->GetFrame()->GetPage()->GetVisualViewport().Scale());
-
-  IntRect viewport_in_screen =
-      frame_impl_->GetFrame()->GetPage()->GetChromeClient().ViewportToScreen(
-          IntRect(IntPoint(), viewport_size), frame_impl_->GetFrame()->View());
-  reset_data->setObject("viewportSize",
-                        BuildObjectForSize(viewport_in_screen.Size()));
-
-  // The zoom factor in the overlay frame already has been multiplied by the
-  // window to viewport scale (aka device scale factor), so cancel it.
-  reset_data->setDouble(
-      "pageZoomFactor",
-      frame_impl_->GetFrame()->PageZoomFactor() / WindowToViewportScale());
-
-  reset_data->setInteger("scrollX", document_scroll_offset.X());
-  reset_data->setInteger("scrollY", document_scroll_offset.Y());
-  EvaluateInOverlay("reset", std::move(reset_data));
-}
-
-void InspectorOverlay::EvaluateInOverlay(const String& method,
-                                         const String& argument) {
-  ScriptForbiddenScope::AllowUserAgentScript allow_script;
-  std::unique_ptr<protocol::ListValue> command = protocol::ListValue::create();
-  command->pushValue(protocol::StringValue::create(method));
-  command->pushValue(protocol::StringValue::create(argument));
-  ToLocalFrame(OverlayPage()->MainFrame())
-      ->GetScriptController()
-      .ExecuteScriptInMainWorld(
-          "dispatch(" + command->serialize() + ")",
-          ScriptController::kExecuteScriptWhenScriptsDisabled);
-}
-
-void InspectorOverlay::EvaluateInOverlay(
-    const String& method,
-    std::unique_ptr<protocol::Value> argument) {
-  ScriptForbiddenScope::AllowUserAgentScript allow_script;
-  std::unique_ptr<protocol::ListValue> command = protocol::ListValue::create();
-  command->pushValue(protocol::StringValue::create(method));
-  command->pushValue(std::move(argument));
-  ToLocalFrame(OverlayPage()->MainFrame())
-      ->GetScriptController()
-      .ExecuteScriptInMainWorld(
-          "dispatch(" + command->serialize() + ")",
-          ScriptController::kExecuteScriptWhenScriptsDisabled);
-}
-
-String InspectorOverlay::EvaluateInOverlayForTest(const String& script) {
-  ScriptForbiddenScope::AllowUserAgentScript allow_script;
-  v8::HandleScope handle_scope(ToIsolate(OverlayMainFrame()));
-  v8::Local<v8::Value> string =
-      ToLocalFrame(OverlayPage()->MainFrame())
-          ->GetScriptController()
-          .ExecuteScriptInMainWorldAndReturnValue(
-              ScriptSourceCode(script),
-              ScriptController::kExecuteScriptWhenScriptsDisabled);
-  return ToCoreStringWithUndefinedOrNullCheck(string);
-}
-
-void InspectorOverlay::OnTimer(TimerBase*) {
-  resize_timer_active_ = false;
-  ScheduleUpdate();
-}
-
-void InspectorOverlay::ClearInternal() {
-  if (overlay_page_) {
-    overlay_page_->WillBeDestroyed();
-    overlay_page_.Clear();
-    overlay_chrome_client_.Clear();
-  }
-  resize_timer_active_ = false;
-  paused_in_debugger_message_ = String();
-  inspect_mode_ = InspectorDOMAgent::kNotSearching;
-  timer_.Stop();
-  HideHighlight();
-}
-
-void InspectorOverlay::Clear() {
-  ClearInternal();
-  v8_session_ = nullptr;
-  dom_agent_.Clear();
-  overlay_host_->SetListener(nullptr);
-}
-
-void InspectorOverlay::OverlayResumed() {
-  if (v8_session_)
-    v8_session_->resume();
-}
-
-void InspectorOverlay::OverlaySteppedOver() {
-  if (v8_session_)
-    v8_session_->stepOver();
-}
-
-void InspectorOverlay::Suspend() {
-  if (!suspended_) {
-    suspended_ = true;
-    ClearInternal();
-  }
-}
-
-void InspectorOverlay::Resume() {
-  suspended_ = false;
-}
-
-void InspectorOverlay::PageLayoutInvalidated(bool resized) {
-  if (resized && draw_view_size_) {
-    resize_timer_active_ = true;
-    timer_.StartOneShot(1, BLINK_FROM_HERE);
-  }
-  ScheduleUpdate();
-}
-
-void InspectorOverlay::SetShowViewportSizeOnResize(bool show) {
-  draw_view_size_ = show;
-}
-
-bool InspectorOverlay::HandleMouseMove(const WebMouseEvent& event) {
-  if (!ShouldSearchForNode())
-    return false;
-
-  LocalFrame* frame = frame_impl_->GetFrame();
-  if (!frame || !frame->View() || frame->ContentLayoutItem().IsNull())
-    return false;
-  Node* node = HoveredNodeForEvent(
-      frame, event, event.GetModifiers() & WebInputEvent::kShiftKey);
-
-  // Do not highlight within user agent shadow root unless requested.
-  if (inspect_mode_ != InspectorDOMAgent::kSearchingForUAShadow) {
-    ShadowRoot* shadow_root = InspectorDOMAgent::UserAgentShadowRoot(node);
-    if (shadow_root)
-      node = &shadow_root->host();
-  }
-
-  // Shadow roots don't have boxes - use host element instead.
-  if (node && node->IsShadowRoot())
-    node = node->ParentOrShadowHostNode();
-
-  if (!node)
-    return true;
-
-  if (node->IsFrameOwnerElement()) {
-    HTMLFrameOwnerElement* frame_owner = ToHTMLFrameOwnerElement(node);
-    if (frame_owner->ContentFrame() &&
-        !frame_owner->ContentFrame()->IsLocalFrame()) {
-      // Do not consume event so that remote frame can handle it.
-      HideHighlight();
-      hovered_node_for_inspect_mode_.Clear();
-      return false;
-    }
-  }
-
-  Node* event_target = (event.GetModifiers() & WebInputEvent::kShiftKey)
-                           ? HoveredNodeForEvent(frame, event, false)
-                           : nullptr;
-  if (event_target == node)
-    event_target = nullptr;
-
-  if (node && inspect_mode_highlight_config_) {
-    hovered_node_for_inspect_mode_ = node;
-    if (dom_agent_)
-      dom_agent_->NodeHighlightedInOverlay(node);
-    HighlightNode(node, event_target, *inspect_mode_highlight_config_,
-                  (event.GetModifiers() &
-                   (WebInputEvent::kControlKey | WebInputEvent::kMetaKey)));
-  }
-  return true;
-}
-
-bool InspectorOverlay::HandleMouseDown() {
-  swallow_next_mouse_up_ = false;
-  if (!ShouldSearchForNode())
-    return false;
-
-  if (hovered_node_for_inspect_mode_) {
-    swallow_next_mouse_up_ = true;
-    Inspect(hovered_node_for_inspect_mode_.Get());
-    hovered_node_for_inspect_mode_.Clear();
-    return true;
-  }
-  return false;
-}
-
-bool InspectorOverlay::HandleMouseUp() {
-  if (swallow_next_mouse_up_) {
-    swallow_next_mouse_up_ = false;
-    return true;
-  }
-  return false;
-}
-
-bool InspectorOverlay::HandleGestureEvent(const WebGestureEvent& event) {
-  if (!ShouldSearchForNode() || event.GetType() != WebInputEvent::kGestureTap)
-    return false;
-  Node* node = HoveredNodeForEvent(frame_impl_->GetFrame(), event, false);
-  if (node && inspect_mode_highlight_config_) {
-    HighlightNode(node, *inspect_mode_highlight_config_, false);
-    Inspect(node);
-    return true;
-  }
-  return false;
-}
-
-bool InspectorOverlay::HandleTouchEvent(const WebTouchEvent& event) {
-  if (!ShouldSearchForNode())
-    return false;
-  Node* node = HoveredNodeForEvent(frame_impl_->GetFrame(), event, false);
-  if (node && inspect_mode_highlight_config_) {
-    HighlightNode(node, *inspect_mode_highlight_config_, false);
-    Inspect(node);
-    return true;
-  }
-  return false;
-}
-
-bool InspectorOverlay::ShouldSearchForNode() {
-  return inspect_mode_ != InspectorDOMAgent::kNotSearching;
-}
-
-void InspectorOverlay::Inspect(Node* node) {
-  if (dom_agent_)
-    dom_agent_->Inspect(node);
-}
-
-}  // namespace blink
diff --git a/third_party/WebKit/Source/web/InspectorOverlay.h b/third_party/WebKit/Source/web/InspectorOverlay.h
deleted file mode 100644
index 6cc0a65..0000000
--- a/third_party/WebKit/Source/web/InspectorOverlay.h
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2011 Google Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1.  Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer.
- * 2.  Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in the
- *     documentation and/or other materials provided with the distribution.
- * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
- *     its contributors may be used to endorse or promote products derived
- *     from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef InspectorOverlay_h
-#define InspectorOverlay_h
-
-#include <v8-inspector.h>
-#include <memory>
-#include "core/inspector/InspectorDOMAgent.h"
-#include "core/inspector/InspectorOverlayHost.h"
-#include "core/inspector/protocol/Forward.h"
-#include "platform/Timer.h"
-#include "platform/geometry/FloatQuad.h"
-#include "platform/geometry/LayoutRect.h"
-#include "platform/graphics/Color.h"
-#include "platform/heap/Handle.h"
-#include "platform/wtf/RefPtr.h"
-#include "platform/wtf/text/WTFString.h"
-#include "public/platform/WebInputEvent.h"
-
-namespace blink {
-
-class Color;
-class LocalFrame;
-class Node;
-class Page;
-class PageOverlay;
-class WebGestureEvent;
-class WebMouseEvent;
-class WebLocalFrameImpl;
-class WebTouchEvent;
-
-namespace protocol {
-class Value;
-}
-
-class InspectorOverlay final
-    : public GarbageCollectedFinalized<InspectorOverlay>,
-      public InspectorDOMAgent::Client,
-      public InspectorOverlayHost::Listener {
-  USING_GARBAGE_COLLECTED_MIXIN(InspectorOverlay);
-
- public:
-  explicit InspectorOverlay(WebLocalFrameImpl*);
-  ~InspectorOverlay() override;
-  DECLARE_TRACE();
-
-  void Init(v8_inspector::V8InspectorSession*, InspectorDOMAgent*);
-
-  void Clear();
-  void Suspend();
-  void Resume();
-  bool HandleInputEvent(const WebInputEvent&);
-  void PageLayoutInvalidated(bool resized);
-  void SetShowViewportSizeOnResize(bool);
-  void ShowReloadingBlanket();
-  void HideReloadingBlanket();
-  void SetPausedInDebuggerMessage(const String&);
-
-  // Does not yet include paint.
-  void UpdateAllLifecyclePhases();
-
-  PageOverlay* GetPageOverlay() { return page_overlay_.get(); };
-  String EvaluateInOverlayForTest(const String&);
-
- private:
-  class InspectorOverlayChromeClient;
-  class InspectorPageOverlayDelegate;
-
-  // InspectorOverlayHost::Listener implementation.
-  void OverlayResumed() override;
-  void OverlaySteppedOver() override;
-
-  // InspectorDOMAgent::Client implementation.
-  void HideHighlight() override;
-  void HighlightNode(Node*,
-                     const InspectorHighlightConfig&,
-                     bool omit_tooltip) override;
-  void HighlightQuad(std::unique_ptr<FloatQuad>,
-                     const InspectorHighlightConfig&) override;
-  void SetInspectMode(InspectorDOMAgent::SearchMode,
-                      std::unique_ptr<InspectorHighlightConfig>) override;
-
-  void HighlightNode(Node*,
-                     Node* event_target,
-                     const InspectorHighlightConfig&,
-                     bool omit_tooltip);
-  bool IsEmpty();
-  void DrawNodeHighlight();
-  void DrawQuadHighlight();
-  void DrawPausedInDebuggerMessage();
-  void DrawViewSize();
-
-  float WindowToViewportScale() const;
-
-  Page* OverlayPage();
-  LocalFrame* OverlayMainFrame();
-  void Reset(const IntSize& viewport_size,
-             const IntPoint& document_scroll_offset);
-  void EvaluateInOverlay(const String& method, const String& argument);
-  void EvaluateInOverlay(const String& method,
-                         std::unique_ptr<protocol::Value> argument);
-  void OnTimer(TimerBase*);
-  void RebuildOverlayPage();
-  void Invalidate();
-  void ScheduleUpdate();
-  void ClearInternal();
-
-  bool HandleMouseDown();
-  bool HandleMouseUp();
-  bool HandleGestureEvent(const WebGestureEvent&);
-  bool HandleTouchEvent(const WebTouchEvent&);
-  bool HandleMouseMove(const WebMouseEvent&);
-  bool ShouldSearchForNode();
-  void Inspect(Node*);
-
-  Member<WebLocalFrameImpl> frame_impl_;
-  String paused_in_debugger_message_;
-  Member<Node> highlight_node_;
-  Member<Node> event_target_node_;
-  InspectorHighlightConfig node_highlight_config_;
-  std::unique_ptr<FloatQuad> highlight_quad_;
-  Member<Page> overlay_page_;
-  Member<InspectorOverlayChromeClient> overlay_chrome_client_;
-  Member<InspectorOverlayHost> overlay_host_;
-  InspectorHighlightConfig quad_highlight_config_;
-  bool draw_view_size_;
-  bool resize_timer_active_;
-  bool omit_tooltip_;
-  TaskRunnerTimer<InspectorOverlay> timer_;
-  bool suspended_;
-  bool show_reloading_blanket_;
-  bool in_layout_;
-  bool needs_update_;
-  v8_inspector::V8InspectorSession* v8_session_;
-  Member<InspectorDOMAgent> dom_agent_;
-  std::unique_ptr<PageOverlay> page_overlay_;
-  Member<Node> hovered_node_for_inspect_mode_;
-  bool swallow_next_mouse_up_;
-  InspectorDOMAgent::SearchMode inspect_mode_;
-  std::unique_ptr<InspectorHighlightConfig> inspect_mode_highlight_config_;
-};
-
-}  // namespace blink
-
-#endif  // InspectorOverlay_h
diff --git a/third_party/WebKit/Source/web/InspectorOverlayAgent.cpp b/third_party/WebKit/Source/web/InspectorOverlayAgent.cpp
new file mode 100644
index 0000000..e5ad62d
--- /dev/null
+++ b/third_party/WebKit/Source/web/InspectorOverlayAgent.cpp
@@ -0,0 +1,1162 @@
+/*
+ * Copyright (C) 2011 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ *     its contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "web/InspectorOverlayAgent.h"
+
+#include <memory>
+
+#include "bindings/core/v8/ScriptController.h"
+#include "bindings/core/v8/ScriptSourceCode.h"
+#include "bindings/core/v8/V8Binding.h"
+#include "bindings/core/v8/V8InspectorOverlayHost.h"
+#include "core/dom/DOMNodeIds.h"
+#include "core/dom/Node.h"
+#include "core/dom/StaticNodeList.h"
+#include "core/dom/TaskRunnerHelper.h"
+#include "core/frame/FrameView.h"
+#include "core/frame/LocalFrame.h"
+#include "core/frame/LocalFrameClient.h"
+#include "core/frame/Settings.h"
+#include "core/frame/VisualViewport.h"
+#include "core/html/HTMLFrameOwnerElement.h"
+#include "core/input/EventHandler.h"
+#include "core/inspector/IdentifiersFactory.h"
+#include "core/inspector/InspectedFrames.h"
+#include "core/inspector/InspectorDOMAgent.h"
+#include "core/inspector/InspectorOverlayHost.h"
+#include "core/layout/api/LayoutViewItem.h"
+#include "core/loader/EmptyClients.h"
+#include "core/loader/FrameLoadRequest.h"
+#include "core/page/ChromeClient.h"
+#include "core/page/Page.h"
+#include "platform/ScriptForbiddenScope.h"
+#include "platform/graphics/Color.h"
+#include "platform/graphics/GraphicsContext.h"
+#include "platform/graphics/paint/CullRect.h"
+#include "platform/wtf/AutoReset.h"
+#include "public/platform/Platform.h"
+#include "public/platform/WebData.h"
+#include "v8/include/v8.h"
+#include "web/ChromeClientImpl.h"
+#include "web/PageOverlay.h"
+#include "web/WebInputEventConversion.h"
+#include "web/WebLocalFrameImpl.h"
+#include "web/WebViewImpl.h"
+
+namespace blink {
+
+using protocol::Maybe;
+using protocol::Response;
+
+namespace {
+
+namespace OverlayAgentState {
+static const char kEnabled[] = "enabled";
+static const char kShowDebugBorders[] = "showDebugBorders";
+static const char kShowFPSCounter[] = "showFPSCounter";
+static const char kShowPaintRects[] = "showPaintRects";
+static const char kShowScrollBottleneckRects[] = "showScrollBottleneckRects";
+static const char kShowSizeOnResize[] = "showSizeOnResize";
+static const char kSuspended[] = "suspended";
+static const char kPausedInDebuggerMessage[] = "pausedInDebuggerMessage";
+}
+
+Node* HoveredNodeForPoint(LocalFrame* frame,
+                          const IntPoint& point_in_root_frame,
+                          bool ignore_pointer_events_none) {
+  HitTestRequest::HitTestRequestType hit_type =
+      HitTestRequest::kMove | HitTestRequest::kReadOnly |
+      HitTestRequest::kAllowChildFrameContent;
+  if (ignore_pointer_events_none)
+    hit_type |= HitTestRequest::kIgnorePointerEventsNone;
+  HitTestRequest request(hit_type);
+  HitTestResult result(request,
+                       frame->View()->RootFrameToContents(point_in_root_frame));
+  frame->ContentLayoutItem().HitTest(result);
+  Node* node = result.InnerPossiblyPseudoNode();
+  while (node && node->getNodeType() == Node::kTextNode)
+    node = node->parentNode();
+  return node;
+}
+
+Node* HoveredNodeForEvent(LocalFrame* frame,
+                          const WebGestureEvent& event,
+                          bool ignore_pointer_events_none) {
+  return HoveredNodeForPoint(frame,
+                             RoundedIntPoint(event.PositionInRootFrame()),
+                             ignore_pointer_events_none);
+}
+
+Node* HoveredNodeForEvent(LocalFrame* frame,
+                          const WebMouseEvent& event,
+                          bool ignore_pointer_events_none) {
+  return HoveredNodeForPoint(frame,
+                             RoundedIntPoint(event.PositionInRootFrame()),
+                             ignore_pointer_events_none);
+}
+
+Node* HoveredNodeForEvent(LocalFrame* frame,
+                          const WebTouchEvent& event,
+                          bool ignore_pointer_events_none) {
+  if (!event.touches_length)
+    return nullptr;
+  WebTouchPoint transformed_point = event.TouchPointInRootFrame(0);
+  return HoveredNodeForPoint(frame, RoundedIntPoint(transformed_point.position),
+                             ignore_pointer_events_none);
+}
+
+bool ParseQuad(std::unique_ptr<protocol::Array<double>> quad_array,
+               FloatQuad* quad) {
+  const size_t kCoordinatesInQuad = 8;
+  if (!quad_array || quad_array->length() != kCoordinatesInQuad)
+    return false;
+  quad->SetP1(FloatPoint(quad_array->get(0), quad_array->get(1)));
+  quad->SetP2(FloatPoint(quad_array->get(2), quad_array->get(3)));
+  quad->SetP3(FloatPoint(quad_array->get(4), quad_array->get(5)));
+  quad->SetP4(FloatPoint(quad_array->get(6), quad_array->get(7)));
+  return true;
+}
+
+}  // namespace
+
+class InspectorOverlayAgent::InspectorPageOverlayDelegate final
+    : public PageOverlay::Delegate {
+ public:
+  explicit InspectorPageOverlayDelegate(InspectorOverlayAgent& overlay)
+      : overlay_(&overlay) {}
+
+  void PaintPageOverlay(const PageOverlay&,
+                        GraphicsContext& graphics_context,
+                        const WebSize& web_view_size) const override {
+    if (overlay_->IsEmpty())
+      return;
+
+    FrameView* view = overlay_->OverlayMainFrame()->View();
+    DCHECK(!view->NeedsLayout());
+    view->Paint(graphics_context,
+                CullRect(IntRect(0, 0, view->Width(), view->Height())));
+  }
+
+ private:
+  Persistent<InspectorOverlayAgent> overlay_;
+};
+
+class InspectorOverlayAgent::InspectorOverlayChromeClient final
+    : public EmptyChromeClient {
+ public:
+  static InspectorOverlayChromeClient* Create(ChromeClient& client,
+                                              InspectorOverlayAgent& overlay) {
+    return new InspectorOverlayChromeClient(client, overlay);
+  }
+
+  DEFINE_INLINE_VIRTUAL_TRACE() {
+    visitor->Trace(client_);
+    visitor->Trace(overlay_);
+    EmptyChromeClient::Trace(visitor);
+  }
+
+  void SetCursor(const Cursor& cursor, LocalFrame* local_root) override {
+    ToChromeClientImpl(client_)->SetCursorOverridden(false);
+    ToChromeClientImpl(client_)->SetCursor(cursor,
+                                           overlay_->frame_impl_->GetFrame());
+    ToChromeClientImpl(client_)->SetCursorOverridden(false);
+  }
+
+  void SetToolTip(LocalFrame& frame,
+                  const String& tooltip,
+                  TextDirection direction) override {
+    DCHECK_EQ(&frame, overlay_->OverlayMainFrame());
+    client_->SetToolTip(*overlay_->frame_impl_->GetFrame(), tooltip, direction);
+  }
+
+  void InvalidateRect(const IntRect&) override { overlay_->Invalidate(); }
+
+  void ScheduleAnimation(LocalFrame* frame) override {
+    if (overlay_->in_layout_)
+      return;
+
+    client_->ScheduleAnimation(frame);
+  }
+
+ private:
+  InspectorOverlayChromeClient(ChromeClient& client,
+                               InspectorOverlayAgent& overlay)
+      : client_(&client), overlay_(&overlay) {}
+
+  Member<ChromeClient> client_;
+  Member<InspectorOverlayAgent> overlay_;
+};
+
+InspectorOverlayAgent::InspectorOverlayAgent(
+    WebLocalFrameImpl* frame_impl,
+    InspectedFrames* inspected_frames,
+    v8_inspector::V8InspectorSession* v8_session,
+    InspectorDOMAgent* dom_agent)
+    : frame_impl_(frame_impl),
+      inspected_frames_(inspected_frames),
+      enabled_(false),
+      overlay_host_(new InspectorOverlayHost(this)),
+      draw_view_size_(false),
+      resize_timer_active_(false),
+      omit_tooltip_(false),
+      timer_(TaskRunnerHelper::Get(TaskType::kUnspecedTimer,
+                                   frame_impl->GetFrame()),
+             this,
+             &InspectorOverlayAgent::OnTimer),
+      suspended_(false),
+      show_reloading_blanket_(false),
+      in_layout_(false),
+      needs_update_(false),
+      v8_session_(v8_session),
+      dom_agent_(dom_agent),
+      swallow_next_mouse_up_(false),
+      inspect_mode_(kNotSearching),
+      backend_node_id_to_inspect_(0) {}
+
+InspectorOverlayAgent::~InspectorOverlayAgent() {
+  DCHECK(!overlay_page_);
+}
+
+DEFINE_TRACE(InspectorOverlayAgent) {
+  visitor->Trace(frame_impl_);
+  visitor->Trace(inspected_frames_);
+  visitor->Trace(highlight_node_);
+  visitor->Trace(event_target_node_);
+  visitor->Trace(overlay_page_);
+  visitor->Trace(overlay_chrome_client_);
+  visitor->Trace(overlay_host_);
+  visitor->Trace(dom_agent_);
+  visitor->Trace(hovered_node_for_inspect_mode_);
+  InspectorBaseAgent::Trace(visitor);
+}
+
+void InspectorOverlayAgent::Restore() {
+  if (state_->booleanProperty(OverlayAgentState::kEnabled, false))
+    enabled_ = true;
+  setShowDebugBorders(
+      state_->booleanProperty(OverlayAgentState::kShowDebugBorders, false));
+  setShowFPSCounter(
+      state_->booleanProperty(OverlayAgentState::kShowFPSCounter, false));
+  setShowPaintRects(
+      state_->booleanProperty(OverlayAgentState::kShowPaintRects, false));
+  setShowScrollBottleneckRects(state_->booleanProperty(
+      OverlayAgentState::kShowScrollBottleneckRects, false));
+  setShowViewportSizeOnResize(
+      state_->booleanProperty(OverlayAgentState::kShowSizeOnResize, false));
+  String message;
+  state_->getString(OverlayAgentState::kPausedInDebuggerMessage, &message);
+  setPausedInDebuggerMessage(message);
+  setSuspended(state_->booleanProperty(OverlayAgentState::kSuspended, false));
+}
+
+void InspectorOverlayAgent::Dispose() {
+  show_reloading_blanket_ = false;
+  ClearInternal();
+  InspectorBaseAgent::Dispose();
+}
+
+Response InspectorOverlayAgent::enable() {
+  if (!dom_agent_->Enabled())
+    return Response::Error("DOM should be enabled first");
+  state_->setBoolean(OverlayAgentState::kEnabled, true);
+  enabled_ = true;
+  if (backend_node_id_to_inspect_)
+    GetFrontend()->inspectNodeRequested(backend_node_id_to_inspect_);
+  backend_node_id_to_inspect_ = 0;
+  return Response::OK();
+}
+
+Response InspectorOverlayAgent::disable() {
+  state_->setBoolean(OverlayAgentState::kEnabled, false);
+  enabled_ = false;
+  setShowDebugBorders(false);
+  setShowFPSCounter(false);
+  setShowPaintRects(false);
+  setShowScrollBottleneckRects(false);
+  setShowViewportSizeOnResize(false);
+  setPausedInDebuggerMessage(String());
+  setSuspended(false);
+  SetSearchingForNode(kNotSearching,
+                      Maybe<protocol::Overlay::HighlightConfig>());
+  return Response::OK();
+}
+
+Response InspectorOverlayAgent::setShowDebugBorders(bool show) {
+  state_->setBoolean(OverlayAgentState::kShowDebugBorders, show);
+  if (show) {
+    Response response = CompositingEnabled();
+    if (!response.isSuccess())
+      return response;
+  }
+  frame_impl_->ViewImpl()->SetShowDebugBorders(show);
+  return Response::OK();
+}
+
+Response InspectorOverlayAgent::setShowFPSCounter(bool show) {
+  state_->setBoolean(OverlayAgentState::kShowFPSCounter, show);
+  if (show) {
+    Response response = CompositingEnabled();
+    if (!response.isSuccess())
+      return response;
+  }
+  frame_impl_->ViewImpl()->SetShowFPSCounter(show);
+  return Response::OK();
+}
+
+Response InspectorOverlayAgent::setShowPaintRects(bool show) {
+  state_->setBoolean(OverlayAgentState::kShowPaintRects, show);
+  if (show) {
+    Response response = CompositingEnabled();
+    if (!response.isSuccess())
+      return response;
+  }
+  frame_impl_->ViewImpl()->SetShowPaintRects(show);
+  if (!show && frame_impl_->GetFrameView())
+    frame_impl_->GetFrameView()->Invalidate();
+  return Response::OK();
+}
+
+Response InspectorOverlayAgent::setShowScrollBottleneckRects(bool show) {
+  state_->setBoolean(OverlayAgentState::kShowScrollBottleneckRects, show);
+  if (show) {
+    Response response = CompositingEnabled();
+    if (!response.isSuccess())
+      return response;
+  }
+  frame_impl_->ViewImpl()->SetShowScrollBottleneckRects(show);
+  return Response::OK();
+}
+
+Response InspectorOverlayAgent::setShowViewportSizeOnResize(bool show) {
+  state_->setBoolean(OverlayAgentState::kShowSizeOnResize, show);
+  draw_view_size_ = show;
+  return Response::OK();
+}
+
+Response InspectorOverlayAgent::setPausedInDebuggerMessage(
+    Maybe<String> message) {
+  String just_message = message.fromMaybe(String());
+  state_->setString(OverlayAgentState::kPausedInDebuggerMessage, just_message);
+  paused_in_debugger_message_ = just_message;
+  ScheduleUpdate();
+  return Response::OK();
+}
+
+Response InspectorOverlayAgent::setSuspended(bool suspended) {
+  state_->setBoolean(OverlayAgentState::kSuspended, suspended);
+  if (suspended && !suspended_ && !show_reloading_blanket_)
+    ClearInternal();
+  suspended_ = suspended;
+  return Response::OK();
+}
+
+Response InspectorOverlayAgent::setInspectMode(
+    const String& mode,
+    Maybe<protocol::Overlay::HighlightConfig> highlight_config) {
+  SearchMode search_mode;
+  if (mode == protocol::Overlay::InspectModeEnum::SearchForNode) {
+    search_mode = kSearchingForNormal;
+  } else if (mode == protocol::Overlay::InspectModeEnum::SearchForUAShadowDOM) {
+    search_mode = kSearchingForUAShadow;
+  } else if (mode == protocol::Overlay::InspectModeEnum::None) {
+    search_mode = kNotSearching;
+  } else {
+    return Response::Error(
+        String("Unknown mode \"" + mode + "\" was provided."));
+  }
+
+  if (search_mode != kNotSearching) {
+    Response response = dom_agent_->PushDocumentUponHandlelessOperation();
+    if (!response.isSuccess())
+      return response;
+  }
+
+  return SetSearchingForNode(search_mode, std::move(highlight_config));
+}
+
+Response InspectorOverlayAgent::highlightRect(
+    int x,
+    int y,
+    int width,
+    int height,
+    Maybe<protocol::DOM::RGBA> color,
+    Maybe<protocol::DOM::RGBA> outline_color) {
+  std::unique_ptr<FloatQuad> quad =
+      WTF::WrapUnique(new FloatQuad(FloatRect(x, y, width, height)));
+  InnerHighlightQuad(std::move(quad), std::move(color),
+                     std::move(outline_color));
+  return Response::OK();
+}
+
+Response InspectorOverlayAgent::highlightQuad(
+    std::unique_ptr<protocol::Array<double>> quad_array,
+    Maybe<protocol::DOM::RGBA> color,
+    Maybe<protocol::DOM::RGBA> outline_color) {
+  std::unique_ptr<FloatQuad> quad = WTF::MakeUnique<FloatQuad>();
+  if (!ParseQuad(std::move(quad_array), quad.get()))
+    return Response::Error("Invalid Quad format");
+  InnerHighlightQuad(std::move(quad), std::move(color),
+                     std::move(outline_color));
+  return Response::OK();
+}
+
+Response InspectorOverlayAgent::highlightNode(
+    std::unique_ptr<protocol::Overlay::HighlightConfig>
+        highlight_inspector_object,
+    Maybe<int> node_id,
+    Maybe<int> backend_node_id,
+    Maybe<String> object_id) {
+  Node* node = nullptr;
+  Response response;
+  if (node_id.isJust()) {
+    response = dom_agent_->AssertNode(node_id.fromJust(), node);
+  } else if (backend_node_id.isJust()) {
+    node = DOMNodeIds::NodeForId(backend_node_id.fromJust());
+    response = !node ? Response::Error("No node found for given backend id")
+                     : Response::OK();
+  } else if (object_id.isJust()) {
+    response = dom_agent_->NodeForRemoteObjectId(object_id.fromJust(), node);
+  } else {
+    response = Response::Error(
+        "Either nodeId, backendNodeId or objectId must be specified");
+  }
+
+  if (!response.isSuccess())
+    return response;
+
+  std::unique_ptr<InspectorHighlightConfig> highlight_config;
+  response = HighlightConfigFromInspectorObject(
+      std::move(highlight_inspector_object), &highlight_config);
+  if (!response.isSuccess())
+    return response;
+
+  InnerHighlightNode(node, nullptr, *highlight_config, false);
+  return Response::OK();
+}
+
+Response InspectorOverlayAgent::highlightFrame(
+    const String& frame_id,
+    Maybe<protocol::DOM::RGBA> color,
+    Maybe<protocol::DOM::RGBA> outline_color) {
+  LocalFrame* frame =
+      IdentifiersFactory::FrameById(inspected_frames_, frame_id);
+  // FIXME: Inspector doesn't currently work cross process.
+  if (frame && frame->DeprecatedLocalOwner()) {
+    std::unique_ptr<InspectorHighlightConfig> highlight_config =
+        WTF::MakeUnique<InspectorHighlightConfig>();
+    highlight_config->show_info = true;  // Always show tooltips for frames.
+    highlight_config->content =
+        InspectorDOMAgent::ParseColor(color.fromMaybe(nullptr));
+    highlight_config->content_outline =
+        InspectorDOMAgent::ParseColor(outline_color.fromMaybe(nullptr));
+    InnerHighlightNode(frame->DeprecatedLocalOwner(), nullptr,
+                       *highlight_config, false);
+  }
+  return Response::OK();
+}
+
+Response InspectorOverlayAgent::hideHighlight() {
+  InnerHideHighlight();
+  return Response::OK();
+}
+
+Response InspectorOverlayAgent::getHighlightObjectForTest(
+    int node_id,
+    std::unique_ptr<protocol::DictionaryValue>* result) {
+  Node* node = nullptr;
+  Response response = dom_agent_->AssertNode(node_id, node);
+  if (!response.isSuccess())
+    return response;
+  InspectorHighlight highlight(node, InspectorHighlight::DefaultConfig(), true);
+  *result = highlight.AsProtocolValue();
+  return Response::OK();
+}
+
+void InspectorOverlayAgent::Invalidate() {
+  if (!page_overlay_) {
+    page_overlay_ = PageOverlay::Create(
+        frame_impl_, WTF::WrapUnique(new InspectorPageOverlayDelegate(*this)));
+  }
+
+  page_overlay_->Update();
+}
+
+void InspectorOverlayAgent::UpdateAllLifecyclePhases() {
+  if (IsEmpty())
+    return;
+
+  AutoReset<bool> scoped(&in_layout_, true);
+  if (needs_update_) {
+    needs_update_ = false;
+    RebuildOverlayPage();
+  }
+  OverlayMainFrame()->View()->UpdateAllLifecyclePhases();
+}
+
+bool InspectorOverlayAgent::HandleInputEvent(const WebInputEvent& input_event) {
+  bool handled = false;
+
+  if (IsEmpty())
+    return false;
+
+  if (input_event.GetType() == WebInputEvent::kGestureTap) {
+    // We only have a use for gesture tap.
+    WebGestureEvent transformed_event = TransformWebGestureEvent(
+        frame_impl_->GetFrameView(),
+        static_cast<const WebGestureEvent&>(input_event));
+    handled = HandleGestureEvent(transformed_event);
+    if (handled)
+      return true;
+
+    OverlayMainFrame()->GetEventHandler().HandleGestureEvent(transformed_event);
+  }
+  if (WebInputEvent::IsMouseEventType(input_event.GetType())) {
+    WebMouseEvent mouse_event =
+        TransformWebMouseEvent(frame_impl_->GetFrameView(),
+                               static_cast<const WebMouseEvent&>(input_event));
+
+    if (mouse_event.GetType() == WebInputEvent::kMouseMove)
+      handled = HandleMouseMove(mouse_event);
+    else if (mouse_event.GetType() == WebInputEvent::kMouseDown)
+      handled = HandleMouseDown();
+    else if (mouse_event.GetType() == WebInputEvent::kMouseUp)
+      handled = HandleMouseUp();
+
+    if (handled)
+      return true;
+
+    if (mouse_event.GetType() == WebInputEvent::kMouseMove) {
+      handled = OverlayMainFrame()->GetEventHandler().HandleMouseMoveEvent(
+                    mouse_event, TransformWebMouseEventVector(
+                                     frame_impl_->GetFrameView(),
+                                     std::vector<const WebInputEvent*>())) !=
+                WebInputEventResult::kNotHandled;
+    }
+    if (mouse_event.GetType() == WebInputEvent::kMouseDown) {
+      handled = OverlayMainFrame()->GetEventHandler().HandleMousePressEvent(
+                    mouse_event) != WebInputEventResult::kNotHandled;
+    }
+    if (mouse_event.GetType() == WebInputEvent::kMouseUp) {
+      handled = OverlayMainFrame()->GetEventHandler().HandleMouseReleaseEvent(
+                    mouse_event) != WebInputEventResult::kNotHandled;
+    }
+  }
+
+  if (WebInputEvent::IsTouchEventType(input_event.GetType())) {
+    WebTouchEvent transformed_event =
+        TransformWebTouchEvent(frame_impl_->GetFrameView(),
+                               static_cast<const WebTouchEvent&>(input_event));
+    handled = HandleTouchEvent(transformed_event);
+    if (handled)
+      return true;
+    OverlayMainFrame()->GetEventHandler().HandleTouchEvent(
+        transformed_event, Vector<WebTouchEvent>());
+  }
+  if (WebInputEvent::IsKeyboardEventType(input_event.GetType())) {
+    OverlayMainFrame()->GetEventHandler().KeyEvent(
+        static_cast<const WebKeyboardEvent&>(input_event));
+  }
+
+  if (input_event.GetType() == WebInputEvent::kMouseWheel) {
+    WebMouseWheelEvent transformed_event = TransformWebMouseWheelEvent(
+        frame_impl_->GetFrameView(),
+        static_cast<const WebMouseWheelEvent&>(input_event));
+    handled = OverlayMainFrame()->GetEventHandler().HandleWheelEvent(
+                  transformed_event) != WebInputEventResult::kNotHandled;
+  }
+
+  return handled;
+}
+
+void InspectorOverlayAgent::ShowReloadingBlanket() {
+  show_reloading_blanket_ = true;
+  ScheduleUpdate();
+}
+
+void InspectorOverlayAgent::HideReloadingBlanket() {
+  if (!show_reloading_blanket_)
+    return;
+  show_reloading_blanket_ = false;
+  if (suspended_)
+    ClearInternal();
+  else
+    ScheduleUpdate();
+}
+
+void InspectorOverlayAgent::InnerHideHighlight() {
+  highlight_node_.Clear();
+  event_target_node_.Clear();
+  highlight_quad_.reset();
+  ScheduleUpdate();
+}
+
+void InspectorOverlayAgent::InnerHighlightNode(
+    Node* node,
+    Node* event_target,
+    const InspectorHighlightConfig& highlight_config,
+    bool omit_tooltip) {
+  node_highlight_config_ = highlight_config;
+  highlight_node_ = node;
+  event_target_node_ = event_target;
+  omit_tooltip_ = omit_tooltip;
+  ScheduleUpdate();
+}
+
+void InspectorOverlayAgent::InnerHighlightQuad(
+    std::unique_ptr<FloatQuad> quad,
+    Maybe<protocol::DOM::RGBA> color,
+    Maybe<protocol::DOM::RGBA> outline_color) {
+  quad_content_color_ = InspectorDOMAgent::ParseColor(color.fromMaybe(nullptr));
+  quad_content_outline_color_ =
+      InspectorDOMAgent::ParseColor(outline_color.fromMaybe(nullptr));
+  highlight_quad_ = std::move(quad);
+  omit_tooltip_ = false;
+  ScheduleUpdate();
+}
+
+bool InspectorOverlayAgent::IsEmpty() {
+  if (show_reloading_blanket_)
+    return false;
+  if (suspended_)
+    return true;
+  bool has_visible_elements = highlight_node_ || event_target_node_ ||
+                              highlight_quad_ ||
+                              (resize_timer_active_ && draw_view_size_) ||
+                              !paused_in_debugger_message_.IsNull();
+  return !has_visible_elements && inspect_mode_ == kNotSearching;
+}
+
+void InspectorOverlayAgent::ScheduleUpdate() {
+  if (IsEmpty()) {
+    if (page_overlay_)
+      page_overlay_.reset();
+    return;
+  }
+  needs_update_ = true;
+  LocalFrame* frame = frame_impl_->GetFrame();
+  if (frame) {
+    frame->GetPage()->GetChromeClient().ScheduleAnimation(frame);
+  }
+}
+
+void InspectorOverlayAgent::RebuildOverlayPage() {
+  FrameView* view = frame_impl_->GetFrameView();
+  LocalFrame* frame = frame_impl_->GetFrame();
+  if (!view || !frame)
+    return;
+
+  IntRect visible_rect_in_document =
+      view->GetScrollableArea()->VisibleContentRect();
+  IntSize viewport_size = frame->GetPage()->GetVisualViewport().Size();
+  OverlayMainFrame()->View()->Resize(viewport_size);
+  OverlayPage()->GetVisualViewport().SetSize(viewport_size);
+  OverlayMainFrame()->SetPageZoomFactor(WindowToViewportScale());
+
+  Reset(viewport_size, visible_rect_in_document.Location());
+
+  if (show_reloading_blanket_) {
+    EvaluateInOverlay("showReloadingBlanket", "");
+    return;
+  }
+  DrawNodeHighlight();
+  DrawQuadHighlight();
+  DrawPausedInDebuggerMessage();
+  DrawViewSize();
+}
+
+static std::unique_ptr<protocol::DictionaryValue> BuildObjectForSize(
+    const IntSize& size) {
+  std::unique_ptr<protocol::DictionaryValue> result =
+      protocol::DictionaryValue::create();
+  result->setInteger("width", size.Width());
+  result->setInteger("height", size.Height());
+  return result;
+}
+
+void InspectorOverlayAgent::DrawNodeHighlight() {
+  if (!highlight_node_)
+    return;
+
+  String selectors = node_highlight_config_.selector_list;
+  StaticElementList* elements = nullptr;
+  DummyExceptionStateForTesting exception_state;
+  ContainerNode* query_base = highlight_node_->ContainingShadowRoot();
+  if (!query_base)
+    query_base = highlight_node_->ownerDocument();
+  if (selectors.length()) {
+    elements =
+        query_base->QuerySelectorAll(AtomicString(selectors), exception_state);
+  }
+  if (elements && !exception_state.HadException()) {
+    for (unsigned i = 0; i < elements->length(); ++i) {
+      Element* element = elements->item(i);
+      InspectorHighlight highlight(element, node_highlight_config_, false);
+      std::unique_ptr<protocol::DictionaryValue> highlight_json =
+          highlight.AsProtocolValue();
+      EvaluateInOverlay("drawHighlight", std::move(highlight_json));
+    }
+  }
+
+  bool append_element_info =
+      highlight_node_->IsElementNode() && !omit_tooltip_ &&
+      node_highlight_config_.show_info && highlight_node_->GetLayoutObject() &&
+      highlight_node_->GetDocument().GetFrame();
+  InspectorHighlight highlight(highlight_node_.Get(), node_highlight_config_,
+                               append_element_info);
+  if (event_target_node_) {
+    highlight.AppendEventTargetQuads(event_target_node_.Get(),
+                                     node_highlight_config_);
+  }
+
+  std::unique_ptr<protocol::DictionaryValue> highlight_json =
+      highlight.AsProtocolValue();
+  EvaluateInOverlay("drawHighlight", std::move(highlight_json));
+}
+
+void InspectorOverlayAgent::DrawQuadHighlight() {
+  if (!highlight_quad_)
+    return;
+
+  InspectorHighlight highlight(WindowToViewportScale());
+  highlight.AppendQuad(*highlight_quad_, quad_content_color_,
+                       quad_content_outline_color_);
+  EvaluateInOverlay("drawHighlight", highlight.AsProtocolValue());
+}
+
+void InspectorOverlayAgent::DrawPausedInDebuggerMessage() {
+  if (inspect_mode_ == kNotSearching && !paused_in_debugger_message_.IsNull()) {
+    EvaluateInOverlay("drawPausedInDebuggerMessage",
+                      paused_in_debugger_message_);
+  }
+}
+
+void InspectorOverlayAgent::DrawViewSize() {
+  if (resize_timer_active_ && draw_view_size_)
+    EvaluateInOverlay("drawViewSize", "");
+}
+
+float InspectorOverlayAgent::WindowToViewportScale() const {
+  LocalFrame* frame = frame_impl_->GetFrame();
+  if (!frame)
+    return 1.0f;
+  return frame->GetPage()->GetChromeClient().WindowToViewportScalar(1.0f);
+}
+
+Page* InspectorOverlayAgent::OverlayPage() {
+  if (overlay_page_)
+    return overlay_page_.Get();
+
+  ScriptForbiddenScope::AllowUserAgentScript allow_script;
+
+  DEFINE_STATIC_LOCAL(LocalFrameClient, dummy_local_frame_client,
+                      (EmptyLocalFrameClient::Create()));
+  Page::PageClients page_clients;
+  FillWithEmptyClients(page_clients);
+  DCHECK(!overlay_chrome_client_);
+  overlay_chrome_client_ = InspectorOverlayChromeClient::Create(
+      frame_impl_->GetFrame()->GetPage()->GetChromeClient(), *this);
+  page_clients.chrome_client = overlay_chrome_client_.Get();
+  overlay_page_ = Page::Create(page_clients);
+
+  Settings& settings = frame_impl_->GetFrame()->GetPage()->GetSettings();
+  Settings& overlay_settings = overlay_page_->GetSettings();
+
+  overlay_settings.GetGenericFontFamilySettings().UpdateStandard(
+      settings.GetGenericFontFamilySettings().Standard());
+  overlay_settings.GetGenericFontFamilySettings().UpdateSerif(
+      settings.GetGenericFontFamilySettings().Serif());
+  overlay_settings.GetGenericFontFamilySettings().UpdateSansSerif(
+      settings.GetGenericFontFamilySettings().SansSerif());
+  overlay_settings.GetGenericFontFamilySettings().UpdateCursive(
+      settings.GetGenericFontFamilySettings().Cursive());
+  overlay_settings.GetGenericFontFamilySettings().UpdateFantasy(
+      settings.GetGenericFontFamilySettings().Fantasy());
+  overlay_settings.GetGenericFontFamilySettings().UpdatePictograph(
+      settings.GetGenericFontFamilySettings().Pictograph());
+  overlay_settings.SetMinimumFontSize(settings.GetMinimumFontSize());
+  overlay_settings.SetMinimumLogicalFontSize(
+      settings.GetMinimumLogicalFontSize());
+  overlay_settings.SetScriptEnabled(true);
+  overlay_settings.SetPluginsEnabled(false);
+  overlay_settings.SetLoadsImagesAutomatically(true);
+  // FIXME: http://crbug.com/363843. Inspector should probably create its
+  // own graphics layers and attach them to the tree rather than going
+  // through some non-composited paint function.
+  overlay_settings.SetAcceleratedCompositingEnabled(false);
+
+  LocalFrame* frame =
+      LocalFrame::Create(&dummy_local_frame_client, *overlay_page_, 0);
+  frame->SetView(FrameView::Create(*frame));
+  frame->Init();
+  FrameLoader& loader = frame->Loader();
+  frame->View()->SetCanHaveScrollbars(false);
+  frame->View()->SetBaseBackgroundColor(Color::kTransparent);
+
+  const WebData& overlay_page_html_resource =
+      Platform::Current()->LoadResource("InspectorOverlayPage.html");
+  loader.Load(
+      FrameLoadRequest(0, ResourceRequest(BlankURL()),
+                       SubstituteData(overlay_page_html_resource, "text/html",
+                                      "UTF-8", KURL(), kForceSynchronousLoad)));
+  v8::Isolate* isolate = ToIsolate(frame);
+  ScriptState* script_state = ToScriptStateForMainWorld(frame);
+  DCHECK(script_state);
+  ScriptState::Scope scope(script_state);
+  v8::Local<v8::Object> global = script_state->GetContext()->Global();
+  v8::Local<v8::Value> overlay_host_obj =
+      ToV8(overlay_host_.Get(), global, isolate);
+  DCHECK(!overlay_host_obj.IsEmpty());
+  global
+      ->Set(script_state->GetContext(),
+            V8AtomicString(isolate, "InspectorOverlayHost"), overlay_host_obj)
+      .ToChecked();
+
+#if OS(WIN)
+  EvaluateInOverlay("setPlatform", "windows");
+#elif OS(MACOSX)
+  EvaluateInOverlay("setPlatform", "mac");
+#elif OS(POSIX)
+  EvaluateInOverlay("setPlatform", "linux");
+#endif
+
+  return overlay_page_.Get();
+}
+
+LocalFrame* InspectorOverlayAgent::OverlayMainFrame() {
+  return ToLocalFrame(OverlayPage()->MainFrame());
+}
+
+void InspectorOverlayAgent::Reset(const IntSize& viewport_size,
+                                  const IntPoint& document_scroll_offset) {
+  std::unique_ptr<protocol::DictionaryValue> reset_data =
+      protocol::DictionaryValue::create();
+  reset_data->setDouble(
+      "deviceScaleFactor",
+      frame_impl_->GetFrame()->GetPage()->DeviceScaleFactorDeprecated());
+  reset_data->setDouble(
+      "pageScaleFactor",
+      frame_impl_->GetFrame()->GetPage()->GetVisualViewport().Scale());
+
+  IntRect viewport_in_screen =
+      frame_impl_->GetFrame()->GetPage()->GetChromeClient().ViewportToScreen(
+          IntRect(IntPoint(), viewport_size), frame_impl_->GetFrame()->View());
+  reset_data->setObject("viewportSize",
+                        BuildObjectForSize(viewport_in_screen.Size()));
+
+  // The zoom factor in the overlay frame already has been multiplied by the
+  // window to viewport scale (aka device scale factor), so cancel it.
+  reset_data->setDouble(
+      "pageZoomFactor",
+      frame_impl_->GetFrame()->PageZoomFactor() / WindowToViewportScale());
+
+  reset_data->setInteger("scrollX", document_scroll_offset.X());
+  reset_data->setInteger("scrollY", document_scroll_offset.Y());
+  EvaluateInOverlay("reset", std::move(reset_data));
+}
+
+void InspectorOverlayAgent::EvaluateInOverlay(const String& method,
+                                              const String& argument) {
+  ScriptForbiddenScope::AllowUserAgentScript allow_script;
+  std::unique_ptr<protocol::ListValue> command = protocol::ListValue::create();
+  command->pushValue(protocol::StringValue::create(method));
+  command->pushValue(protocol::StringValue::create(argument));
+  ToLocalFrame(OverlayPage()->MainFrame())
+      ->GetScriptController()
+      .ExecuteScriptInMainWorld(
+          "dispatch(" + command->serialize() + ")",
+          ScriptController::kExecuteScriptWhenScriptsDisabled);
+}
+
+void InspectorOverlayAgent::EvaluateInOverlay(
+    const String& method,
+    std::unique_ptr<protocol::Value> argument) {
+  ScriptForbiddenScope::AllowUserAgentScript allow_script;
+  std::unique_ptr<protocol::ListValue> command = protocol::ListValue::create();
+  command->pushValue(protocol::StringValue::create(method));
+  command->pushValue(std::move(argument));
+  ToLocalFrame(OverlayPage()->MainFrame())
+      ->GetScriptController()
+      .ExecuteScriptInMainWorld(
+          "dispatch(" + command->serialize() + ")",
+          ScriptController::kExecuteScriptWhenScriptsDisabled);
+}
+
+String InspectorOverlayAgent::EvaluateInOverlayForTest(const String& script) {
+  ScriptForbiddenScope::AllowUserAgentScript allow_script;
+  v8::HandleScope handle_scope(ToIsolate(OverlayMainFrame()));
+  v8::Local<v8::Value> string =
+      ToLocalFrame(OverlayPage()->MainFrame())
+          ->GetScriptController()
+          .ExecuteScriptInMainWorldAndReturnValue(
+              ScriptSourceCode(script),
+              ScriptController::kExecuteScriptWhenScriptsDisabled);
+  return ToCoreStringWithUndefinedOrNullCheck(string);
+}
+
+void InspectorOverlayAgent::OnTimer(TimerBase*) {
+  resize_timer_active_ = false;
+  ScheduleUpdate();
+}
+
+void InspectorOverlayAgent::ClearInternal() {
+  if (overlay_page_) {
+    overlay_page_->WillBeDestroyed();
+    overlay_page_.Clear();
+    overlay_chrome_client_.Clear();
+  }
+  resize_timer_active_ = false;
+  paused_in_debugger_message_ = String();
+  inspect_mode_ = kNotSearching;
+  timer_.Stop();
+  InnerHideHighlight();
+}
+
+void InspectorOverlayAgent::OverlayResumed() {
+  if (v8_session_)
+    v8_session_->resume();
+}
+
+void InspectorOverlayAgent::OverlaySteppedOver() {
+  if (v8_session_)
+    v8_session_->stepOver();
+}
+
+void InspectorOverlayAgent::PageLayoutInvalidated(bool resized) {
+  if (resized && draw_view_size_) {
+    resize_timer_active_ = true;
+    timer_.StartOneShot(1, BLINK_FROM_HERE);
+  }
+  ScheduleUpdate();
+}
+
+bool InspectorOverlayAgent::HandleMouseMove(const WebMouseEvent& event) {
+  if (!ShouldSearchForNode())
+    return false;
+
+  LocalFrame* frame = frame_impl_->GetFrame();
+  if (!frame || !frame->View() || frame->ContentLayoutItem().IsNull())
+    return false;
+  Node* node = HoveredNodeForEvent(
+      frame, event, event.GetModifiers() & WebInputEvent::kShiftKey);
+
+  // Do not highlight within user agent shadow root unless requested.
+  if (inspect_mode_ != kSearchingForUAShadow) {
+    ShadowRoot* shadow_root = InspectorDOMAgent::UserAgentShadowRoot(node);
+    if (shadow_root)
+      node = &shadow_root->host();
+  }
+
+  // Shadow roots don't have boxes - use host element instead.
+  if (node && node->IsShadowRoot())
+    node = node->ParentOrShadowHostNode();
+
+  if (!node)
+    return true;
+
+  if (node->IsFrameOwnerElement()) {
+    HTMLFrameOwnerElement* frame_owner = ToHTMLFrameOwnerElement(node);
+    if (frame_owner->ContentFrame() &&
+        !frame_owner->ContentFrame()->IsLocalFrame()) {
+      // Do not consume event so that remote frame can handle it.
+      InnerHideHighlight();
+      hovered_node_for_inspect_mode_.Clear();
+      return false;
+    }
+  }
+
+  Node* event_target = (event.GetModifiers() & WebInputEvent::kShiftKey)
+                           ? HoveredNodeForEvent(frame, event, false)
+                           : nullptr;
+  if (event_target == node)
+    event_target = nullptr;
+
+  if (node && inspect_mode_highlight_config_) {
+    hovered_node_for_inspect_mode_ = node;
+    NodeHighlightRequested(node);
+    bool omit_tooltip = event.GetModifiers() &
+                        (WebInputEvent::kControlKey | WebInputEvent::kMetaKey);
+    InnerHighlightNode(node, event_target, *inspect_mode_highlight_config_,
+                       omit_tooltip);
+  }
+  return true;
+}
+
+bool InspectorOverlayAgent::HandleMouseDown() {
+  swallow_next_mouse_up_ = false;
+  if (!ShouldSearchForNode())
+    return false;
+
+  if (hovered_node_for_inspect_mode_) {
+    swallow_next_mouse_up_ = true;
+    Inspect(hovered_node_for_inspect_mode_.Get());
+    hovered_node_for_inspect_mode_.Clear();
+    return true;
+  }
+  return false;
+}
+
+bool InspectorOverlayAgent::HandleMouseUp() {
+  if (swallow_next_mouse_up_) {
+    swallow_next_mouse_up_ = false;
+    return true;
+  }
+  return false;
+}
+
+bool InspectorOverlayAgent::HandleGestureEvent(const WebGestureEvent& event) {
+  if (!ShouldSearchForNode() || event.GetType() != WebInputEvent::kGestureTap)
+    return false;
+  Node* node = HoveredNodeForEvent(frame_impl_->GetFrame(), event, false);
+  if (node && inspect_mode_highlight_config_) {
+    InnerHighlightNode(node, nullptr, *inspect_mode_highlight_config_, false);
+    Inspect(node);
+    return true;
+  }
+  return false;
+}
+
+bool InspectorOverlayAgent::HandleTouchEvent(const WebTouchEvent& event) {
+  if (!ShouldSearchForNode())
+    return false;
+  Node* node = HoveredNodeForEvent(frame_impl_->GetFrame(), event, false);
+  if (node && inspect_mode_highlight_config_) {
+    InnerHighlightNode(node, nullptr, *inspect_mode_highlight_config_, false);
+    Inspect(node);
+    return true;
+  }
+  return false;
+}
+
+Response InspectorOverlayAgent::CompositingEnabled() {
+  bool main_frame = frame_impl_->ViewImpl() && !frame_impl_->Parent();
+  if (!main_frame || !frame_impl_->ViewImpl()
+                          ->GetPage()
+                          ->GetSettings()
+                          .GetAcceleratedCompositingEnabled())
+    return Response::Error("Compositing mode is not supported");
+  return Response::OK();
+}
+
+bool InspectorOverlayAgent::ShouldSearchForNode() {
+  return inspect_mode_ != kNotSearching;
+}
+
+void InspectorOverlayAgent::Inspect(Node* inspected_node) {
+  if (!inspected_node)
+    return;
+
+  Node* node = inspected_node;
+  while (node && !node->IsElementNode() && !node->IsDocumentNode() &&
+         !node->IsDocumentFragment())
+    node = node->ParentOrShadowHostNode();
+  if (!node)
+    return;
+
+  int backend_node_id = DOMNodeIds::IdForNode(node);
+  if (!enabled_) {
+    backend_node_id_to_inspect_ = backend_node_id;
+    return;
+  }
+
+  GetFrontend()->inspectNodeRequested(backend_node_id);
+}
+
+void InspectorOverlayAgent::NodeHighlightRequested(Node* node) {
+  if (!enabled_)
+    return;
+
+  while (node && !node->IsElementNode() && !node->IsDocumentNode() &&
+         !node->IsDocumentFragment())
+    node = node->ParentOrShadowHostNode();
+
+  if (!node)
+    return;
+
+  int node_id = dom_agent_->PushNodePathToFrontend(node);
+  GetFrontend()->nodeHighlightRequested(node_id);
+}
+
+Response InspectorOverlayAgent::SetSearchingForNode(
+    SearchMode search_mode,
+    Maybe<protocol::Overlay::HighlightConfig> highlight_inspector_object) {
+  if (search_mode == kNotSearching) {
+    inspect_mode_ = search_mode;
+    ScheduleUpdate();
+    hovered_node_for_inspect_mode_.Clear();
+    InnerHideHighlight();
+    return Response::OK();
+  }
+
+  std::unique_ptr<InspectorHighlightConfig> config;
+  Response response = HighlightConfigFromInspectorObject(
+      std::move(highlight_inspector_object), &config);
+  if (!response.isSuccess())
+    return response;
+  inspect_mode_ = search_mode;
+  inspect_mode_highlight_config_ = std::move(config);
+  ScheduleUpdate();
+  return Response::OK();
+}
+
+Response InspectorOverlayAgent::HighlightConfigFromInspectorObject(
+    Maybe<protocol::Overlay::HighlightConfig> highlight_inspector_object,
+    std::unique_ptr<InspectorHighlightConfig>* out_config) {
+  if (!highlight_inspector_object.isJust()) {
+    return Response::Error(
+        "Internal error: highlight configuration parameter is missing");
+  }
+
+  protocol::Overlay::HighlightConfig* config =
+      highlight_inspector_object.fromJust();
+  std::unique_ptr<InspectorHighlightConfig> highlight_config =
+      WTF::MakeUnique<InspectorHighlightConfig>();
+  highlight_config->show_info = config->getShowInfo(false);
+  highlight_config->show_rulers = config->getShowRulers(false);
+  highlight_config->show_extension_lines = config->getShowExtensionLines(false);
+  highlight_config->display_as_material = config->getDisplayAsMaterial(false);
+  highlight_config->content =
+      InspectorDOMAgent::ParseColor(config->getContentColor(nullptr));
+  highlight_config->padding =
+      InspectorDOMAgent::ParseColor(config->getPaddingColor(nullptr));
+  highlight_config->border =
+      InspectorDOMAgent::ParseColor(config->getBorderColor(nullptr));
+  highlight_config->margin =
+      InspectorDOMAgent::ParseColor(config->getMarginColor(nullptr));
+  highlight_config->event_target =
+      InspectorDOMAgent::ParseColor(config->getEventTargetColor(nullptr));
+  highlight_config->shape =
+      InspectorDOMAgent::ParseColor(config->getShapeColor(nullptr));
+  highlight_config->shape_margin =
+      InspectorDOMAgent::ParseColor(config->getShapeMarginColor(nullptr));
+  highlight_config->selector_list = config->getSelectorList("");
+
+  *out_config = std::move(highlight_config);
+  return Response::OK();
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/web/InspectorOverlayAgent.h b/third_party/WebKit/Source/web/InspectorOverlayAgent.h
new file mode 100644
index 0000000..b61ccd5
--- /dev/null
+++ b/third_party/WebKit/Source/web/InspectorOverlayAgent.h
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2011 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ *     its contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef InspectorOverlayAgent_h
+#define InspectorOverlayAgent_h
+
+#include <v8-inspector.h>
+#include <memory>
+#include "core/inspector/InspectorBaseAgent.h"
+#include "core/inspector/InspectorHighlight.h"
+#include "core/inspector/InspectorOverlayHost.h"
+#include "core/inspector/protocol/Overlay.h"
+#include "platform/Timer.h"
+#include "platform/geometry/FloatQuad.h"
+#include "platform/geometry/LayoutRect.h"
+#include "platform/graphics/Color.h"
+#include "platform/heap/Handle.h"
+#include "platform/wtf/RefPtr.h"
+#include "platform/wtf/text/WTFString.h"
+#include "public/platform/WebInputEvent.h"
+
+namespace blink {
+
+class Color;
+class InspectedFrames;
+class InspectorDOMAgent;
+class LocalFrame;
+class Node;
+class Page;
+class PageOverlay;
+class WebGestureEvent;
+class WebMouseEvent;
+class WebLocalFrameImpl;
+class WebTouchEvent;
+
+class InspectorOverlayAgent final
+    : public InspectorBaseAgent<protocol::Overlay::Metainfo>,
+      public InspectorOverlayHost::Listener {
+  WTF_MAKE_NONCOPYABLE(InspectorOverlayAgent);
+  USING_GARBAGE_COLLECTED_MIXIN(InspectorOverlayAgent);
+
+ public:
+  InspectorOverlayAgent(WebLocalFrameImpl*,
+                        InspectedFrames*,
+                        v8_inspector::V8InspectorSession*,
+                        InspectorDOMAgent*);
+  ~InspectorOverlayAgent() override;
+  DECLARE_TRACE();
+
+  // protocol::Dispatcher::OverlayCommandHandler implementation.
+  protocol::Response enable() override;
+  protocol::Response disable() override;
+  protocol::Response setShowPaintRects(bool) override;
+  protocol::Response setShowDebugBorders(bool) override;
+  protocol::Response setShowFPSCounter(bool) override;
+  protocol::Response setShowScrollBottleneckRects(bool) override;
+  protocol::Response setShowViewportSizeOnResize(bool) override;
+  protocol::Response setPausedInDebuggerMessage(
+      protocol::Maybe<String>) override;
+  protocol::Response setSuspended(bool) override;
+  protocol::Response setInspectMode(
+      const String& mode,
+      protocol::Maybe<protocol::Overlay::HighlightConfig>) override;
+  protocol::Response highlightRect(
+      int x,
+      int y,
+      int width,
+      int height,
+      protocol::Maybe<protocol::DOM::RGBA> color,
+      protocol::Maybe<protocol::DOM::RGBA> outline_color) override;
+  protocol::Response highlightQuad(
+      std::unique_ptr<protocol::Array<double>> quad,
+      protocol::Maybe<protocol::DOM::RGBA> color,
+      protocol::Maybe<protocol::DOM::RGBA> outline_color) override;
+  protocol::Response highlightNode(
+      std::unique_ptr<protocol::Overlay::HighlightConfig>,
+      protocol::Maybe<int> node_id,
+      protocol::Maybe<int> backend_node_id,
+      protocol::Maybe<String> object_id) override;
+  protocol::Response hideHighlight() override;
+  protocol::Response highlightFrame(
+      const String& frame_id,
+      protocol::Maybe<protocol::DOM::RGBA> content_color,
+      protocol::Maybe<protocol::DOM::RGBA> content_outline_color) override;
+  protocol::Response getHighlightObjectForTest(
+      int node_id,
+      std::unique_ptr<protocol::DictionaryValue>* highlight) override;
+
+  // InspectorBaseAgent overrides.
+  void Restore() override;
+  void Dispose() override;
+
+  void Inspect(Node*);
+  bool HandleInputEvent(const WebInputEvent&);
+  void PageLayoutInvalidated(bool resized);
+  void ShowReloadingBlanket();
+  void HideReloadingBlanket();
+  // Does not yet include paint.
+  void UpdateAllLifecyclePhases();
+  PageOverlay* GetPageOverlay() { return page_overlay_.get(); };
+  String EvaluateInOverlayForTest(const String&);
+
+ private:
+  class InspectorOverlayChromeClient;
+  class InspectorPageOverlayDelegate;
+
+  enum SearchMode {
+    kNotSearching,
+    kSearchingForNormal,
+    kSearchingForUAShadow,
+  };
+
+  // InspectorOverlayHost::Listener implementation.
+  void OverlayResumed() override;
+  void OverlaySteppedOver() override;
+
+  bool IsEmpty();
+  void DrawNodeHighlight();
+  void DrawQuadHighlight();
+  void DrawPausedInDebuggerMessage();
+  void DrawViewSize();
+
+  float WindowToViewportScale() const;
+
+  Page* OverlayPage();
+  LocalFrame* OverlayMainFrame();
+  void Reset(const IntSize& viewport_size,
+             const IntPoint& document_scroll_offset);
+  void EvaluateInOverlay(const String& method, const String& argument);
+  void EvaluateInOverlay(const String& method,
+                         std::unique_ptr<protocol::Value> argument);
+  void OnTimer(TimerBase*);
+  void RebuildOverlayPage();
+  void Invalidate();
+  void ScheduleUpdate();
+  void ClearInternal();
+
+  bool HandleMouseDown();
+  bool HandleMouseUp();
+  bool HandleGestureEvent(const WebGestureEvent&);
+  bool HandleTouchEvent(const WebTouchEvent&);
+  bool HandleMouseMove(const WebMouseEvent&);
+
+  protocol::Response CompositingEnabled();
+
+  bool ShouldSearchForNode();
+  void NodeHighlightRequested(Node*);
+  protocol::Response SetSearchingForNode(
+      SearchMode,
+      protocol::Maybe<protocol::Overlay::HighlightConfig>);
+  protocol::Response HighlightConfigFromInspectorObject(
+      protocol::Maybe<protocol::Overlay::HighlightConfig>
+          highlight_inspector_object,
+      std::unique_ptr<InspectorHighlightConfig>*);
+  void InnerHighlightQuad(std::unique_ptr<FloatQuad>,
+                          protocol::Maybe<protocol::DOM::RGBA> color,
+                          protocol::Maybe<protocol::DOM::RGBA> outline_color);
+  void InnerHighlightNode(Node*,
+                          Node* event_target,
+                          const InspectorHighlightConfig&,
+                          bool omit_tooltip);
+  void InnerHideHighlight();
+
+  Member<WebLocalFrameImpl> frame_impl_;
+  Member<InspectedFrames> inspected_frames_;
+  bool enabled_;
+  String paused_in_debugger_message_;
+  Member<Node> highlight_node_;
+  Member<Node> event_target_node_;
+  InspectorHighlightConfig node_highlight_config_;
+  std::unique_ptr<FloatQuad> highlight_quad_;
+  Member<Page> overlay_page_;
+  Member<InspectorOverlayChromeClient> overlay_chrome_client_;
+  Member<InspectorOverlayHost> overlay_host_;
+  Color quad_content_color_;
+  Color quad_content_outline_color_;
+  bool draw_view_size_;
+  bool resize_timer_active_;
+  bool omit_tooltip_;
+  TaskRunnerTimer<InspectorOverlayAgent> timer_;
+  bool suspended_;
+  bool show_reloading_blanket_;
+  bool in_layout_;
+  bool needs_update_;
+  v8_inspector::V8InspectorSession* v8_session_;
+  Member<InspectorDOMAgent> dom_agent_;
+  std::unique_ptr<PageOverlay> page_overlay_;
+  Member<Node> hovered_node_for_inspect_mode_;
+  bool swallow_next_mouse_up_;
+  SearchMode inspect_mode_;
+  std::unique_ptr<InspectorHighlightConfig> inspect_mode_highlight_config_;
+  int backend_node_id_to_inspect_;
+};
+
+}  // namespace blink
+
+#endif  // InspectorOverlayAgent_h
diff --git a/third_party/WebKit/Source/web/InspectorRenderingAgent.cpp b/third_party/WebKit/Source/web/InspectorRenderingAgent.cpp
deleted file mode 100644
index 75ef216..0000000
--- a/third_party/WebKit/Source/web/InspectorRenderingAgent.cpp
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "web/InspectorRenderingAgent.h"
-
-#include "core/frame/FrameView.h"
-#include "core/frame/Settings.h"
-#include "core/page/Page.h"
-#include "web/InspectorOverlay.h"
-#include "web/WebLocalFrameImpl.h"
-#include "web/WebViewImpl.h"
-
-namespace blink {
-
-using protocol::Response;
-
-namespace RenderingAgentState {
-static const char kShowDebugBorders[] = "showDebugBorders";
-static const char kShowFPSCounter[] = "showFPSCounter";
-static const char kShowPaintRects[] = "showPaintRects";
-static const char kShowScrollBottleneckRects[] = "showScrollBottleneckRects";
-static const char kShowSizeOnResize[] = "showSizeOnResize";
-}
-
-InspectorRenderingAgent* InspectorRenderingAgent::Create(
-    WebLocalFrameImpl* web_local_frame_impl,
-    InspectorOverlay* overlay) {
-  return new InspectorRenderingAgent(web_local_frame_impl, overlay);
-}
-
-InspectorRenderingAgent::InspectorRenderingAgent(
-    WebLocalFrameImpl* web_local_frame_impl,
-    InspectorOverlay* overlay)
-    : web_local_frame_impl_(web_local_frame_impl), overlay_(overlay) {}
-
-WebViewImpl* InspectorRenderingAgent::GetWebViewImpl() {
-  return web_local_frame_impl_->ViewImpl();
-}
-
-void InspectorRenderingAgent::Restore() {
-  setShowDebugBorders(
-      state_->booleanProperty(RenderingAgentState::kShowDebugBorders, false));
-  setShowFPSCounter(
-      state_->booleanProperty(RenderingAgentState::kShowFPSCounter, false));
-  setShowPaintRects(
-      state_->booleanProperty(RenderingAgentState::kShowPaintRects, false));
-  setShowScrollBottleneckRects(state_->booleanProperty(
-      RenderingAgentState::kShowScrollBottleneckRects, false));
-  setShowViewportSizeOnResize(
-      state_->booleanProperty(RenderingAgentState::kShowSizeOnResize, false));
-}
-
-Response InspectorRenderingAgent::disable() {
-  setShowDebugBorders(false);
-  setShowFPSCounter(false);
-  setShowPaintRects(false);
-  setShowScrollBottleneckRects(false);
-  setShowViewportSizeOnResize(false);
-  return Response::OK();
-}
-
-Response InspectorRenderingAgent::setShowDebugBorders(bool show) {
-  state_->setBoolean(RenderingAgentState::kShowDebugBorders, show);
-  if (show) {
-    Response response = CompositingEnabled();
-    if (!response.isSuccess())
-      return response;
-  }
-  GetWebViewImpl()->SetShowDebugBorders(show);
-  return Response::OK();
-}
-
-Response InspectorRenderingAgent::setShowFPSCounter(bool show) {
-  state_->setBoolean(RenderingAgentState::kShowFPSCounter, show);
-  if (show) {
-    Response response = CompositingEnabled();
-    if (!response.isSuccess())
-      return response;
-  }
-  GetWebViewImpl()->SetShowFPSCounter(show);
-  return Response::OK();
-}
-
-Response InspectorRenderingAgent::setShowPaintRects(bool show) {
-  state_->setBoolean(RenderingAgentState::kShowPaintRects, show);
-  GetWebViewImpl()->SetShowPaintRects(show);
-  if (!show && web_local_frame_impl_->GetFrameView())
-    web_local_frame_impl_->GetFrameView()->Invalidate();
-  return Response::OK();
-}
-
-Response InspectorRenderingAgent::setShowScrollBottleneckRects(bool show) {
-  state_->setBoolean(RenderingAgentState::kShowScrollBottleneckRects, show);
-  if (show) {
-    Response response = CompositingEnabled();
-    if (!response.isSuccess())
-      return response;
-  }
-  GetWebViewImpl()->SetShowScrollBottleneckRects(show);
-  return Response::OK();
-}
-
-Response InspectorRenderingAgent::setShowViewportSizeOnResize(bool show) {
-  state_->setBoolean(RenderingAgentState::kShowSizeOnResize, show);
-  if (overlay_)
-    overlay_->SetShowViewportSizeOnResize(show);
-  return Response::OK();
-}
-
-Response InspectorRenderingAgent::CompositingEnabled() {
-  if (!GetWebViewImpl()
-           ->GetPage()
-           ->GetSettings()
-           .GetAcceleratedCompositingEnabled())
-    return Response::Error("Compositing mode is not supported");
-  return Response::OK();
-}
-
-DEFINE_TRACE(InspectorRenderingAgent) {
-  visitor->Trace(web_local_frame_impl_);
-  visitor->Trace(overlay_);
-  InspectorBaseAgent::Trace(visitor);
-}
-
-}  // namespace blink
diff --git a/third_party/WebKit/Source/web/InspectorRenderingAgent.h b/third_party/WebKit/Source/web/InspectorRenderingAgent.h
deleted file mode 100644
index a22ba79..0000000
--- a/third_party/WebKit/Source/web/InspectorRenderingAgent.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef InspectorRenderingAgent_h
-#define InspectorRenderingAgent_h
-
-#include "core/inspector/InspectorBaseAgent.h"
-#include "core/inspector/protocol/Rendering.h"
-
-namespace blink {
-
-class InspectorOverlay;
-class WebLocalFrameImpl;
-class WebViewImpl;
-
-class InspectorRenderingAgent final
-    : public InspectorBaseAgent<protocol::Rendering::Metainfo> {
-  WTF_MAKE_NONCOPYABLE(InspectorRenderingAgent);
-
- public:
-  static InspectorRenderingAgent* Create(WebLocalFrameImpl*, InspectorOverlay*);
-
-  // protocol::Dispatcher::PageCommandHandler implementation.
-  protocol::Response setShowPaintRects(bool) override;
-  protocol::Response setShowDebugBorders(bool) override;
-  protocol::Response setShowFPSCounter(bool) override;
-  protocol::Response setShowScrollBottleneckRects(bool) override;
-  protocol::Response setShowViewportSizeOnResize(bool) override;
-
-  // InspectorBaseAgent overrides.
-  protocol::Response disable() override;
-  void Restore() override;
-
-  DECLARE_VIRTUAL_TRACE();
-
- private:
-  InspectorRenderingAgent(WebLocalFrameImpl*, InspectorOverlay*);
-  protocol::Response CompositingEnabled();
-  WebViewImpl* GetWebViewImpl();
-
-  Member<WebLocalFrameImpl> web_local_frame_impl_;
-  Member<InspectorOverlay> overlay_;
-};
-
-}  // namespace blink
-
-#endif  // !defined(InspectorRenderingAgent_h)
diff --git a/third_party/WebKit/Source/web/LocalFrameClientImpl.cpp b/third_party/WebKit/Source/web/LocalFrameClientImpl.cpp
index 7112694b7..13cda10 100644
--- a/third_party/WebKit/Source/web/LocalFrameClientImpl.cpp
+++ b/third_party/WebKit/Source/web/LocalFrameClientImpl.cpp
@@ -231,6 +231,15 @@
     web_frame_->Client()->DidUpdateCurrentHistoryItem();
 }
 
+bool LocalFrameClientImpl::AllowContentInitiatedDataUrlNavigations(
+    const KURL& url) {
+  if (RuntimeEnabledFeatures::allowContentInitiatedDataUrlNavigationsEnabled())
+    return true;
+  if (web_frame_->Client())
+    return web_frame_->Client()->AllowContentInitiatedDataUrlNavigations(url);
+  return false;
+}
+
 bool LocalFrameClientImpl::HasWebView() const {
   return web_frame_->ViewImpl();
 }
diff --git a/third_party/WebKit/Source/web/LocalFrameClientImpl.h b/third_party/WebKit/Source/web/LocalFrameClientImpl.h
index b989c5f..43bc8445 100644
--- a/third_party/WebKit/Source/web/LocalFrameClientImpl.h
+++ b/third_party/WebKit/Source/web/LocalFrameClientImpl.h
@@ -169,6 +169,8 @@
   void DidChangeScrollOffset() override;
   void DidUpdateCurrentHistoryItem() override;
 
+  bool AllowContentInitiatedDataUrlNavigations(const KURL&) override;
+
   WebCookieJar* CookieJar() const override;
   void FrameFocused() const override;
   void DidChangeName(const String&) override;
diff --git a/third_party/WebKit/Source/web/WebDevToolsAgentImpl.cpp b/third_party/WebKit/Source/web/WebDevToolsAgentImpl.cpp
index 3609203..b2577a4 100644
--- a/third_party/WebKit/Source/web/WebDevToolsAgentImpl.cpp
+++ b/third_party/WebKit/Source/web/WebDevToolsAgentImpl.cpp
@@ -84,8 +84,7 @@
 #include "public/web/WebSettings.h"
 #include "web/DevToolsEmulator.h"
 #include "web/InspectorEmulationAgent.h"
-#include "web/InspectorOverlay.h"
-#include "web/InspectorRenderingAgent.h"
+#include "web/InspectorOverlayAgent.h"
 #include "web/WebFrameWidgetImpl.h"
 #include "web/WebInputEventConversion.h"
 #include "web/WebLocalFrameImpl.h"
@@ -233,11 +232,9 @@
 WebDevToolsAgentImpl* WebDevToolsAgentImpl::Create(
     WebLocalFrameImpl* frame,
     WebDevToolsAgentClient* client) {
-  InspectorOverlay* overlay = new InspectorOverlay(frame);
-
   if (!IsMainFrame(frame)) {
     WebDevToolsAgentImpl* agent =
-        new WebDevToolsAgentImpl(frame, client, overlay, false);
+        new WebDevToolsAgentImpl(frame, client, false);
     if (frame->FrameWidget())
       agent->LayerTreeViewChanged(
           ToWebFrameWidgetImpl(frame->FrameWidget())->LayerTreeView());
@@ -245,8 +242,7 @@
   }
 
   WebViewImpl* view = frame->ViewImpl();
-  WebDevToolsAgentImpl* agent =
-      new WebDevToolsAgentImpl(frame, client, overlay, true);
+  WebDevToolsAgentImpl* agent = new WebDevToolsAgentImpl(frame, client, true);
   agent->LayerTreeViewChanged(view->LayerTreeView());
   return agent;
 }
@@ -254,7 +250,6 @@
 WebDevToolsAgentImpl::WebDevToolsAgentImpl(
     WebLocalFrameImpl* web_local_frame_impl,
     WebDevToolsAgentClient* client,
-    InspectorOverlay* overlay,
     bool include_view_agents)
     : client_(client),
       web_local_frame_impl_(web_local_frame_impl),
@@ -262,16 +257,15 @@
           web_local_frame_impl_->GetFrame()->InstrumentingAgents()),
       resource_content_loader_(InspectorResourceContentLoader::Create(
           web_local_frame_impl_->GetFrame())),
-      overlay_(overlay),
       inspected_frames_(
           InspectedFrames::Create(web_local_frame_impl_->GetFrame())),
       resource_container_(new InspectorResourceContainer(inspected_frames_)),
-      dom_agent_(nullptr),
       page_agent_(nullptr),
       network_agent_(nullptr),
       layer_tree_agent_(nullptr),
       tracing_agent_(nullptr),
       trace_events_agent_(new InspectorTraceEvents()),
+      overlay_agent_(nullptr),
       include_view_agents_(include_view_agents),
       layer_tree_id_(0) {
   DCHECK(IsMainThread());
@@ -287,15 +281,14 @@
   visitor->Trace(web_local_frame_impl_);
   visitor->Trace(instrumenting_agents_);
   visitor->Trace(resource_content_loader_);
-  visitor->Trace(overlay_);
   visitor->Trace(inspected_frames_);
   visitor->Trace(resource_container_);
-  visitor->Trace(dom_agent_);
   visitor->Trace(page_agent_);
   visitor->Trace(network_agent_);
   visitor->Trace(layer_tree_agent_);
   visitor->Trace(tracing_agent_);
   visitor->Trace(trace_events_agent_);
+  visitor->Trace(overlay_agent_);
   visitor->Trace(session_);
 }
 
@@ -323,8 +316,7 @@
       main_thread_debugger->ContextGroupId(inspected_frames_->Root()), state);
 
   InspectorDOMAgent* dom_agent = new InspectorDOMAgent(
-      isolate, inspected_frames_.Get(), session_->V8Session(), overlay_.Get());
-  dom_agent_ = dom_agent;
+      isolate, inspected_frames_.Get(), session_->V8Session());
   session_->Append(dom_agent);
 
   InspectorLayerTreeAgent* layer_tree_agent =
@@ -338,7 +330,7 @@
   session_->Append(network_agent);
 
   InspectorCSSAgent* css_agent = InspectorCSSAgent::Create(
-      dom_agent_, inspected_frames_.Get(), network_agent_,
+      dom_agent, inspected_frames_.Get(), network_agent_,
       resource_content_loader_.Get(), resource_container_.Get());
   session_->Append(css_agent);
 
@@ -362,8 +354,8 @@
   tracing_agent_ = tracing_agent;
   session_->Append(tracing_agent);
 
-  session_->Append(new InspectorDOMDebuggerAgent(isolate, dom_agent_,
-                                                 session_->V8Session()));
+  session_->Append(
+      new InspectorDOMDebuggerAgent(isolate, dom_agent, session_->V8Session()));
 
   session_->Append(InspectorInputAgent::Create(inspected_frames_.Get()));
 
@@ -380,6 +372,12 @@
   session_->Append(
       new DeviceOrientationInspectorAgent(inspected_frames_.Get()));
 
+  InspectorOverlayAgent* overlay_agent =
+      new InspectorOverlayAgent(web_local_frame_impl_, inspected_frames_.Get(),
+                                session_->V8Session(), dom_agent);
+  overlay_agent_ = overlay_agent;
+  session_->Append(overlay_agent);
+
   tracing_agent_->SetLayerTreeId(layer_tree_id_);
   network_agent_->SetHostId(host_id);
 
@@ -388,33 +386,25 @@
     // during remote->local transition we cannot access mainFrameImpl() yet, so
     // we have to store the frame which will become the main frame later.
     session_->Append(
-        InspectorRenderingAgent::Create(web_local_frame_impl_, overlay_.Get()));
-    session_->Append(
         InspectorEmulationAgent::Create(web_local_frame_impl_, this));
     // TODO(dgozman): migrate each of the following agents to frame once module
     // is ready.
     Page* page = web_local_frame_impl_->ViewImpl()->GetPage();
     session_->Append(InspectorDatabaseAgent::Create(page));
-    session_->Append(new InspectorAccessibilityAgent(page, dom_agent_));
+    session_->Append(new InspectorAccessibilityAgent(page, dom_agent));
     session_->Append(InspectorDOMStorageAgent::Create(page));
     session_->Append(InspectorCacheStorageAgent::Create());
   }
 
-  if (overlay_)
-    overlay_->Init(session_->V8Session(), dom_agent_);
-
   Platform::Current()->CurrentThread()->AddTaskObserver(this);
 }
 
 void WebDevToolsAgentImpl::DestroySession() {
-  if (overlay_)
-    overlay_->Clear();
-
+  overlay_agent_.Clear();
   tracing_agent_.Clear();
   layer_tree_agent_.Clear();
   network_agent_.Clear();
   page_agent_.Clear();
-  dom_agent_.Clear();
 
   session_->Dispose();
   session_.Clear();
@@ -497,13 +487,13 @@
 }
 
 void WebDevToolsAgentImpl::ShowReloadingBlanket() {
-  if (overlay_)
-    overlay_->ShowReloadingBlanket();
+  if (overlay_agent_)
+    overlay_agent_->ShowReloadingBlanket();
 }
 
 void WebDevToolsAgentImpl::HideReloadingBlanket() {
-  if (overlay_)
-    overlay_->HideReloadingBlanket();
+  if (overlay_agent_)
+    overlay_agent_->HideReloadingBlanket();
 }
 
 void WebDevToolsAgentImpl::SetCPUThrottlingRate(double rate) {
@@ -537,7 +527,7 @@
 void WebDevToolsAgentImpl::InspectElementAt(
     int session_id,
     const WebPoint& point_in_root_frame) {
-  if (!dom_agent_ || !session_ || session_->SessionId() != session_id)
+  if (!overlay_agent_ || !session_ || session_->SessionId() != session_id)
     return;
   HitTestRequest::HitTestRequestType hit_type =
       HitTestRequest::kMove | HitTestRequest::kReadOnly |
@@ -557,7 +547,7 @@
   Node* node = result.InnerNode();
   if (!node && web_local_frame_impl_->GetFrame()->GetDocument())
     node = web_local_frame_impl_->GetFrame()->GetDocument()->documentElement();
-  dom_agent_->Inspect(node);
+  overlay_agent_->Inspect(node);
 }
 
 void WebDevToolsAgentImpl::FailedToRequestDevTools() {
@@ -574,19 +564,8 @@
 }
 
 void WebDevToolsAgentImpl::PageLayoutInvalidated(bool resized) {
-  if (overlay_)
-    overlay_->PageLayoutInvalidated(resized);
-}
-
-void WebDevToolsAgentImpl::ConfigureOverlay(bool suspended,
-                                            const String& message) {
-  if (!overlay_)
-    return;
-  overlay_->SetPausedInDebuggerMessage(message);
-  if (suspended)
-    overlay_->Suspend();
-  else
-    overlay_->Resume();
+  if (overlay_agent_)
+    overlay_agent_->PageLayoutInvalidated(resized);
 }
 
 void WebDevToolsAgentImpl::WaitForCreateWindow(LocalFrame* frame) {
@@ -599,10 +578,10 @@
 
 WebString WebDevToolsAgentImpl::EvaluateInWebInspectorOverlay(
     const WebString& script) {
-  if (!overlay_)
+  if (!overlay_agent_)
     return WebString();
 
-  return overlay_->EvaluateInOverlayForTest(script);
+  return overlay_agent_->EvaluateInOverlayForTest(script);
 }
 
 bool WebDevToolsAgentImpl::CacheDisabled() {
diff --git a/third_party/WebKit/Source/web/WebDevToolsAgentImpl.h b/third_party/WebKit/Source/web/WebDevToolsAgentImpl.h
index d37a8e413..a42daf4 100644
--- a/third_party/WebKit/Source/web/WebDevToolsAgentImpl.h
+++ b/third_party/WebKit/Source/web/WebDevToolsAgentImpl.h
@@ -48,7 +48,7 @@
 
 class GraphicsLayer;
 class InspectedFrames;
-class InspectorOverlay;
+class InspectorOverlayAgent;
 class InspectorResourceContainer;
 class InspectorResourceContentLoader;
 class InspectorTraceEvents;
@@ -74,7 +74,7 @@
 
   void WillBeDestroyed();
   WebDevToolsAgentClient* Client() { return client_; }
-  InspectorOverlay* Overlay() const { return overlay_.Get(); }
+  InspectorOverlayAgent* OverlayAgent() const { return overlay_agent_.Get(); }
   void FlushProtocolNotifications();
 
   // Instrumentation from web/ layer.
@@ -105,7 +105,6 @@
  private:
   WebDevToolsAgentImpl(WebLocalFrameImpl*,
                        WebDevToolsAgentClient*,
-                       InspectorOverlay*,
                        bool include_view_agents);
 
   // InspectorTracingAgent::Client implementation.
@@ -119,7 +118,6 @@
 
   // InspectorPageAgent::Client implementation.
   void PageLayoutInvalidated(bool resized) override;
-  void ConfigureOverlay(bool suspended, const String& message) override;
   void WaitForCreateWindow(LocalFrame*) override;
 
   // InspectorSession::Client implementation.
@@ -150,16 +148,15 @@
 
   Member<CoreProbeSink> instrumenting_agents_;
   Member<InspectorResourceContentLoader> resource_content_loader_;
-  Member<InspectorOverlay> overlay_;
   Member<InspectedFrames> inspected_frames_;
   Member<InspectorResourceContainer> resource_container_;
 
-  Member<InspectorDOMAgent> dom_agent_;
   Member<InspectorPageAgent> page_agent_;
   Member<InspectorNetworkAgent> network_agent_;
   Member<InspectorLayerTreeAgent> layer_tree_agent_;
   Member<InspectorTracingAgent> tracing_agent_;
   Member<InspectorTraceEvents> trace_events_agent_;
+  Member<InspectorOverlayAgent> overlay_agent_;
 
   Member<InspectorSession> session_;
   bool include_view_agents_;
diff --git a/third_party/WebKit/Source/web/WebFrameWidgetImpl.cpp b/third_party/WebKit/Source/web/WebFrameWidgetImpl.cpp
index fe97646..90915e7 100644
--- a/third_party/WebKit/Source/web/WebFrameWidgetImpl.cpp
+++ b/third_party/WebKit/Source/web/WebFrameWidgetImpl.cpp
@@ -67,7 +67,7 @@
 #include "web/CompositorMutatorImpl.h"
 #include "web/CompositorWorkerProxyClientImpl.h"
 #include "web/ContextMenuAllowedScope.h"
-#include "web/InspectorOverlay.h"
+#include "web/InspectorOverlayAgent.h"
 #include "web/PageOverlay.h"
 #include "web/WebDevToolsAgentImpl.h"
 #include "web/WebInputEventConversion.h"
@@ -251,7 +251,7 @@
   if (!local_root_)
     return;
 
-  if (InspectorOverlay* overlay = GetInspectorOverlay()) {
+  if (InspectorOverlayAgent* overlay = GetInspectorOverlay()) {
     overlay->UpdateAllLifecyclePhases();
     // TODO(chrishtr): integrate paint into the overlay's lifecycle.
     if (overlay->GetPageOverlay() &&
@@ -363,7 +363,7 @@
   if (!GetPage())
     return WebInputEventResult::kNotHandled;
 
-  if (InspectorOverlay* overlay = GetInspectorOverlay()) {
+  if (InspectorOverlayAgent* overlay = GetInspectorOverlay()) {
     if (overlay->HandleInputEvent(input_event))
       return WebInputEventResult::kHandledSuppressed;
   }
@@ -1181,11 +1181,11 @@
   return result;
 }
 
-InspectorOverlay* WebFrameWidgetImpl::GetInspectorOverlay() {
+InspectorOverlayAgent* WebFrameWidgetImpl::GetInspectorOverlay() {
   if (!local_root_)
     return nullptr;
   if (WebDevToolsAgentImpl* devtools = local_root_->DevToolsAgentImpl())
-    return devtools->Overlay();
+    return devtools->OverlayAgent();
   return nullptr;
 }
 
diff --git a/third_party/WebKit/Source/web/WebFrameWidgetImpl.h b/third_party/WebKit/Source/web/WebFrameWidgetImpl.h
index ac28f742..3bd30608 100644
--- a/third_party/WebKit/Source/web/WebFrameWidgetImpl.h
+++ b/third_party/WebKit/Source/web/WebFrameWidgetImpl.h
@@ -51,7 +51,7 @@
 class CompositorAnimationHost;
 class Frame;
 class Element;
-class InspectorOverlay;
+class InspectorOverlayAgent;
 class LocalFrame;
 class PaintLayerCompositor;
 class UserGestureToken;
@@ -184,7 +184,7 @@
   WebInputEventResult HandleKeyEvent(const WebKeyboardEvent&) override;
   WebInputEventResult HandleCharEvent(const WebKeyboardEvent&) override;
 
-  InspectorOverlay* GetInspectorOverlay();
+  InspectorOverlayAgent* GetInspectorOverlay();
 
   // This method returns the focused frame belonging to this WebWidget, that
   // is, a focused frame with the same local root as the one corresponding
diff --git a/third_party/WebKit/Source/web/WebRemoteFrameImpl.cpp b/third_party/WebKit/Source/web/WebRemoteFrameImpl.cpp
index 7edbe0e..6ce5f226 100644
--- a/third_party/WebKit/Source/web/WebRemoteFrameImpl.cpp
+++ b/third_party/WebKit/Source/web/WebRemoteFrameImpl.cpp
@@ -487,7 +487,7 @@
   if (Parent() && Parent()->IsWebLocalFrame()) {
     WebLocalFrameImpl* parent_frame =
         ToWebLocalFrameImpl(Parent()->ToWebLocalFrame());
-    parent_frame->GetFrame()->Loader().CheckCompleted();
+    parent_frame->GetFrame()->GetDocument()->CheckCompleted();
   }
 }
 
diff --git a/third_party/WebKit/Source/web/WebViewImpl.cpp b/third_party/WebKit/Source/web/WebViewImpl.cpp
index bb2303f4c..aa3ad81 100644
--- a/third_party/WebKit/Source/web/WebViewImpl.cpp
+++ b/third_party/WebKit/Source/web/WebViewImpl.cpp
@@ -162,7 +162,7 @@
 #include "web/DedicatedWorkerMessagingProxyProviderImpl.h"
 #include "web/DevToolsEmulator.h"
 #include "web/FullscreenController.h"
-#include "web/InspectorOverlay.h"
+#include "web/InspectorOverlayAgent.h"
 #include "web/LinkHighlightImpl.h"
 #include "web/PageOverlay.h"
 #include "web/PrerendererClientImpl.h"
@@ -432,9 +432,9 @@
   return main_frame ? main_frame->DevToolsAgentImpl() : nullptr;
 }
 
-InspectorOverlay* WebViewImpl::GetInspectorOverlay() {
+InspectorOverlayAgent* WebViewImpl::GetInspectorOverlay() {
   if (WebDevToolsAgentImpl* devtools = MainFrameDevToolsAgentImpl())
-    return devtools->Overlay();
+    return devtools->OverlayAgent();
   return nullptr;
 }
 
@@ -2026,7 +2026,7 @@
   PageWidgetDelegate::UpdateAllLifecyclePhases(*page_,
                                                *MainFrameImpl()->GetFrame());
 
-  if (InspectorOverlay* overlay = GetInspectorOverlay()) {
+  if (InspectorOverlayAgent* overlay = GetInspectorOverlay()) {
     overlay->UpdateAllLifecyclePhases();
     // TODO(chrishtr): integrate paint into the overlay's lifecycle.
     if (overlay->GetPageOverlay() &&
@@ -2169,7 +2169,7 @@
   if (dev_tools_emulator_->HandleInputEvent(input_event))
     return WebInputEventResult::kHandledSuppressed;
 
-  if (InspectorOverlay* overlay = GetInspectorOverlay()) {
+  if (InspectorOverlayAgent* overlay = GetInspectorOverlay()) {
     if (overlay->HandleInputEvent(input_event))
       return WebInputEventResult::kHandledSuppressed;
   }
@@ -4132,7 +4132,7 @@
 void WebViewImpl::UpdatePageOverlays() {
   if (page_color_overlay_)
     page_color_overlay_->Update();
-  if (InspectorOverlay* overlay = GetInspectorOverlay()) {
+  if (InspectorOverlayAgent* overlay = GetInspectorOverlay()) {
     PageOverlay* inspector_page_overlay = overlay->GetPageOverlay();
     if (inspector_page_overlay)
       inspector_page_overlay->Update();
diff --git a/third_party/WebKit/Source/web/WebViewImpl.h b/third_party/WebKit/Source/web/WebViewImpl.h
index 3473323..61b1a3b 100644
--- a/third_party/WebKit/Source/web/WebViewImpl.h
+++ b/third_party/WebKit/Source/web/WebViewImpl.h
@@ -77,7 +77,7 @@
 class DevToolsEmulator;
 class Frame;
 class FullscreenController;
-class InspectorOverlay;
+class InspectorOverlayAgent;
 class LinkHighlightImpl;
 class PageOverlay;
 class PageScaleConstraintsSet;
@@ -504,7 +504,7 @@
   }
 
  private:
-  InspectorOverlay* GetInspectorOverlay();
+  InspectorOverlayAgent* GetInspectorOverlay();
 
   void SetPageScaleFactorAndLocation(float, const FloatPoint&);
   void PropagateZoomFactorToLocalFrameRoots(Frame*, float);
diff --git a/third_party/WebKit/public/web/WebFrameClient.h b/third_party/WebKit/public/web/WebFrameClient.h
index b32fe082..1792f5b 100644
--- a/third_party/WebKit/public/web/WebFrameClient.h
+++ b/third_party/WebKit/public/web/WebFrameClient.h
@@ -330,6 +330,14 @@
     return WebHistoryItem();
   }
 
+  // Asks the embedder whether the frame is allowed to navigate the main frame
+  // to a data URL.
+  // TODO(crbug.com/713259): Move renderer side checks to
+  //                         RenderFrameImpl::DecidePolicyForNavigation().
+  virtual bool AllowContentInitiatedDataUrlNavigations(const WebURL&) {
+    return false;
+  }
+
   // Navigational notifications ------------------------------------------
 
   // These notifications bracket any loading that occurs in the WebFrame.
diff --git a/third_party/closure_compiler/compile2.py b/third_party/closure_compiler/compile2.py
index 19ac4ebb..db34e50b 100755
--- a/third_party/closure_compiler/compile2.py
+++ b/third_party/closure_compiler/compile2.py
@@ -75,7 +75,7 @@
     """
     print >> sys.stderr, "(ERROR) %s" % msg
 
-  def _run_jar(self, jar, args):
+  def run_jar(self, jar, args):
     """Runs a .jar from the command line with arguments.
 
     Args:
@@ -264,7 +264,7 @@
 
     self._log_debug("Args: %s" % " ".join(args))
 
-    _, stderr = self._run_jar(self._compiler_jar, args)
+    _, stderr = self.run_jar(self._compiler_jar, args)
 
     errors = stderr.strip().split("\n\n")
     maybe_summary = errors.pop()
diff --git a/third_party/closure_compiler/compile_js.gni b/third_party/closure_compiler/compile_js.gni
new file mode 100644
index 0000000..958ac1b
--- /dev/null
+++ b/third_party/closure_compiler/compile_js.gni
@@ -0,0 +1,165 @@
+# Copyright 2017 The Chromium Authors.  All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/closure_compiler/closure_args.gni")
+
+script_path = "//third_party/closure_compiler"
+compiler_path = "$script_path/compiler/compiler.jar"
+
+# Defines a target that creates an ordering for .js files to be used by
+# js_binary to compile.
+#
+# Variables:
+#   sources:
+#     List of Javascript files to include in the library
+#
+#   deps:
+#     List of js_library targets to depend on
+#
+# Example:
+#   js_library("apple_tree") {
+#     sources = ["tree_main.js"]
+#     deps = [
+#       ":branch",
+#       ":trunk",
+#       ":root",
+#     ]
+#   }
+
+template("js_library") {
+  assert(defined(invoker.sources) || defined(invoker.deps),
+         "Need sources or deps in $target_name for js_library")
+  action(target_name) {
+    script = "$script_path/js_library.py"
+    forward_variables_from(invoker,
+                           [
+                             "sources",
+                             "deps",
+                           ])
+    output_file = "$target_gen_dir/$target_name.js_library"
+    outputs = [
+      output_file,
+    ]
+    args = [ "--output" ] + [ rebase_path(output_file, root_build_dir) ]
+    if (defined(sources)) {
+      args += [ "--sources" ] + rebase_path(sources, root_build_dir)
+    }
+    if (defined(deps)) {
+      args += [ "--deps" ]
+      foreach(dep, deps) {
+        # Get the output path for each dep
+        dep_gen_dir = get_label_info(dep, "target_gen_dir")
+        dep_name = get_label_info(dep, "name")
+        dep_output_path = "$dep_gen_dir/$dep_name.js_library"
+        args += [ rebase_path(dep_output_path, root_build_dir) ]
+      }
+    }
+  }
+}
+
+# Defines a target that compiles javascript files using the Closure compiler.
+# This will produce a minified javascript output file. Additional checks and
+# optimizations can be configured using the closure_flags attribute.
+#
+# Variables:
+#   sources:
+#     List of .js files to compile
+#
+#   deps:
+#     List of js_library rules to depend on
+#
+#   outputs:
+#     A file to write the compiled .js to.
+#     Only takes in a single file, but must be placed in a list
+#
+#   bootstrap_file:
+#      A .js files to include before all others
+#
+#   config_files:
+#     A list of .js files to include after the bootstrap_file but before all
+#     others
+#
+#   closure_flags:
+#     A list of custom flags to pass to the Closure compiler.  Do not include
+#     the leading dashes
+#
+#   externs_list:
+#     A list of .js files to pass to the compiler as externs
+#
+# Example:
+#   js_binary("tree") {
+#     sources = ["tree_main.js"]
+#     deps = [":apple_tree"]
+#     outputs = [ "$target_gen_dir/tree.js" ]
+#     bootstrap_file = "bootstrap.js"
+#     config_files = [
+#       "config1.js",
+#       "config2.js",
+#     ]
+#     closure_flags = ["jscomp_error=undefinedVars"]
+#     externs_list = [ "externs.js" ]
+#   }
+
+template("js_binary") {
+  assert(defined(invoker.sources) || defined(invoker.deps),
+         "Need sources or deps in $target_name for js_binary")
+  assert(defined(invoker.outputs), "Need outputs in $target_name for js_binary")
+
+  action(target_name) {
+    script = "$script_path/js_binary.py"
+    forward_variables_from(invoker,
+                           [
+                             "sources",
+                             "deps",
+                             "outputs",
+                             "bootstrap_file",
+                             "config_files",
+                             "closure_flags",
+                             "externs_list",
+                           ])
+    args = [
+      "--compiler",
+      rebase_path(compiler_path, root_build_dir),
+    ]
+    args += [ "--output" ] + rebase_path(outputs, root_build_dir)
+    if (defined(sources)) {
+      args += [ "--sources" ] + rebase_path(sources, root_build_dir)
+    } else {
+      sources = []
+    }
+    if (defined(deps)) {
+      args += [ "--deps" ]
+      foreach(dep, deps) {
+        # Get the output path for each dep
+        dep_gen_dir = get_label_info(dep, "target_gen_dir")
+        dep_name = get_label_info(dep, "name")
+        dep_output_path = "$dep_gen_dir/$dep_name.js_library"
+        args += [ rebase_path(dep_output_path, root_build_dir) ]
+      }
+    }
+    if (defined(bootstrap_file)) {
+      args += [
+        "--bootstrap",
+        rebase_path(bootstrap_file, root_build_dir),
+      ]
+      sources += [ bootstrap_file ]
+    }
+    if (defined(config_files)) {
+      args += [ "--config" ] + rebase_path(config_files, root_build_dir)
+      sources += config_files
+    }
+
+    # |minifying_closure_args| from
+    # //third_party/closure_compiler/closure_args.gni
+    args += [ "--flags" ] + minifying_closure_args
+    if (defined(closure_flags)) {
+      args += closure_flags
+    }
+    if (defined(externs_list)) {
+      args += [ "--externs" ]
+      args += rebase_path(externs_list, root_build_dir)
+      sources += externs_list
+    }
+  }
+}
diff --git a/third_party/closure_compiler/js_binary.py b/third_party/closure_compiler/js_binary.py
new file mode 100644
index 0000000..b79a09f
--- /dev/null
+++ b/third_party/closure_compiler/js_binary.py
@@ -0,0 +1,80 @@
+# Copyright 2017 The Chromium Authors.  All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Used by a js_binary action to compile javascript files.
+
+This script takes in a list of sources and dependencies and compiles them all
+together into a single compiled .js file.  The dependencies are ordered in a
+post-order, left-to-right traversal order.  If multiple instances of the same
+source file are read, only the first is kept. The script can also take in
+optional --flags argument which will add custom flags to the compiler.  Any
+extern files can also be passed in using the --extern flag.
+"""
+
+from argparse import ArgumentParser
+import os
+import compile2
+
+
+def ParseDepList(dep):
+  """Parses a depenency list, returns |sources, deps|."""
+  assert os.path.isfile(dep), (os.path.splitext(dep) +
+                               ' is not a js_library target')
+  with open(dep, 'r') as dep_list:
+    lines = dep_list.read().splitlines()
+  assert 'deps:' in lines, dep + ' is not formated correctly, missing "deps:"'
+  split = lines.index('deps:')
+  return lines[1:split], lines[split+1:]
+
+
+def CrawlDepsTree(deps, sources):
+  """Parses the dependency tree creating a post-order listing of sources."""
+  for dep in deps:
+    new_sources, new_deps = ParseDepList(dep)
+
+    sources = CrawlDepsTree(new_deps, sources)
+    sources += [source for source in new_sources if source not in sources]
+  return sources
+
+
+def main():
+  parser = ArgumentParser()
+  parser.add_argument('-c', '--compiler', required=True,
+                      help='Path to compiler')
+  parser.add_argument('-s', '--sources', nargs='*', default=[],
+                      help='List of js source files')
+  parser.add_argument('-o', '--output', required=True,
+                      help='Compile to output')
+  parser.add_argument('-d', '--deps', nargs='*', default=[],
+                      help='List of js_libarary dependencies')
+  parser.add_argument('-b', '--bootstrap',
+                      help='A file to include before all others')
+  parser.add_argument('-cf', '--config', nargs='*', default=[],
+                      help='A list of files to include after bootstrap and '
+                      'before all others')
+  parser.add_argument('-f', '--flags', nargs='*', default=[],
+                      help='A list of custom flags to pass to the compiler. '
+                      'Do not include leading dashes')
+  parser.add_argument('-e', '--externs', nargs='*', default=[],
+                      help='A list of extern files to pass to the compiler')
+
+  args = parser.parse_args()
+  sources = CrawlDepsTree(args.deps, []) + args.sources
+
+  compiler_args = ['--%s' % flag for flag in args.flags]
+  compiler_args += ['--externs=%s' % e for e in args.externs]
+  compiler_args += [
+      '--js_output_file',
+      args.output,
+      '--js',
+  ]
+  if args.bootstrap:
+    compiler_args += [args.bootstrap]
+  compiler_args += args.config
+  compiler_args += sources
+
+  compile2.Checker().run_jar(args.compiler, compiler_args)
+
+
+if __name__ == '__main__':
+  main()
diff --git a/third_party/closure_compiler/js_library.py b/third_party/closure_compiler/js_library.py
new file mode 100644
index 0000000..6d153d0
--- /dev/null
+++ b/third_party/closure_compiler/js_library.py
@@ -0,0 +1,29 @@
+# Copyright 2017 The Chromium Authors.  All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Generates a file describing the js_library to be used by js_binary action.
+
+This script takes in a list of sources and dependencies as described by a
+js_library action.  It creates a file listing the sources and dependencies
+that can later be used by a js_binary action to compile the javascript.
+"""
+
+from argparse import ArgumentParser
+
+
+def main():
+  parser = ArgumentParser()
+  parser.add_argument('-s', '--sources', nargs='*', default=[],
+                      help='List of js source files')
+  parser.add_argument('-o', '--output', help='Write list to output')
+  parser.add_argument('-d', '--deps', nargs='*', default=[],
+                      help='List of js_library dependencies')
+  args = parser.parse_args()
+
+  with open(args.output, 'w') as out:
+    out.write('sources:\n%s\ndeps:\n%s' % ('\n'.join(args.sources),
+                                           '\n'.join(args.deps)))
+
+
+if __name__ == '__main__':
+  main()
diff --git a/third_party/wayland-protocols/include/protocol/remote-shell-unstable-v1-client-protocol.h b/third_party/wayland-protocols/include/protocol/remote-shell-unstable-v1-client-protocol.h
index 77b8e86..7b3daa9 100644
--- a/third_party/wayland-protocols/include/protocol/remote-shell-unstable-v1-client-protocol.h
+++ b/third_party/wayland-protocols/include/protocol/remote-shell-unstable-v1-client-protocol.h
@@ -1,4 +1,4 @@
-/* Generated by wayland-scanner 1.11.0 */
+/* Generated by wayland-scanner 1.12.90 */
 
 #ifndef REMOTE_SHELL_UNSTABLE_V1_CLIENT_PROTOCOL_H
 #define REMOTE_SHELL_UNSTABLE_V1_CLIENT_PROTOCOL_H
@@ -229,30 +229,6 @@
 };
 #endif /* ZCR_REMOTE_SHELL_V1_LAYOUT_MODE_ENUM */
 
-#ifndef ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_ENUM
-#define ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_ENUM
-/**
- * @ingroup iface_zcr_remote_surface_v1
- * systemui visibility behavior
- *
- * Determine the visibility behavior of the system UI.
- */
-enum zcr_remote_surface_v1_systemui_visibility_state {
-  /**
-   * systemui is visible
-   */
-  ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_VISIBLE = 1,
-  /**
-   * systemui autohides and is not sticky
-   */
-  ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_AUTOHIDE_NON_STICKY = 2,
-  /**
-   * systemui autohides and is sticky
-   */
-  ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_AUTOHIDE_STICKY = 3,
-};
-#endif /* ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_ENUM */
-
 /**
  * @ingroup iface_zcr_remote_shell_v1
  * @struct zcr_remote_shell_v1_listener
@@ -283,15 +259,15 @@
 				      int32_t work_area_inset_right,
 				      int32_t work_area_inset_bottom,
 				      uint32_t layout_mode);
-	/**
-	 * area of remote shell
-	 *
-	 * Defines an area of the remote shell used for layout. Each
-	 * series of "workspace" events must be terminated by a "configure"
-	 * event.
-	 * @since 3
-	 */
-	void (*workspace)(void *data,
+        /**
+         * area of remote shell
+         *
+         * Defines an area of the remote shell used for layout. Each
+         * series of "workspace" events must be terminated by a "configure"
+         * event.
+         * @since 5
+         */
+        void (*workspace)(void *data,
 			  struct zcr_remote_shell_v1 *zcr_remote_shell_v1,
 			  uint32_t id_hi,
 			  uint32_t id_lo,
@@ -305,14 +281,14 @@
 			  int32_t inset_bottom,
 			  int32_t transform,
 			  wl_fixed_t scale_factor);
-	/**
-	 * suggests configuration of remote shell
-	 *
-	 * Suggests a new configuration of the remote shell. Preceded by
-	 * a series of "workspace" events.
-	 * @since 3
-	 */
-	void (*configure)(void *data,
+        /**
+         * suggests configuration of remote shell
+         *
+         * Suggests a new configuration of the remote shell. Preceded by
+         * a series of "workspace" events.
+         * @since 5
+         */
+        void (*configure)(void *data,
 			  struct zcr_remote_shell_v1 *zcr_remote_shell_v1,
 			  uint32_t layout_mode);
 };
@@ -343,11 +319,11 @@
 /**
  * @ingroup iface_zcr_remote_shell_v1
  */
-#define ZCR_REMOTE_SHELL_V1_WORKSPACE_SINCE_VERSION 4
+#define ZCR_REMOTE_SHELL_V1_WORKSPACE_SINCE_VERSION 5
 /**
  * @ingroup iface_zcr_remote_shell_v1
  */
-#define ZCR_REMOTE_SHELL_V1_CONFIGURE_SINCE_VERSION 4
+#define ZCR_REMOTE_SHELL_V1_CONFIGURE_SINCE_VERSION 5
 
 /**
  * @ingroup iface_zcr_remote_shell_v1
@@ -440,6 +416,30 @@
 	return (struct zcr_notification_surface_v1 *) id;
 }
 
+#ifndef ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_ENUM
+#define ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_ENUM
+/**
+ * @ingroup iface_zcr_remote_surface_v1
+ * systemui visibility behavior
+ *
+ * Determine the visibility behavior of the system UI.
+ */
+enum zcr_remote_surface_v1_systemui_visibility_state {
+  /**
+   * system ui is visible
+   */
+  ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_VISIBLE = 1,
+  /**
+   * system ui autohides and is not sticky
+   */
+  ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_AUTOHIDE_NON_STICKY = 2,
+  /**
+   * system ui autohides and is sticky
+   */
+  ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_AUTOHIDE_STICKY = 3,
+};
+#endif /* ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_ENUM */
+
 /**
  * @ingroup iface_zcr_remote_surface_v1
  * @struct zcr_remote_surface_v1_listener
@@ -473,28 +473,28 @@
 	void (*state_type_changed)(void *data,
 				   struct zcr_remote_surface_v1 *zcr_remote_surface_v1,
 				   uint32_t state_type);
-	/**
-	 * suggest a surface change
-	 *
-	 * The configure event asks the client to change surface state.
-	 *
-	 * The client must apply the origin offset to window positions in
-	 * set_window_geometry requests.
-	 *
-	 * The states listed in the event are state_type values, and might
-	 * change due to a client request or an event directly handled by
-	 * the compositor.
-	 *
-	 * Clients should arrange their surface for the new state, and then
-	 * send an ack_configure request with the serial sent in this
-	 * configure event at some point before committing the new surface.
-	 *
-	 * If the client receives multiple configure events before it can
-	 * respond to one, it is free to discard all but the last event it
-	 * received.
-	 * @since 3
-	 */
-	void (*configure)(void *data,
+        /**
+         * suggest a surface change
+         *
+         * The configure event asks the client to change surface state.
+         *
+         * The client must apply the origin offset to window positions in
+         * set_window_geometry requests.
+         *
+         * The states listed in the event are state_type values, and might
+         * change due to a client request or an event directly handled by
+         * the compositor.
+         *
+         * Clients should arrange their surface for the new state, and then
+         * send an ack_configure request with the serial sent in this
+         * configure event at some point before committing the new surface.
+         *
+         * If the client receives multiple configure events before it can
+         * respond to one, it is free to discard all but the last event it
+         * received.
+         * @since 5
+         */
+        void (*configure)(void *data,
 			  struct zcr_remote_surface_v1 *zcr_remote_surface_v1,
 			  int32_t origin_offset_x,
 			  int32_t origin_offset_y,
@@ -532,8 +532,11 @@
 #define ZCR_REMOTE_SURFACE_V1_SET_SYSTEM_MODAL 16
 #define ZCR_REMOTE_SURFACE_V1_UNSET_SYSTEM_MODAL 17
 #define ZCR_REMOTE_SURFACE_V1_SET_RECTANGULAR_SURFACE_SHADOW 18
-#define ZCR_REMOTE_SURFACE_V1_ACK_CONFIGURE 19
-#define ZCR_REMOTE_SURFACE_V1_MOVE 20
+#define ZCR_REMOTE_SURFACE_V1_SET_SYSTEMUI_VISIBILITY 19
+#define ZCR_REMOTE_SURFACE_V1_SET_ALWAYS_ON_TOP 20
+#define ZCR_REMOTE_SURFACE_V1_UNSET_ALWAYS_ON_TOP 21
+#define ZCR_REMOTE_SURFACE_V1_ACK_CONFIGURE 22
+#define ZCR_REMOTE_SURFACE_V1_MOVE 23
 
 /**
  * @ingroup iface_zcr_remote_surface_v1
@@ -546,7 +549,7 @@
 /**
  * @ingroup iface_zcr_remote_surface_v1
  */
-#define ZCR_REMOTE_SURFACE_V1_CONFIGURE_SINCE_VERSION 4
+#define ZCR_REMOTE_SURFACE_V1_CONFIGURE_SINCE_VERSION 5
 
 /**
  * @ingroup iface_zcr_remote_surface_v1
@@ -631,11 +634,19 @@
 /**
  * @ingroup iface_zcr_remote_surface_v1
  */
-#define ZCR_REMOTE_SURFACE_V1_ACK_CONFIGURE_SINCE_VERSION 4
+#define ZCR_REMOTE_SURFACE_V1_SET_ALWAYS_ON_TOP_SINCE_VERSION 4
 /**
  * @ingroup iface_zcr_remote_surface_v1
  */
-#define ZCR_REMOTE_SURFACE_V1_MOVE_SINCE_VERSION 4
+#define ZCR_REMOTE_SURFACE_V1_UNSET_ALWAYS_ON_TOP_SINCE_VERSION 4
+/**
+ * @ingroup iface_zcr_remote_surface_v1
+ */
+#define ZCR_REMOTE_SURFACE_V1_ACK_CONFIGURE_SINCE_VERSION 5
+/**
+ * @ingroup iface_zcr_remote_surface_v1
+ */
+#define ZCR_REMOTE_SURFACE_V1_MOVE_SINCE_VERSION 5
 
 /** @ingroup iface_zcr_remote_surface_v1 */
 static inline void
@@ -972,15 +983,43 @@
 /**
  * @ingroup iface_zcr_remote_surface_v1
  *
- * Requests how the surface will change the system UI visibility when it is made
- * active.
+ * Requests how the surface will change the visibility of the system UI when it
+ * is made active.
+ */
+static inline void zcr_remote_surface_v1_set_systemui_visibility(
+    struct zcr_remote_surface_v1* zcr_remote_surface_v1,
+    uint32_t visibility) {
+  wl_proxy_marshal((struct wl_proxy*)zcr_remote_surface_v1,
+                   ZCR_REMOTE_SURFACE_V1_SET_SYSTEMUI_VISIBILITY, visibility);
+}
+
+/**
+ * @ingroup iface_zcr_remote_surface_v1
  *
-static inline void
-zcr_remote_surface_v1_set_systemui_visibility(struct zcr_remote_surface_v1
-*zcr_remote_surface_v1, uint32_t visibility)
-{
-  wl_proxy_marshal((struct wl_proxy *) zcr_remote_surface_v1,
-       ZCR_REMOTE_SURFACE_V1_SET_SYSTEM_UI_VISIBILITY, visibility);
+ * Request that surface is made to be always on top.
+ *
+ * This is only a request that the window should be always on top.
+ * The compositor may choose to ignore this request.
+ *
+ */
+static inline void zcr_remote_surface_v1_set_always_on_top(
+    struct zcr_remote_surface_v1* zcr_remote_surface_v1) {
+  wl_proxy_marshal((struct wl_proxy*)zcr_remote_surface_v1,
+                   ZCR_REMOTE_SURFACE_V1_SET_ALWAYS_ON_TOP);
+}
+
+/**
+ * @ingroup iface_zcr_remote_surface_v1
+ *
+ * Request that surface is made to be not always on top.
+ *
+ * This is only a request that the window should be not always on top.
+ * The compositor may choose to ignore this request.
+ */
+static inline void zcr_remote_surface_v1_unset_always_on_top(
+    struct zcr_remote_surface_v1* zcr_remote_surface_v1) {
+  wl_proxy_marshal((struct wl_proxy*)zcr_remote_surface_v1,
+                   ZCR_REMOTE_SURFACE_V1_UNSET_ALWAYS_ON_TOP);
 }
 
 /**
diff --git a/third_party/wayland-protocols/include/protocol/remote-shell-unstable-v1-server-protocol.h b/third_party/wayland-protocols/include/protocol/remote-shell-unstable-v1-server-protocol.h
index 1337a055..79a014b 100644
--- a/third_party/wayland-protocols/include/protocol/remote-shell-unstable-v1-server-protocol.h
+++ b/third_party/wayland-protocols/include/protocol/remote-shell-unstable-v1-server-protocol.h
@@ -1,4 +1,4 @@
-/* Generated by wayland-scanner 1.11.0 */
+/* Generated by wayland-scanner 1.12.90 */
 
 #ifndef REMOTE_SHELL_UNSTABLE_V1_SERVER_PROTOCOL_H
 #define REMOTE_SHELL_UNSTABLE_V1_SERVER_PROTOCOL_H
@@ -232,30 +232,6 @@
 };
 #endif /* ZCR_REMOTE_SHELL_V1_LAYOUT_MODE_ENUM */
 
-#ifndef ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_ENUM
-#define ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_ENUM
-/**
- * @ingroup iface_zcr_remote_surface_v1
- * systemui visibility behavior
- *
- * Determine the visibility of the system UI.
- */
-enum zcr_remote_surface_v1_systemui_visibility_state {
-  /**
-   * systemui is visible
-   */
-  ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_VISIBLE = 1,
-  /**
-   * systemui autohides and is not sticky
-   */
-  ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_AUTOHIDE_NON_STICKY = 2,
-  /**
-   * systemui autohides and is sticky
-   */
-  ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_AUTOHIDE_STICKY = 3,
-};
-#endif /* ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_ENUM */
-
 /**
  * @ingroup iface_zcr_remote_shell_v1
  * @struct zcr_remote_shell_v1_interface
@@ -319,11 +295,11 @@
 /**
  * @ingroup iface_zcr_remote_shell_v1
  */
-#define ZCR_REMOTE_SHELL_V1_WORKSPACE_SINCE_VERSION 4
+#define ZCR_REMOTE_SHELL_V1_WORKSPACE_SINCE_VERSION 5
 /**
  * @ingroup iface_zcr_remote_shell_v1
  */
-#define ZCR_REMOTE_SHELL_V1_CONFIGURE_SINCE_VERSION 4
+#define ZCR_REMOTE_SHELL_V1_CONFIGURE_SINCE_VERSION 5
 
 /**
  * @ingroup iface_zcr_remote_shell_v1
@@ -382,6 +358,30 @@
 	wl_resource_post_event(resource_, ZCR_REMOTE_SHELL_V1_CONFIGURE, layout_mode);
 }
 
+#ifndef ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_ENUM
+#define ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_ENUM
+/**
+ * @ingroup iface_zcr_remote_surface_v1
+ * systemui visibility behavior
+ *
+ * Determine the visibility behavior of the system UI.
+ */
+enum zcr_remote_surface_v1_systemui_visibility_state {
+  /**
+   * system ui is visible
+   */
+  ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_VISIBLE = 1,
+  /**
+   * system ui autohides and is not sticky
+   */
+  ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_AUTOHIDE_NON_STICKY = 2,
+  /**
+   * system ui autohides and is sticky
+   */
+  ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_AUTOHIDE_STICKY = 3,
+};
+#endif /* ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_ENUM */
+
 /**
  * @ingroup iface_zcr_remote_surface_v1
  * @struct zcr_remote_surface_v1_interface
@@ -631,19 +631,38 @@
 					       int32_t y,
 					       int32_t width,
 					       int32_t height);
-
         /**
          * requests the system ui visibility behavior for the surface
          *
-         * Requests how the surface will change the system UI visibility when it
-         * is made* active.
-         *
+         * Requests how the surface will change the visibility of the
+         * system UI when it is made active.
          * @since 3
          */
         void (*set_systemui_visibility)(struct wl_client* client,
                                         struct wl_resource* resource,
                                         uint32_t visibility);
-
+        /**
+         * set always on top
+         *
+         * Request that surface is made to be always on top.
+         *
+         * This is only a request that the window should be always on top.
+         * The compositor may choose to ignore this request.
+         * @since 4
+         */
+        void (*set_always_on_top)(struct wl_client* client,
+                                  struct wl_resource* resource);
+        /**
+         * unset always on top
+         *
+         * Request that surface is made to be not always on top.
+         *
+         * This is only a request that the window should be not always on
+         * top. The compositor may choose to ignore this request.
+         * @since 4
+         */
+        void (*unset_always_on_top)(struct wl_client* client,
+                                    struct wl_resource* resource);
         /**
          * ack a configure event
          *
@@ -669,7 +688,7 @@
          * indicates which configure event the client really is responding
          * to.
          * @param serial the serial from the configure event
-         * @since 4
+         * @since 5
          */
         void (*ack_configure)(struct wl_client* client,
                               struct wl_resource* resource,
@@ -688,7 +707,7 @@
          *
          * The compositor may ignore move requests depending on the state
          * of the surface, e.g. fullscreen or maximized.
-         * @since 4
+         * @since 5
          */
         void (*move)(struct wl_client* client, struct wl_resource* resource);
 };
@@ -708,7 +727,7 @@
 /**
  * @ingroup iface_zcr_remote_surface_v1
  */
-#define ZCR_REMOTE_SURFACE_V1_CONFIGURE_SINCE_VERSION 4
+#define ZCR_REMOTE_SURFACE_V1_CONFIGURE_SINCE_VERSION 5
 
 /**
  * @ingroup iface_zcr_remote_surface_v1
@@ -793,11 +812,19 @@
 /**
  * @ingroup iface_zcr_remote_surface_v1
  */
-#define ZCR_REMOTE_SURFACE_V1_ACK_CONFIGURE_SINCE_VERSION 4
+#define ZCR_REMOTE_SURFACE_V1_SET_ALWAYS_ON_TOP_SINCE_VERSION 4
 /**
  * @ingroup iface_zcr_remote_surface_v1
  */
-#define ZCR_REMOTE_SURFACE_V1_MOVE_SINCE_VERSION 4
+#define ZCR_REMOTE_SURFACE_V1_UNSET_ALWAYS_ON_TOP_SINCE_VERSION 4
+/**
+ * @ingroup iface_zcr_remote_surface_v1
+ */
+#define ZCR_REMOTE_SURFACE_V1_ACK_CONFIGURE_SINCE_VERSION 5
+/**
+ * @ingroup iface_zcr_remote_surface_v1
+ */
+#define ZCR_REMOTE_SURFACE_V1_MOVE_SINCE_VERSION 5
 
 /**
  * @ingroup iface_zcr_remote_surface_v1
diff --git a/third_party/wayland-protocols/protocol/remote-shell-protocol.c b/third_party/wayland-protocols/protocol/remote-shell-protocol.c
index d66e1b4..6aa75fa 100644
--- a/third_party/wayland-protocols/protocol/remote-shell-protocol.c
+++ b/third_party/wayland-protocols/protocol/remote-shell-protocol.c
@@ -1,4 +1,4 @@
-/* Generated by wayland-scanner 1.11.0 */
+/* Generated by wayland-scanner 1.12.90 */
 
 /*
  * Copyright 2016 The Chromium Authors.
@@ -63,12 +63,12 @@
 static const struct wl_message zcr_remote_shell_v1_events[] = {
 	{ "activated", "?o?o", types + 18 },
 	{ "configuration_changed", "iiifiiiiu", types + 0 },
-	{ "workspace", "4uuiiiiiiiiif", types + 0 },
-	{ "configure", "4u", types + 0 },
+	{ "workspace", "5uuiiiiiiiiif", types + 0 },
+	{ "configure", "5u", types + 0 },
 };
 
 WL_EXPORT const struct wl_interface zcr_remote_shell_v1_interface = {
-	"zcr_remote_shell_v1", 4,
+	"zcr_remote_shell_v1", 5,
 	3, zcr_remote_shell_v1_requests,
 	4, zcr_remote_shell_v1_events,
 };
@@ -94,19 +94,21 @@
 	{ "unset_system_modal", "", types + 0 },
 	{ "set_rectangular_surface_shadow", "2iiii", types + 0 },
 	{ "set_systemui_visibility", "3u", types + 0 },
-	{ "ack_configure", "4u", types + 0 },
-	{ "move", "4", types + 0 },
+	{ "set_always_on_top", "4", types + 0 },
+	{ "unset_always_on_top", "4", types + 0 },
+	{ "ack_configure", "5u", types + 0 },
+	{ "move", "5", types + 0 },
 };
 
 static const struct wl_message zcr_remote_surface_v1_events[] = {
 	{ "close", "", types + 0 },
 	{ "state_type_changed", "u", types + 0 },
-	{ "configure", "4iiau", types + 0 },
+	{ "configure", "5iiau", types + 0 },
 };
 
 WL_EXPORT const struct wl_interface zcr_remote_surface_v1_interface = {
-	"zcr_remote_surface_v1", 4,
-	22, zcr_remote_surface_v1_requests,
+	"zcr_remote_surface_v1", 5,
+	24, zcr_remote_surface_v1_requests,
 	3, zcr_remote_surface_v1_events,
 };
 
diff --git a/third_party/wayland-protocols/unstable/remote-shell/remote-shell-unstable-v1.xml b/third_party/wayland-protocols/unstable/remote-shell/remote-shell-unstable-v1.xml
index f9f766b2..9542dec 100644
--- a/third_party/wayland-protocols/unstable/remote-shell/remote-shell-unstable-v1.xml
+++ b/third_party/wayland-protocols/unstable/remote-shell/remote-shell-unstable-v1.xml
@@ -38,7 +38,7 @@
     reset.
   </description>
 
-  <interface name="zcr_remote_shell_v1" version="4">
+  <interface name="zcr_remote_shell_v1" version="5">
     <description summary="remote_shell">
       The global interface that allows clients to turn a wl_surface into a
       "real window" which is remotely managed but can be stacked, activated
@@ -140,9 +140,9 @@
       <arg name="layout_mode" type="uint"/>
     </event>
 
-    <!-- Version 4 additions -->
+    <!-- Version 5 additions -->
 
-    <event name="workspace" since="4">
+    <event name="workspace" since="5">
       <description summary="area of remote shell">
 	Defines an area of the remote shell used for layout. Each series of
 	"workspace" events must be terminated by a "configure" event.
@@ -161,7 +161,7 @@
       <arg name="scale_factor" type="fixed"/>
     </event>
 
-    <event name="configure" since="4">
+    <event name="configure" since="5">
       <description summary="suggests configuration of remote shell">
 	Suggests a new configuration of the remote shell. Preceded by a series
 	of "workspace" events.
@@ -170,7 +170,7 @@
     </event>
   </interface>
 
-  <interface name="zcr_remote_surface_v1" version="4">
+  <interface name="zcr_remote_surface_v1" version="5">
     <description summary="A desktop window">
       An interface that may be implemented by a wl_surface, for
       implementations that provide a desktop-style user interface
@@ -448,7 +448,28 @@
 
     <!-- Version 4 additions -->
 
-    <event name="configure" since="4">
+    <request name="set_always_on_top" since="4">
+      <description summary="set always on top">
+	Request that surface is made to be always on top.
+
+	This is only a request that the window should be always on top.
+	The compositor may choose to ignore this request.
+
+      </description>
+    </request>
+
+    <request name="unset_always_on_top" since="4">
+      <description summary="unset always on top">
+	Request that surface is made to be not always on top.
+
+	This is only a request that the window should be not always on top.
+	The compositor may choose to ignore this request.
+      </description>
+    </request>
+
+    <!-- Version 5 additions -->
+
+    <event name="configure" since="5">
       <description summary="suggest a surface change">
 	The configure event asks the client to change surface state.
 
@@ -471,7 +492,7 @@
       <arg name="serial" type="uint"/>
     </event>
 
-    <request name="ack_configure" since="4">
+    <request name="ack_configure" since="5">
       <description summary="ack a configure event">
 	When a configure event is received, if a client commits the
 	surface in response to the configure event, then the client
@@ -496,7 +517,7 @@
       <arg name="serial" type="uint" summary="the serial from the configure event"/>
     </request>
 
-    <request name="move" since="4">
+    <request name="move" since="5">
       <description summary="start an interactive move">
 	Start an interactive, user-driven move of the surface.
 
diff --git a/tools/binary_size/diagnose_apk_bloat.py b/tools/binary_size/diagnose_apk_bloat.py
index 2a7fbf5..06a7ac01 100755
--- a/tools/binary_size/diagnose_apk_bloat.py
+++ b/tools/binary_size/diagnose_apk_bloat.py
@@ -10,8 +10,8 @@
 
 import argparse
 import collections
+from contextlib import contextmanager
 import distutils.spawn
-import itertools
 import json
 import multiprocessing
 import os
@@ -36,9 +36,10 @@
 _global_restore_checkout_func = None
 
 
-def _RestoreFunc(subrepo):
+def _SetRestoreFunc(subrepo):
   branch = _GitCmd(['rev-parse', '--abbrev-ref', 'HEAD'], subrepo)
-  return lambda: _GitCmd(['checkout', branch], subrepo)
+  global _global_restore_checkout_func
+  _global_restore_checkout_func = lambda: _GitCmd(['checkout', branch], subrepo)
 
 
 class BaseDiff(object):
@@ -241,7 +242,7 @@
     self.rev = rev
     self.metadata = _GenerateMetadata([self], build, metadata_path, subrepo)
 
-  def ArchiveBuildResults(self):
+  def ArchiveBuildResults(self, bs_dir):
     """Save build artifacts necessary for diffing."""
     _Print('Saving build results to: {}', self.dir)
     _EnsureDirsExist(self.dir)
@@ -249,7 +250,7 @@
     self._ArchiveFile(build.main_lib_path)
     lib_name_noext = os.path.splitext(os.path.basename(build.main_lib_path))[0]
     size_path = os.path.join(self.dir, lib_name_noext + '.size')
-    supersize_path = os.path.join(_SRC_ROOT, 'tools/binary_size/supersize')
+    supersize_path = os.path.join(bs_dir, 'supersize')
     tool_prefix = _FindToolPrefix(build.output_directory)
     supersize_cmd = [supersize_path, 'archive', size_path, '--elf-file',
                      build.main_lib_path, '--tool-prefix', tool_prefix,
@@ -478,7 +479,7 @@
   sys.exit(1)
 
 
-def _DownloadBuildArtifacts(archive, build, depot_tools_path=None):
+def _DownloadBuildArtifacts(archive, build, bs_dir, depot_tools_path):
   """Download artifacts from arm32 chromium perf builder."""
   if depot_tools_path:
     gsutil_path = os.path.join(depot_tools_path, 'gsutil.py')
@@ -491,12 +492,12 @@
 
   download_dir = tempfile.mkdtemp(dir=_SRC_ROOT)
   try:
-    _DownloadAndArchive(gsutil_path, archive, download_dir, build)
+    _DownloadAndArchive(gsutil_path, archive, download_dir, build, bs_dir)
   finally:
     shutil.rmtree(download_dir)
 
 
-def _DownloadAndArchive(gsutil_path, archive, dl_dir, build):
+def _DownloadAndArchive(gsutil_path, archive, dl_dir, build, bs_dir):
   dl_file = 'full-build-linux_%s.zip' % archive.rev
   dl_url = 'gs://chrome-perf/Android Builder/%s' % dl_file
   dl_dst = os.path.join(dl_dir, dl_file)
@@ -521,7 +522,7 @@
     _ExtractFiles(to_extract, _CLOUD_OUT_DIR, extract_dir, z)
     dl_out = os.path.join(extract_dir, _CLOUD_OUT_DIR)
     build.output_directory, output_directory = dl_out, build.output_directory
-    archive.ArchiveBuildResults()
+    archive.ArchiveBuildResults(bs_dir)
     build.output_directory = output_directory
 
 
@@ -544,10 +545,24 @@
   logfile.write('%s\n' % s)
 
 
+@contextmanager
+def _TmpBinarySizeDir():
+  """Recursively copy files to a temp dir and yield the tmp binary_size dir."""
+  # Needs to be at same level of nesting as the real //tools/binary_size
+  # since supersize uses this to find d3 in //third_party.
+  tmp_dir = tempfile.mkdtemp(dir=_SRC_ROOT)
+  try:
+    bs_dir = os.path.join(tmp_dir, 'binary_size')
+    shutil.copytree(os.path.join(_SRC_ROOT, 'tools', 'binary_size'), bs_dir)
+    yield bs_dir
+  finally:
+    shutil.rmtree(tmp_dir)
+
+
 def main():
   parser = argparse.ArgumentParser(
       description='Find the cause of APK size bloat.')
-  parser.add_argument('--archive-dir',
+  parser.add_argument('--archive-directory',
                       default=_DEFAULT_ARCHIVE_DIR,
                       help='Where results are stored.')
   parser.add_argument('rev',
@@ -618,7 +633,7 @@
 
   subrepo = args.subrepo or _SRC_ROOT
   _EnsureDirectoryClean(subrepo)
-  _global_restore_checkout_func = _RestoreFunc(subrepo)
+  _SetRestoreFunc(subrepo)
   revs = _GenerateRevList(args.rev,
                           args.reference_rev or args.rev + '^',
                           args.all,
@@ -629,29 +644,30 @@
         ResourceSizesDiff(
             build.apk_name, slow_options=args.include_slow_options)
     ]
-  diff_mngr = _DiffArchiveManager(revs, args.archive_dir, diffs, build, subrepo)
+  diff_mngr = _DiffArchiveManager(
+      revs, args.archive_directory, diffs, build, subrepo)
   consecutive_failures = 0
-  for i, archive in enumerate(diff_mngr.IterArchives()):
-    if archive.Exists():
-      _Print('Found matching metadata for {}, skipping build step.',
-             archive.rev)
-    else:
-      if build.IsCloud():
-        _DownloadBuildArtifacts(archive, build,
-                                depot_tools_path=args.depot_tools_path)
+  with _TmpBinarySizeDir() as bs_dir:
+    for i, archive in enumerate(diff_mngr.IterArchives()):
+      if archive.Exists():
+        _Print('Found matching metadata for {}, skipping build step.',
+               archive.rev)
       else:
-        build_success = _SyncAndBuild(archive, build, subrepo)
-        if not build_success:
-          consecutive_failures += 1
-          if consecutive_failures > _ALLOWED_CONSECUTIVE_FAILURES:
-            _Die('{} builds failed in a row, last failure was {}.',
-                 consecutive_failures, archive.rev)
+        if build.IsCloud():
+          _DownloadBuildArtifacts(archive, build, bs_dir, args.depot_tools_path)
         else:
-          archive.ArchiveBuildResults()
-          consecutive_failures = 0
+          build_success = _SyncAndBuild(archive, build, subrepo)
+          if not build_success:
+            consecutive_failures += 1
+            if consecutive_failures > _ALLOWED_CONSECUTIVE_FAILURES:
+              _Die('{} builds failed in a row, last failure was {}.',
+                   consecutive_failures, archive.rev)
+          else:
+            archive.ArchiveBuildResults(bs_dir)
+            consecutive_failures = 0
 
-    if i != 0:
-      diff_mngr.MaybeDiff(i - 1, i)
+      if i != 0:
+        diff_mngr.MaybeDiff(i - 1, i)
 
   _global_restore_checkout_func()
 
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 44d8e14..5c19fcf 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -29288,6 +29288,16 @@
   </summary>
 </histogram>
 
+<histogram name="Memory.Experimental.Gpu.PhysicalFootprint.MacOS" units="MB">
+  <owner>erikchen@chromium.org</owner>
+  <summary>
+    The physical footprint of the GPU process on macOS. Other measurements fail
+    to correctly account for OpenGL memory usage.  This metric also has flaws
+    and is not intended for permanent use.  It's an emergency measure added to
+    help debug https://crbug.com/713854.  Recorded once per UMA ping.
+  </summary>
+</histogram>
+
 <histogram base="true" name="Memory.Experimental.Renderer" units="MB">
 <!-- Name completed by histogram_suffixes name="RendererMemoryAllocator" -->
 
@@ -29371,16 +29381,6 @@
   </summary>
 </histogram>
 
-<histogram name="Memory.Gpu.PhysicalFootprint.MacOS" units="MB">
-  <owner>erikchen@chromium.org</owner>
-  <summary>
-    The physical footprint of the GPU process on macOS. Other measurements fail
-    to correctly account for OpenGL memory usage.  This metric also has flaws
-    and is not intended for permanent use.  It's an emergency measure added to
-    help debug https://crbug.com/713854.  Recorded once per UMA ping.
-  </summary>
-</histogram>
-
 <histogram name="Memory.Graphics" units="MB">
   <owner>hajimehoshi@chromium.org</owner>
   <owner>jamescook@chromium.org</owner>
@@ -33526,6 +33526,9 @@
 </histogram>
 
 <histogram name="Net.CRLRequestFailedTimeMs" units="ms">
+  <obsolete>
+    Deprecated 2017-04-21 as it was Linux/CrOS only.
+  </obsolete>
   <owner>rsleevi@chromium.org</owner>
   <summary>
     When validating an HTTPS certificate we may have to block to fetch one or
@@ -33535,6 +33538,9 @@
 </histogram>
 
 <histogram name="Net.CRLRequestSuccess" enum="BooleanSuccess">
+  <obsolete>
+    Deprecated 2017-04-21 as it was Linux/CrOS only.
+  </obsolete>
   <owner>rsleevi@chromium.org</owner>
   <summary>
     When validating an HTTPS certificate we may have to block to fetch one or
@@ -33543,6 +33549,9 @@
 </histogram>
 
 <histogram name="Net.CRLRequestTimeMs" units="ms">
+  <obsolete>
+    Deprecated 2017-04-21 as it was Linux/CrOS only.
+  </obsolete>
   <owner>rsleevi@chromium.org</owner>
   <summary>
     When validating an HTTPS certificate we may have to block to fetch one or
@@ -35451,6 +35460,9 @@
 </histogram>
 
 <histogram name="Net.OCSPRequestFailedTimeMs" units="ms">
+  <obsolete>
+    Deprecated 2017-04-21 as it was Linux/CrOS only.
+  </obsolete>
   <owner>rsleevi@chromium.org</owner>
   <summary>
     When validating an HTTPS certificate we may have to make one or more HTTP
@@ -35460,6 +35472,9 @@
 </histogram>
 
 <histogram name="Net.OCSPRequestSuccess" enum="BooleanSuccess">
+  <obsolete>
+    Deprecated 2017-04-21 as it was Linux/CrOS only.
+  </obsolete>
   <owner>rsleevi@chromium.org</owner>
   <summary>
     When validating an HTTPS certificate we may have to make one or more HTTP
@@ -35469,6 +35484,9 @@
 </histogram>
 
 <histogram name="Net.OCSPRequestTimeMs" units="ms">
+  <obsolete>
+    Deprecated 2017-04-21 as it was Linux/CrOS only.
+  </obsolete>
   <owner>rsleevi@chromium.org</owner>
   <summary>
     When validating an HTTPS certificate we may have to make one or more HTTP
@@ -116614,6 +116632,7 @@
 <enum name="UnlockType" type="int">
   <int value="0" label="Password"/>
   <int value="1" label="Pin"/>
+  <int value="2" label="Fingerprint"/>
 </enum>
 
 <enum name="UnPackStatus" type="int">
diff --git a/tools/perf/core/desktop_benchmark_avg_times.json b/tools/perf/core/desktop_benchmark_avg_times.json
index 2708531..f80c711 100644
--- a/tools/perf/core/desktop_benchmark_avg_times.json
+++ b/tools/perf/core/desktop_benchmark_avg_times.json
@@ -1 +1,124 @@
-{"battor.tough_video_cases": 1174.7288519289436, "media.tough_video_cases_extra": 275.80860590281554, "dromaeo.jslibstylejquery": 52.8672397108305, "smoothness.image_decoding_cases": 31.54118863676415, "memory.long_running_idle_gmail_tbmv2": 186.15656200161686, "dummy_benchmark.noisy_benchmark_1": 7.4630223302280205, "page_cycler_v2_site_isolation.basic_oopif": 513.9117965158755, "tab_switching.tough_energy_cases": 635.2167451683356, "sunspider": 23.417549015954137, "media.media_cns_cases": 175.98004016036506, "smoothness.tough_webgl_ad_cases": 276.34991042891215, "dromaeo.jslibattrjquery": 46.66566416062415, "blink_perf.shadow_dom": 81.56559749378646, "blink_perf.events": 66.05652102455497, "tab_switching.top_10": 93.51088824942093, "smoothness.tough_image_decode_cases": 66.97034239201318, "blink_perf.dom": 98.51104682236321, "thread_times.tough_compositor_cases": 276.53419018574874, "storage.indexeddb_endure_tracing": 335.31931550860844, "smoothness.tough_texture_upload_cases": 152.9108924680069, "v8.detached_context_age_in_gc": 46.25958715700636, "page_cycler_v2.intl_ja_zh": 2381.6099734405675, "smoothness.key_desktop_move_cases": 46.49936254688951, "smoothness.scrolling_tough_ad_cases": 209.793282920664, "power.trivial_pages": 772.9753481583161, "blink_perf.canvas": 107.17591333586323, "tab_switching.five_blank_pages": 467.2536632216254, "webrtc.peerconnection": 58.52019228742796, "dummy_benchmark.stable_benchmark_1": 8.010384823665145, "smoothness.gpu_rasterization.tough_path_rendering_cases": 71.44854112284807, "smoothness.tough_animation_cases": 1782.6661928799338, "smoothness.tough_webgl_cases": 176.61726460002717, "dromaeo.jslibeventjquery": 64.36679933947536, "media.mse_cases": 40.25715385278066, "thread_times.tough_scrolling_cases": 768.3866442836212, "v8.todomvc-ignition": 135.6247856377669, "power.top_10": 356.0645871018789, "v8.infinite_scroll-ignition_tbmv2": 474.53039245256565, "webrtc.webrtc_smoothness": 115.75982538233983, "octane": 42.71057095260264, "smoothness.tough_ad_cases": 284.8396057484197, "image_decoding.image_decoding_measurement": 75.86326921706976, "service_worker.service_worker_micro_benchmark": 9.664271195403865, "battor.trivial_pages": 1418.5335564192603, "speedometer-ignition": 73.98038034345589, "dromaeo.jslibattrprototype": 37.17864634084307, "dromaeo.domcoretraverse": 35.63122197623565, "v8.todomvc": 126.77664421172369, "page_cycler_v2.basic_oopif": 518.1674624776242, "blink_perf.bindings": 347.9730213748084, "battor.power_cases": 128.98619307615817, "system_health.memory_desktop": 5293.352725115815, "oortonline_tbmv2": 78.57051863027422, "rasterize_and_record_micro.partial_invalidation": 15.75209499442059, "power.steady_state": 596.0703444047408, "dromaeo.jslibtraversejquery": 51.682706894018715, "smoothness.desktop_tough_pinch_zoom_cases": 391.2880765034602, "page_cycler_v2.typical_25": 2253.314608139926, "v8.browsing_desktop": 794.658185299532, "page_cycler_v2.intl_es_fr_pt-BR": 1275.5369048880207, "jitter": 82.6645822449336, "v8.google": 283.4880456560749, "dromaeo.jslibeventprototype": 25.35767838284989, "tracing.tracing_with_debug_overhead": 200.02585370596066, "startup.cold.blank_page": 644.8810393966897, "page_cycler_v2.intl_ar_fa_he": 1062.7986515655075, "speedometer": 67.36228357814252, "blink_perf.svg": 84.42018363990036, "dromaeo.jslibtraverseprototype": 51.90171639007681, "startup.large_profile.warm.blank_page": 163.33904917099898, "v8.browsing_desktop_ignition": 792.3583991321517, "jetstream": 203.0134370060854, "dromaeo.domcoreattr": 40.5585843068417, "power.gpu_rasterization.top_10": 337.4231767654419, "blink_perf.layout": 453.38422619201697, "dromaeo.domcoremodify": 38.194434186605015, "tab_switching.typical_25": 153.73988430574536, "dromaeo.cssqueryjquery": 197.83100520021776, "indexeddb_perf": 31.579424661053114, "tracing.tracing_with_background_memory_infra": 137.11428626960722, "blink_perf.parser": 400.27208882935196, "v8.top_25_smooth": 318.00548734664915, "oortonline": 97.15901645831764, "dromaeo.domcorequery": 56.04869707744487, "system_health.common_desktop": 2090.303033706495, "page_cycler_v2.intl_hi_ru": 1371.516809665312, "rasterize_and_record_micro.top_25": 411.0595565901862, "tab_switching.tough_image_cases": 73.45545798900515, "blink_perf.paint": 51.07198746293505, "smoothness.tough_filters_cases": 121.1643203953281, "oilpan_gc_times.tough_animation_cases": 875.4915979802608, "dromaeo.jslibstyleprototype": 50.96552480985952, "battor.steady_state": 968.0069302916527, "page_cycler_v2.tough_layout_cases": 1047.1903459546722, "smoothness.top_25_smooth": 507.43153773388775, "smoothness.tough_canvas_cases": 695.2274332830044, "webrtc.getusermedia": 18.085213074427166, "blink_style.top_25": 447.7989743783384, "startup.warm.blank_page": 59.4212770304404, "power.top_25": 813.9569391814146, "smoothness.tough_scrolling_cases": 614.7339893544422, "service_worker.service_worker": 74.72189191356301, "page_cycler_v2.top_10_mobile": 671.6237025374458, "memory.long_running_idle_gmail_background_tbmv2": 188.2264252694185, "webrtc.datachannel": 39.26883707946563, "rasterize_and_record_micro.key_mobile_sites": 625.6900956763161, "power.gpu_rasterization.top_25": 834.0499158447439, "blob_storage.blob_storage": 484.45522527596387, "storage.indexeddb_endure": 73.71679610873377, "thread_times.key_hit_test_cases": 37.737602919340134, "smoothness.gpu_rasterization.tough_filters_cases": 121.51945241427018, "page_cycler_v2.intl_ko_th_vi": 1651.235684189815, "smoothness.tough_path_rendering_cases": 64.1464234056832, "startup.large_profile.cold.blank_page": 1280.4007164489933, "dromaeo.jslibmodifyprototype": 53.84123389631788, "kraken": 30.564739039895958, "v8.infinite_scroll_tbmv2": 490.40869227484546, "media.tough_video_cases": 497.14298761720244, "dromaeo.jslibmodifyjquery": 76.02552633584986, "smoothness.gpu_rasterization_and_decoding.image_decoding_cases": 30.11439689749577, "blink_perf.css": 107.84485836140811, "scheduler.tough_scheduling_cases": 548.2793788776964}
\ No newline at end of file
+{
+    "battor.power_cases": 128.98619307615817,
+    "battor.steady_state": 968.0069302916527,
+    "battor.tough_video_cases": 1174.7288519289436,
+    "battor.trivial_pages": 1418.5335564192603,
+    "blink_perf.bindings": 347.9730213748084,
+    "blink_perf.canvas": 107.17591333586323,
+    "blink_perf.css": 107.84485836140811,
+    "blink_perf.dom": 98.51104682236321,
+    "blink_perf.events": 66.05652102455497,
+    "blink_perf.layout": 453.38422619201697,
+    "blink_perf.paint": 51.07198746293505,
+    "blink_perf.parser": 400.27208882935196,
+    "blink_perf.shadow_dom": 81.56559749378646,
+    "blink_perf.svg": 84.42018363990036,
+    "blink_style.top_25": 447.7989743783384,
+    "blob_storage.blob_storage": 484.45522527596387,
+    "dromaeo.cssqueryjquery": 197.83100520021776,
+    "dromaeo.domcoreattr": 40.5585843068417,
+    "dromaeo.domcoremodify": 38.194434186605015,
+    "dromaeo.domcorequery": 56.04869707744487,
+    "dromaeo.domcoretraverse": 35.63122197623565,
+    "dromaeo.jslibattrjquery": 46.66566416062415,
+    "dromaeo.jslibattrprototype": 37.17864634084307,
+    "dromaeo.jslibeventjquery": 64.36679933947536,
+    "dromaeo.jslibeventprototype": 25.35767838284989,
+    "dromaeo.jslibmodifyjquery": 76.02552633584986,
+    "dromaeo.jslibmodifyprototype": 53.84123389631788,
+    "dromaeo.jslibstylejquery": 52.8672397108305,
+    "dromaeo.jslibstyleprototype": 50.96552480985952,
+    "dromaeo.jslibtraversejquery": 51.682706894018715,
+    "dromaeo.jslibtraverseprototype": 51.90171639007681,
+    "dummy_benchmark.noisy_benchmark_1": 7.4630223302280205,
+    "dummy_benchmark.stable_benchmark_1": 8.010384823665145,
+    "image_decoding.image_decoding_measurement": 75.86326921706976,
+    "indexeddb_perf": 31.579424661053114,
+    "jetstream": 203.0134370060854,
+    "jitter": 82.6645822449336,
+    "kraken": 30.564739039895958,
+    "media.media_cns_cases": 175.98004016036506,
+    "media.mse_cases": 40.25715385278066,
+    "media.tough_video_cases": 497.14298761720244,
+    "media.tough_video_cases_extra": 275.80860590281554,
+    "memory.long_running_idle_gmail_background_tbmv2": 188.2264252694185,
+    "memory.long_running_idle_gmail_tbmv2": 186.15656200161686,
+    "octane": 42.71057095260264,
+    "oilpan_gc_times.tough_animation_cases": 875.4915979802608,
+    "oortonline": 97.15901645831764,
+    "oortonline_tbmv2": 78.57051863027422,
+    "page_cycler_v2.basic_oopif": 518.1674624776242,
+    "page_cycler_v2.intl_ar_fa_he": 1062.7986515655075,
+    "page_cycler_v2.intl_es_fr_pt-BR": 1275.5369048880207,
+    "page_cycler_v2.intl_hi_ru": 1371.516809665312,
+    "page_cycler_v2.intl_ja_zh": 2381.6099734405675,
+    "page_cycler_v2.intl_ko_th_vi": 1651.235684189815,
+    "page_cycler_v2.top_10_mobile": 671.6237025374458,
+    "page_cycler_v2.tough_layout_cases": 1047.1903459546722,
+    "page_cycler_v2.typical_25": 2253.314608139926,
+    "page_cycler_v2_site_isolation.basic_oopif": 513.9117965158755,
+    "power.gpu_rasterization.top_10": 337.4231767654419,
+    "power.gpu_rasterization.top_25": 834.0499158447439,
+    "power.steady_state": 596.0703444047408,
+    "power.top_10": 356.0645871018789,
+    "power.top_25": 813.9569391814146,
+    "power.trivial_pages": 772.9753481583161,
+    "rasterize_and_record_micro.key_mobile_sites": 625.6900956763161,
+    "rasterize_and_record_micro.partial_invalidation": 15.75209499442059,
+    "rasterize_and_record_micro.top_25": 411.0595565901862,
+    "scheduler.tough_scheduling_cases": 548.2793788776964,
+    "service_worker.service_worker": 74.72189191356301,
+    "service_worker.service_worker_micro_benchmark": 9.664271195403865,
+    "smoothness.desktop_tough_pinch_zoom_cases": 391.2880765034602,
+    "smoothness.gpu_rasterization.tough_filters_cases": 121.51945241427018,
+    "smoothness.gpu_rasterization.tough_path_rendering_cases": 71.44854112284807,
+    "smoothness.gpu_rasterization_and_decoding.image_decoding_cases": 30.11439689749577,
+    "smoothness.image_decoding_cases": 31.54118863676415,
+    "smoothness.key_desktop_move_cases": 46.49936254688951,
+    "smoothness.scrolling_tough_ad_cases": 209.793282920664,
+    "smoothness.top_25_smooth": 507.43153773388775,
+    "smoothness.tough_ad_cases": 284.8396057484197,
+    "smoothness.tough_animation_cases": 1782.6661928799338,
+    "smoothness.tough_canvas_cases": 695.2274332830044,
+    "smoothness.tough_filters_cases": 121.1643203953281,
+    "smoothness.tough_image_decode_cases": 66.97034239201318,
+    "smoothness.tough_path_rendering_cases": 64.1464234056832,
+    "smoothness.tough_scrolling_cases": 614.7339893544422,
+    "smoothness.tough_texture_upload_cases": 152.9108924680069,
+    "smoothness.tough_webgl_ad_cases": 276.34991042891215,
+    "smoothness.tough_webgl_cases": 176.61726460002717,
+    "speedometer": 67.36228357814252,
+    "speedometer-ignition": 73.98038034345589,
+    "startup.cold.blank_page": 644.8810393966897,
+    "startup.large_profile.cold.blank_page": 1280.4007164489933,
+    "startup.large_profile.warm.blank_page": 163.33904917099898,
+    "startup.warm.blank_page": 59.4212770304404,
+    "storage.indexeddb_endure": 73.71679610873377,
+    "storage.indexeddb_endure_tracing": 335.31931550860844,
+    "sunspider": 23.417549015954137,
+    "system_health.common_desktop": 2090.303033706495,
+    "system_health.memory_desktop": 5293.352725115815,
+    "tab_switching.five_blank_pages": 467.2536632216254,
+    "tab_switching.top_10": 93.51088824942093,
+    "tab_switching.tough_energy_cases": 635.2167451683356,
+    "tab_switching.tough_image_cases": 73.45545798900515,
+    "tab_switching.typical_25": 153.73988430574536,
+    "thread_times.key_hit_test_cases": 37.737602919340134,
+    "thread_times.tough_compositor_cases": 276.53419018574874,
+    "thread_times.tough_scrolling_cases": 768.3866442836212,
+    "tracing.tracing_with_background_memory_infra": 137.11428626960722,
+    "tracing.tracing_with_debug_overhead": 200.02585370596066,
+    "v8.browsing_desktop": 794.658185299532,
+    "v8.browsing_desktop_ignition": 792.3583991321517,
+    "v8.detached_context_age_in_gc": 46.25958715700636,
+    "v8.google": 283.4880456560749,
+    "v8.infinite_scroll-ignition_tbmv2": 474.53039245256565,
+    "v8.infinite_scroll_tbmv2": 490.40869227484546,
+    "v8.todomvc": 126.77664421172369,
+    "v8.todomvc-ignition": 135.6247856377669,
+    "v8.top_25_smooth": 318.00548734664915,
+    "webrtc.datachannel": 39.26883707946563,
+    "webrtc.getusermedia": 18.085213074427166,
+    "webrtc.peerconnection": 58.52019228742796,
+    "webrtc.webrtc_smoothness": 115.75982538233983
+}
diff --git a/ui/app_list/views/app_list_item_view.cc b/ui/app_list/views/app_list_item_view.cc
index a1fdb97..8df2df4 100644
--- a/ui/app_list/views/app_list_item_view.cc
+++ b/ui/app_list/views/app_list_item_view.cc
@@ -289,8 +289,8 @@
 
   if (!apps_grid_view_->IsSelectedView(this))
     apps_grid_view_->ClearAnySelectedView();
-  context_menu_runner_.reset(new views::MenuRunner(
-      menu_model, views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::ASYNC));
+  context_menu_runner_.reset(
+      new views::MenuRunner(menu_model, views::MenuRunner::HAS_MNEMONICS));
   context_menu_runner_->RunMenuAt(GetWidget(), NULL,
                                   gfx::Rect(point, gfx::Size()),
                                   views::MENU_ANCHOR_TOPLEFT, source_type);
diff --git a/ui/app_list/views/contents_view.cc b/ui/app_list/views/contents_view.cc
index dfff612..da1b424 100644
--- a/ui/app_list/views/contents_view.cc
+++ b/ui/app_list/views/contents_view.cc
@@ -29,6 +29,34 @@
 
 namespace app_list {
 
+namespace {
+
+// Container of the search answer view.
+class SearchAnswerContainerView : public views::View {
+ public:
+  explicit SearchAnswerContainerView(views::View* search_results_page_view)
+      : search_results_page_view_(search_results_page_view) {
+    views::BoxLayout* answer_container_layout =
+        new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0);
+    answer_container_layout->set_main_axis_alignment(
+        views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+    SetLayoutManager(answer_container_layout);
+  }
+
+  // views::View overrides:
+  void ChildPreferredSizeChanged(View* child) override {
+    if (visible())
+      search_results_page_view_->Layout();
+  }
+
+ private:
+  views::View* const search_results_page_view_;
+
+  DISALLOW_COPY_AND_ASSIGN(SearchAnswerContainerView);
+};
+
+}  // namespace
+
 ContentsView::ContentsView(AppListMainView* app_list_main_view)
     : model_(nullptr),
       apps_container_view_(nullptr),
@@ -74,13 +102,9 @@
   search_results_page_view_ = new SearchResultPageView();
 
   // Search answer container UI.
-  search_answer_container_view_ = new views::View;
+  search_answer_container_view_ =
+      new SearchAnswerContainerView(search_results_page_view_);
   search_answer_container_view_->SetVisible(false);
-  views::BoxLayout* answer_container_layout =
-      new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0);
-  answer_container_layout->set_main_axis_alignment(
-      views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
-  search_answer_container_view_->SetLayoutManager(answer_container_layout);
   views::View* search_answer_view = view_delegate->GetSearchAnswerWebView();
   if (search_answer_view)
     search_answer_container_view_->AddChildView(search_answer_view);
diff --git a/ui/app_list/views/search_result_tile_item_view.cc b/ui/app_list/views/search_result_tile_item_view.cc
index 579cf1c..c823af3 100644
--- a/ui/app_list/views/search_result_tile_item_view.cc
+++ b/ui/app_list/views/search_result_tile_item_view.cc
@@ -100,8 +100,8 @@
   if (!selected())
     result_container_->ClearSelectedIndex();
 
-  context_menu_runner_.reset(new views::MenuRunner(
-      menu_model, views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::ASYNC));
+  context_menu_runner_.reset(
+      new views::MenuRunner(menu_model, views::MenuRunner::HAS_MNEMONICS));
   context_menu_runner_->RunMenuAt(GetWidget(), nullptr,
                                   gfx::Rect(point, gfx::Size()),
                                   views::MENU_ANCHOR_TOPLEFT, source_type);
diff --git a/ui/app_list/views/search_result_view.cc b/ui/app_list/views/search_result_view.cc
index 9e5f983a..f5856e2 100644
--- a/ui/app_list/views/search_result_view.cc
+++ b/ui/app_list/views/search_result_view.cc
@@ -406,8 +406,8 @@
   if (!menu_model)
     return;
 
-  context_menu_runner_.reset(new views::MenuRunner(
-      menu_model, views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::ASYNC));
+  context_menu_runner_.reset(
+      new views::MenuRunner(menu_model, views::MenuRunner::HAS_MNEMONICS));
   context_menu_runner_->RunMenuAt(GetWidget(), NULL,
                                   gfx::Rect(point, gfx::Size()),
                                   views::MENU_ANCHOR_TOPLEFT, source_type);
diff --git a/ui/base/x/x11_util.cc b/ui/base/x/x11_util.cc
index 037c236..d33e2d9 100644
--- a/ui/base/x/x11_util.cc
+++ b/ui/base/x/x11_util.cc
@@ -1183,6 +1183,12 @@
   return "Unknown";
 }
 
+bool IsCompositingManagerPresent() {
+  static bool is_compositing_manager_present =
+      XGetSelectionOwner(gfx::GetXDisplay(), GetAtom("_NET_WM_CM_S0")) != None;
+  return is_compositing_manager_present;
+}
+
 void SetDefaultX11ErrorHandlers() {
   SetX11ErrorHandlers(NULL, NULL);
 }
diff --git a/ui/base/x/x11_util.h b/ui/base/x/x11_util.h
index 1ca8404..3dfdcb1 100644
--- a/ui/base/x/x11_util.h
+++ b/ui/base/x/x11_util.h
@@ -274,6 +274,9 @@
 // can't determine it, return "Unknown".
 UI_BASE_X_EXPORT std::string GuessWindowManagerName();
 
+// Returns true if a compositing manager is present.
+UI_BASE_X_EXPORT bool IsCompositingManagerPresent();
+
 // Enable the default X error handlers. These will log the error and abort
 // the process if called. Use SetX11ErrorHandlers() from x11_util_internal.h
 // to set your own error handlers.
diff --git a/ui/gfx/BUILD.gn b/ui/gfx/BUILD.gn
index 595f3d0..744fd65 100644
--- a/ui/gfx/BUILD.gn
+++ b/ui/gfx/BUILD.gn
@@ -255,12 +255,13 @@
     "//base:base_static",
     "//base:i18n",
     "//base/third_party/dynamic_annotations",
+    "//device/vr:features",
     "//skia",
     "//third_party/zlib",
   ]
 
   # Text rendering conditions (complicated so separated out).
-  if (use_aura || is_mac) {
+  if (use_aura || is_mac || (is_android && enable_vr)) {
     # Mac doesn't use RenderTextHarfBuzz by default yet.
     sources += [
       "harfbuzz_font_skia.cc",
@@ -282,7 +283,7 @@
     # We don't support RenderText on these platforms.
   }
 
-  if (is_android && use_aura) {
+  if (is_android && enable_vr) {
     sources -= [
       "platform_font_android.cc",
       "text_utils_android.cc",
@@ -306,7 +307,7 @@
 
   # Android.
   if (is_android) {
-    if (use_aura) {
+    if (enable_vr) {
       sources -= [ "canvas_notimplemented.cc" ]
       sources += [ "font_fallback_android.cc" ]
     } else {
diff --git a/ui/gfx/DEPS b/ui/gfx/DEPS
index 6cd8a7d..bef0c572 100644
--- a/ui/gfx/DEPS
+++ b/ui/gfx/DEPS
@@ -1,6 +1,7 @@
 include_rules = [
   "+base",
   "+cc/paint",
+  "+device/vr/features.h",
   "+skia/ext",
   "+third_party/harfbuzz-ng",
   "+third_party/skia",
diff --git a/ui/gfx/font_render_params.h b/ui/gfx/font_render_params.h
index f451cd1..af89e34 100644
--- a/ui/gfx/font_render_params.h
+++ b/ui/gfx/font_render_params.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "build/build_config.h"
+#include "device/vr/features.h"
 #include "third_party/skia/include/core/SkFontLCDConfig.h"
 #include "ui/gfx/font.h"
 #include "ui/gfx/gfx_export.h"
@@ -112,7 +113,8 @@
 GFX_EXPORT void ClearFontRenderParamsCacheForTest();
 #endif
 
-#if defined(OS_CHROMEOS) || defined(OS_LINUX)
+#if defined(OS_CHROMEOS) || defined(OS_LINUX) || \
+    (defined(OS_ANDROID) && BUILDFLAG(ENABLE_VR))
 // Gets the device scale factor to query the FontRenderParams.
 GFX_EXPORT float GetFontRenderParamsDeviceScaleFactor();
 
diff --git a/ui/gfx/font_render_params_android.cc b/ui/gfx/font_render_params_android.cc
index 729de41..8461909 100644
--- a/ui/gfx/font_render_params_android.cc
+++ b/ui/gfx/font_render_params_android.cc
@@ -28,6 +28,10 @@
   return params;
 }
 
+// A device scale factor used to determine if subpixel positioning
+// should be used.
+float device_scale_factor_ = 1.0f;
+
 }  // namespace
 
 FontRenderParams GetFontRenderParams(const FontRenderParamsQuery& query,
@@ -39,4 +43,12 @@
   return params;
 }
 
+float GetFontRenderParamsDeviceScaleFactor() {
+  return device_scale_factor_;
+}
+
+void SetFontRenderParamsDeviceScaleFactor(float device_scale_factor) {
+  device_scale_factor_ = device_scale_factor;
+}
+
 }  // namespace gfx
diff --git a/ui/message_center/views/message_view_context_menu_controller.cc b/ui/message_center/views/message_view_context_menu_controller.cc
index c7a5fed..1e020be2 100644
--- a/ui/message_center/views/message_view_context_menu_controller.cc
+++ b/ui/message_center/views/message_view_context_menu_controller.cc
@@ -38,9 +38,8 @@
       base::Bind(&MessageViewContextMenuController::OnMenuClosed,
                  base::Unretained(this))));
 
-  menu_runner_.reset(new views::MenuRunner(
-      menu_model_adapter_->CreateMenu(),
-      views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::ASYNC));
+  menu_runner_.reset(new views::MenuRunner(menu_model_adapter_->CreateMenu(),
+                                           views::MenuRunner::HAS_MNEMONICS));
 
   menu_runner_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(), NULL,
                           gfx::Rect(point, gfx::Size()),
diff --git a/ui/views/controls/combobox/combobox.cc b/ui/views/controls/combobox/combobox.cc
index 99d0253..f9c1c2d 100644
--- a/ui/views/controls/combobox/combobox.cc
+++ b/ui/views/controls/combobox/combobox.cc
@@ -952,10 +952,10 @@
   // Allow |menu_runner_| to be set by the testing API, but if this method is
   // ever invoked recursively, ensure the old menu is closed.
   if (!menu_runner_ || menu_runner_->IsRunning()) {
-    menu_runner_.reset(new MenuRunner(
-        menu_model_.get(), MenuRunner::COMBOBOX | MenuRunner::ASYNC,
-        base::Bind(&Combobox::OnMenuClosed, base::Unretained(this),
-                   original_state)));
+    menu_runner_.reset(
+        new MenuRunner(menu_model_.get(), MenuRunner::COMBOBOX,
+                       base::Bind(&Combobox::OnMenuClosed,
+                                  base::Unretained(this), original_state)));
   }
   menu_runner_->RunMenuAt(GetWidget(), nullptr, bounds, anchor_position,
                           source_type);
diff --git a/ui/views/controls/label.cc b/ui/views/controls/label.cc
index 4cbe93c2..182d335e 100644
--- a/ui/views/controls/label.cc
+++ b/ui/views/controls/label.cc
@@ -674,12 +674,11 @@
     return;
 
   context_menu_runner_.reset(
-      new MenuRunner(&context_menu_contents_, MenuRunner::HAS_MNEMONICS |
-                                                  MenuRunner::CONTEXT_MENU |
-                                                  MenuRunner::ASYNC));
-  ignore_result(context_menu_runner_->RunMenuAt(
-      GetWidget(), nullptr, gfx::Rect(point, gfx::Size()), MENU_ANCHOR_TOPLEFT,
-      source_type));
+      new MenuRunner(&context_menu_contents_,
+                     MenuRunner::HAS_MNEMONICS | MenuRunner::CONTEXT_MENU));
+  context_menu_runner_->RunMenuAt(GetWidget(), nullptr,
+                                  gfx::Rect(point, gfx::Size()),
+                                  MENU_ANCHOR_TOPLEFT, source_type);
 }
 
 bool Label::GetDecoratedWordAtPoint(const gfx::Point& point,
diff --git a/ui/views/controls/menu/menu_controller.cc b/ui/views/controls/menu/menu_controller.cc
index d581719..9569f67c 100644
--- a/ui/views/controls/menu/menu_controller.cc
+++ b/ui/views/controls/menu/menu_controller.cc
@@ -407,14 +407,13 @@
   return active_instance_;
 }
 
-MenuItemView* MenuController::Run(Widget* parent,
-                                  MenuButton* button,
-                                  MenuItemView* root,
-                                  const gfx::Rect& bounds,
-                                  MenuAnchorPosition position,
-                                  bool context_menu,
-                                  bool is_nested_drag,
-                                  int* result_event_flags) {
+void MenuController::Run(Widget* parent,
+                         MenuButton* button,
+                         MenuItemView* root,
+                         const gfx::Rect& bounds,
+                         MenuAnchorPosition position,
+                         bool context_menu,
+                         bool is_nested_drag) {
   exit_type_ = EXIT_NONE;
   possible_drag_ = false;
   drag_in_progress_ = false;
@@ -482,7 +481,7 @@
       // notification when the drag has finished.
       StartCancelAllTimer();
     }
-    return NULL;
+    return;
   }
 
   if (button)
@@ -491,8 +490,6 @@
   // Make sure Chrome doesn't attempt to shut down while the menu is showing.
   if (ViewsDelegate::GetInstance())
     ViewsDelegate::GetInstance()->AddRef();
-
-  return nullptr;
 }
 
 void MenuController::Cancel(ExitType type) {
diff --git a/ui/views/controls/menu/menu_controller.h b/ui/views/controls/menu/menu_controller.h
index fbd9f28..5b4dd9f 100644
--- a/ui/views/controls/menu/menu_controller.h
+++ b/ui/views/controls/menu/menu_controller.h
@@ -82,14 +82,13 @@
   // Runs the menu at the specified location. If the menu was configured to
   // block, the selected item is returned. If the menu does not block this
   // returns NULL immediately.
-  MenuItemView* Run(Widget* parent,
-                    MenuButton* button,
-                    MenuItemView* root,
-                    const gfx::Rect& bounds,
-                    MenuAnchorPosition position,
-                    bool context_menu,
-                    bool is_nested_drag,
-                    int* event_flags);
+  void Run(Widget* parent,
+           MenuButton* button,
+           MenuItemView* root,
+           const gfx::Rect& bounds,
+           MenuAnchorPosition position,
+           bool context_menu,
+           bool is_nested_drag);
 
   // Whether or not Run blocks.
   bool IsBlockingRun() const { return blocking_run_; }
diff --git a/ui/views/controls/menu/menu_controller_delegate.h b/ui/views/controls/menu/menu_controller_delegate.h
index 85e9206..273ec7b 100644
--- a/ui/views/controls/menu/menu_controller_delegate.h
+++ b/ui/views/controls/menu/menu_controller_delegate.h
@@ -22,10 +22,9 @@
     DONT_NOTIFY_DELEGATE
   };
 
-  // Invoked when MenuController closes a menu and the MenuController was
-  // configured for asynchronous or drop (MenuRunner::ASYNC,
-  // MenuRunner::FOR_DROP). |mouse_event_flags| are the flags set on the
-  // ui::MouseEvent which selected |menu|, otherwise 0.
+  // Invoked when MenuController closes. unless the owner deletes the
+  // MenuController during MenuDelegate::ExecuteCommand. |mouse_event_flags| are
+  // the flags set on the ui::MouseEvent which selected |menu|, otherwise 0.
   virtual void OnMenuClosed(NotifyType type,
                             MenuItemView* menu,
                             int mouse_event_flags) = 0;
diff --git a/ui/views/controls/menu/menu_controller_unittest.cc b/ui/views/controls/menu/menu_controller_unittest.cc
index fe6c857..a23750a 100644
--- a/ui/views/controls/menu/menu_controller_unittest.cc
+++ b/ui/views/controls/menu/menu_controller_unittest.cc
@@ -598,11 +598,8 @@
   event_generator()->PressTouchId(1);
   event_generator()->ReleaseTouchId(0);
 
-  int mouse_event_flags = 0;
-  MenuItemView* run_result = menu_controller()->Run(
-      owner(), nullptr, menu_item(), gfx::Rect(), MENU_ANCHOR_TOPLEFT, false,
-      false, &mouse_event_flags);
-  EXPECT_EQ(run_result, nullptr);
+  menu_controller()->Run(owner(), nullptr, menu_item(), gfx::Rect(),
+                         MENU_ANCHOR_TOPLEFT, false, false);
 
   MenuControllerTest::ReleaseTouchId(1);
   TestAsyncEscapeKey();
@@ -913,11 +910,8 @@
   EXPECT_EQ(button2, GetHotButton());
 
   MenuController* controller = menu_controller();
-  int mouse_event_flags = 0;
-  MenuItemView* run_result =
-      controller->Run(owner(), nullptr, menu_item(), gfx::Rect(),
-                      MENU_ANCHOR_TOPLEFT, false, false, &mouse_event_flags);
-  EXPECT_EQ(run_result, nullptr);
+  controller->Run(owner(), nullptr, menu_item(), gfx::Rect(),
+                  MENU_ANCHOR_TOPLEFT, false, false);
 
   // |button2| should stay in hot-tracked state but menu controller should not
   // track it anymore (preventing resetting hot-tracked state when changing
@@ -941,12 +935,8 @@
 // MenuControllerDelegate when Accept is called.
 TEST_F(MenuControllerTest, AsynchronousAccept) {
   MenuController* controller = menu_controller();
-
-  int mouse_event_flags = 0;
-  MenuItemView* run_result =
-      controller->Run(owner(), nullptr, menu_item(), gfx::Rect(),
-                      MENU_ANCHOR_TOPLEFT, false, false, &mouse_event_flags);
-  EXPECT_EQ(run_result, nullptr);
+  controller->Run(owner(), nullptr, menu_item(), gfx::Rect(),
+                  MENU_ANCHOR_TOPLEFT, false, false);
   TestMenuControllerDelegate* delegate = menu_controller_delegate();
   EXPECT_EQ(0, delegate->on_menu_closed_called());
 
@@ -966,11 +956,8 @@
 TEST_F(MenuControllerTest, AsynchronousCancelAll) {
   MenuController* controller = menu_controller();
 
-  int mouse_event_flags = 0;
-  MenuItemView* run_result =
-      controller->Run(owner(), nullptr, menu_item(), gfx::Rect(),
-                      MENU_ANCHOR_TOPLEFT, false, false, &mouse_event_flags);
-  EXPECT_EQ(run_result, nullptr);
+  controller->Run(owner(), nullptr, menu_item(), gfx::Rect(),
+                  MENU_ANCHOR_TOPLEFT, false, false);
   TestMenuControllerDelegate* delegate = menu_controller_delegate();
   EXPECT_EQ(0, delegate->on_menu_closed_called());
 
@@ -994,11 +981,8 @@
   controller->AddNestedDelegate(nested_delegate.get());
   EXPECT_EQ(nested_delegate.get(), GetCurrentDelegate());
 
-  int mouse_event_flags = 0;
-  MenuItemView* run_result =
-      controller->Run(owner(), nullptr, menu_item(), gfx::Rect(),
-                      MENU_ANCHOR_TOPLEFT, false, false, &mouse_event_flags);
-  EXPECT_EQ(run_result, nullptr);
+  controller->Run(owner(), nullptr, menu_item(), gfx::Rect(),
+                  MENU_ANCHOR_TOPLEFT, false, false);
 
   controller->CancelAll();
   EXPECT_EQ(delegate, GetCurrentDelegate());
@@ -1113,11 +1097,8 @@
 
   // Nested run
   controller->AddNestedDelegate(nested_delegate.get());
-  int mouse_event_flags = 0;
-  MenuItemView* run_result =
-      controller->Run(owner(), nullptr, menu_item(), gfx::Rect(),
-                      MENU_ANCHOR_TOPLEFT, false, false, &mouse_event_flags);
-  EXPECT_EQ(run_result, nullptr);
+  controller->Run(owner(), nullptr, menu_item(), gfx::Rect(),
+                  MENU_ANCHOR_TOPLEFT, false, false);
 
   controller->CancelAll();
   EXPECT_EQ(1, delegate->on_menu_closed_called());
@@ -1137,11 +1118,8 @@
   EXPECT_EQ(nested_delegate.get(), GetCurrentDelegate());
 
   MenuItemView* item = menu_item();
-  int mouse_event_flags = 0;
-  MenuItemView* run_result =
-      controller->Run(owner(), nullptr, item, gfx::Rect(), MENU_ANCHOR_TOPLEFT,
-                      false, false, &mouse_event_flags);
-  EXPECT_EQ(run_result, nullptr);
+  controller->Run(owner(), nullptr, item, gfx::Rect(), MENU_ANCHOR_TOPLEFT,
+                  false, false);
 
   // Show a sub menu to target with a pointer selection. However have the event
   // occur outside of the bounds of the entire menu.
@@ -1204,11 +1182,8 @@
   EXPECT_EQ(nested_delegate.get(), GetCurrentDelegate());
 
   MenuItemView* item = menu_item();
-  int mouse_event_flags = 0;
-  MenuItemView* run_result =
-      controller->Run(owner(), nullptr, item, gfx::Rect(), MENU_ANCHOR_TOPLEFT,
-                      false, false, &mouse_event_flags);
-  EXPECT_EQ(run_result, nullptr);
+  controller->Run(owner(), nullptr, item, gfx::Rect(), MENU_ANCHOR_TOPLEFT,
+                  false, false);
 
   // Show a sub menu to target with a pointer selection. However have the event
   // occur outside of the bounds of the entire menu.
@@ -1242,11 +1217,8 @@
   EXPECT_EQ(nested_delegate.get(), GetCurrentDelegate());
 
   MenuItemView* item = menu_item();
-  int mouse_event_flags = 0;
-  MenuItemView* run_result =
-      controller->Run(owner(), nullptr, item, gfx::Rect(), MENU_ANCHOR_TOPLEFT,
-                      false, false, &mouse_event_flags);
-  EXPECT_EQ(run_result, nullptr);
+  controller->Run(owner(), nullptr, item, gfx::Rect(), MENU_ANCHOR_TOPLEFT,
+                  false, false);
 
   // Show a sub menu to target with a tap event.
   SubmenuView* sub_menu = item->GetSubmenu();
@@ -1271,12 +1243,8 @@
 TEST_F(MenuControllerTest, AsynchronousCancelEvent) {
   ExitMenuRun();
   MenuController* controller = menu_controller();
-
-  int mouse_event_flags = 0;
-  MenuItemView* run_result =
-      controller->Run(owner(), nullptr, menu_item(), gfx::Rect(),
-                      MENU_ANCHOR_TOPLEFT, false, false, &mouse_event_flags);
-  EXPECT_EQ(run_result, nullptr);
+  controller->Run(owner(), nullptr, menu_item(), gfx::Rect(),
+                  MENU_ANCHOR_TOPLEFT, false, false);
   EXPECT_EQ(MenuController::EXIT_NONE, controller->exit_type());
   ui::CancelModeEvent cancel_event;
   event_generator()->Dispatch(&cancel_event);
@@ -1288,11 +1256,8 @@
 TEST_F(MenuControllerTest, RunWithoutWidgetDoesntCrash) {
   ExitMenuRun();
   MenuController* controller = menu_controller();
-  int mouse_event_flags = 0;
-  MenuItemView* run_result =
-      controller->Run(nullptr, nullptr, menu_item(), gfx::Rect(),
-                      MENU_ANCHOR_TOPLEFT, false, false, &mouse_event_flags);
-  EXPECT_EQ(run_result, nullptr);
+  controller->Run(nullptr, nullptr, menu_item(), gfx::Rect(),
+                  MENU_ANCHOR_TOPLEFT, false, false);
 }
 
 // Tests that if a MenuController is destroying during drag/drop, and another
@@ -1337,12 +1302,8 @@
 TEST_F(MenuControllerTest, DestroyedDuringViewsRelease) {
   ExitMenuRun();
   MenuController* controller = menu_controller();
-
-  int mouse_event_flags = 0;
-  MenuItemView* run_result =
-      controller->Run(owner(), nullptr, menu_item(), gfx::Rect(),
-                      MENU_ANCHOR_TOPLEFT, false, false, &mouse_event_flags);
-  EXPECT_EQ(run_result, nullptr);
+  controller->Run(owner(), nullptr, menu_item(), gfx::Rect(),
+                  MENU_ANCHOR_TOPLEFT, false, false);
   TestDestroyedDuringViewsRelease();
 }
 
@@ -1393,11 +1354,9 @@
   std::unique_ptr<TestMenuControllerDelegate> nested_controller_delegate_1 =
       base::MakeUnique<TestMenuControllerDelegate>();
   controller->AddNestedDelegate(nested_controller_delegate_1.get());
-  int mouse_event_flags = 0;
-  MenuItemView* run_result = controller->Run(
-      owner(), nullptr, nested_menu_item_1.get(), gfx::Rect(150, 50, 100, 100),
-      MENU_ANCHOR_TOPLEFT, true, false, &mouse_event_flags);
-  EXPECT_EQ(run_result, nullptr);
+  controller->Run(owner(), nullptr, nested_menu_item_1.get(),
+                  gfx::Rect(150, 50, 100, 100), MENU_ANCHOR_TOPLEFT, true,
+                  false);
 
   SubmenuView* nested_menu_submenu = nested_menu_item_1->GetSubmenu();
   nested_menu_submenu->SetBounds(0, 0, 100, 100);
@@ -1446,10 +1405,9 @@
   std::unique_ptr<TestMenuControllerDelegate> nested_controller_delegate_2 =
       base::MakeUnique<TestMenuControllerDelegate>();
   controller->AddNestedDelegate(nested_controller_delegate_2.get());
-  run_result = controller->Run(
-      owner(), nullptr, nested_menu_item_2.get(), gfx::Rect(150, 50, 100, 100),
-      MENU_ANCHOR_TOPLEFT, true, false, &mouse_event_flags);
-  EXPECT_EQ(run_result, nullptr);
+  controller->Run(owner(), nullptr, nested_menu_item_2.get(),
+                  gfx::Rect(150, 50, 100, 100), MENU_ANCHOR_TOPLEFT, true,
+                  false);
 
   // The escapce key should only close the nested menu. SelectByChar should not
   // crash.
diff --git a/ui/views/controls/menu/menu_delegate.h b/ui/views/controls/menu/menu_delegate.h
index b1eca37..4dea63f 100644
--- a/ui/views/controls/menu/menu_delegate.h
+++ b/ui/views/controls/menu/menu_delegate.h
@@ -195,7 +195,7 @@
 
   // Notification the menu has closed. This will not be called if MenuRunner is
   // deleted during calls to ExecuteCommand().
-  virtual void OnMenuClosed(MenuItemView* menu, MenuRunner::RunResult result) {}
+  virtual void OnMenuClosed(MenuItemView* menu) {}
 
   // If the user drags the mouse outside the bounds of the menu the delegate
   // is queried for a sibling menu to show. If this returns non-null the
diff --git a/ui/views/controls/menu/menu_model_adapter.cc b/ui/views/controls/menu/menu_model_adapter.cc
index bc04dcb9..06a9d3c 100644
--- a/ui/views/controls/menu/menu_model_adapter.cc
+++ b/ui/views/controls/menu/menu_model_adapter.cc
@@ -269,8 +269,7 @@
   NOTREACHED();
 }
 
-void MenuModelAdapter::OnMenuClosed(MenuItemView* menu,
-                                    MenuRunner::RunResult result) {
+void MenuModelAdapter::OnMenuClosed(MenuItemView* menu) {
   if (!on_menu_closed_callback_.is_null())
     on_menu_closed_callback_.Run();
 }
diff --git a/ui/views/controls/menu/menu_model_adapter.h b/ui/views/controls/menu/menu_model_adapter.h
index c9799da..0ac493c 100644
--- a/ui/views/controls/menu/menu_model_adapter.h
+++ b/ui/views/controls/menu/menu_model_adapter.h
@@ -78,7 +78,7 @@
   void SelectionChanged(MenuItemView* menu) override;
   void WillShowMenu(MenuItemView* menu) override;
   void WillHideMenu(MenuItemView* menu) override;
-  void OnMenuClosed(MenuItemView* menu, MenuRunner::RunResult result) override;
+  void OnMenuClosed(MenuItemView* menu) override;
 
  private:
   // Implementation of BuildMenu().
diff --git a/ui/views/controls/menu/menu_runner.cc b/ui/views/controls/menu/menu_runner.cc
index 935df8de..991f76b 100644
--- a/ui/views/controls/menu/menu_runner.cc
+++ b/ui/views/controls/menu/menu_runner.cc
@@ -28,11 +28,11 @@
   impl_->Release();
 }
 
-MenuRunner::RunResult MenuRunner::RunMenuAt(Widget* parent,
-                                            MenuButton* button,
-                                            const gfx::Rect& bounds,
-                                            MenuAnchorPosition anchor,
-                                            ui::MenuSourceType source_type) {
+void MenuRunner::RunMenuAt(Widget* parent,
+                           MenuButton* button,
+                           const gfx::Rect& bounds,
+                           MenuAnchorPosition anchor,
+                           ui::MenuSourceType source_type) {
   // If we are shown on mouse press, we will eat the subsequent mouse down and
   // the parent widget will not be able to reset its state (it might have mouse
   // capture from the mouse down). So we clear its state here.
@@ -40,8 +40,9 @@
     parent->GetRootView()->SetMouseHandler(nullptr);
 
   if (runner_handler_.get()) {
-    return runner_handler_->RunMenuAt(
-        parent, button, bounds, anchor, source_type, run_types_);
+    runner_handler_->RunMenuAt(parent, button, bounds, anchor, source_type,
+                               run_types_);
+    return;
   }
 
   // The parent of the nested menu will have created a DisplayChangeListener, so
@@ -68,7 +69,7 @@
     }
   }
 
-  return impl_->RunMenuAt(parent, button, bounds, anchor, run_types_);
+  impl_->RunMenuAt(parent, button, bounds, anchor, run_types_);
 }
 
 bool MenuRunner::IsRunning() const {
diff --git a/ui/views/controls/menu/menu_runner.h b/ui/views/controls/menu/menu_runner.h
index 63bb56a..1f8c63bd2 100644
--- a/ui/views/controls/menu/menu_runner.h
+++ b/ui/views/controls/menu/menu_runner.h
@@ -45,17 +45,11 @@
 }
 
 // MenuRunner is responsible for showing (running) the menu and additionally
-// owning the MenuItemView. RunMenuAt() runs a nested message loop. It is safe
-// to delete MenuRunner at any point, but MenuRunner internally only deletes the
-// MenuItemView *after* the nested message loop completes. If MenuRunner is
-// deleted while the menu is showing the delegate of the menu is reset. This is
-// done to ensure delegates aren't notified after they may have been deleted.
-//
-// NOTE: while you can delete a MenuRunner at any point, the nested message loop
-// won't return immediately. This means if you delete the object that owns
-// the MenuRunner while the menu is running, your object is effectively still
-// on the stack. A return value of MENU_DELETED indicated this. In most cases
-// if RunMenuAt() returns MENU_DELETED, you should return immediately.
+// owning the MenuItemView. It is safe to delete MenuRunner at any point, but
+// MenuRunner will not notify you of the closure caused by a deletion.
+// If MenuRunner is deleted while the menu is showing the delegate of the menu
+// is reset. This is done to ensure delegates aren't notified after they may
+// have been deleted.
 //
 // Similarly you should avoid creating MenuRunner on the stack. Doing so means
 // MenuRunner may not be immediately destroyed if your object is destroyed,
@@ -91,18 +85,6 @@
     // the caller is responsible for closing the menu upon completion of the
     // drag-and-drop.
     NESTED_DRAG = 1 << 5,
-
-    // Used for showing a menu which does NOT block the caller. Instead the
-    // delegate is notified when the menu closes via OnMenuClosed.
-    ASYNC = 1 << 6,
-  };
-
-  enum RunResult {
-    // Indicates RunMenuAt is returning because the MenuRunner was deleted.
-    MENU_DELETED,
-
-    // Indicates RunMenuAt returned and MenuRunner was not deleted.
-    NORMAL_EXIT
   };
 
   // Creates a new MenuRunner, which may use a native menu if available.
@@ -118,17 +100,14 @@
   MenuRunner(MenuItemView* menu, int32_t run_types);
   ~MenuRunner();
 
-  // Runs the menu. If this returns MENU_DELETED the method is returning
-  // because the MenuRunner was deleted.
-  // Typically callers should NOT do any processing if this returns
-  // MENU_DELETED.
+  // Runs the menu. MenuDelegate::OnMenuClosed will be notified of the results.
   // If |anchor| uses a |BUBBLE_..| type, the bounds will get determined by
   // using |bounds| as the thing to point at in screen coordinates.
-  RunResult RunMenuAt(Widget* parent,
-                      MenuButton* button,
-                      const gfx::Rect& bounds,
-                      MenuAnchorPosition anchor,
-                      ui::MenuSourceType source_type);
+  void RunMenuAt(Widget* parent,
+                 MenuButton* button,
+                 const gfx::Rect& bounds,
+                 MenuAnchorPosition anchor,
+                 ui::MenuSourceType source_type);
 
   // Returns true if we're in a nested message loop running the menu.
   bool IsRunning() const;
diff --git a/ui/views/controls/menu/menu_runner_cocoa_unittest.mm b/ui/views/controls/menu/menu_runner_cocoa_unittest.mm
index e387dc3..839cefc 100644
--- a/ui/views/controls/menu/menu_runner_cocoa_unittest.mm
+++ b/ui/views/controls/menu/menu_runner_cocoa_unittest.mm
@@ -114,7 +114,7 @@
   int IsAsync() const { return GetParam() == MenuType::VIEWS; }
 
   // Runs the menu after registering |callback| as the menu open callback.
-  MenuRunner::RunResult RunMenu(const base::Closure& callback) {
+  void RunMenu(const base::Closure& callback) {
     if (IsAsync()) {
       // Cancelling an async menu under MenuController::OpenMenuImpl() (which
       // invokes WillShowMenu()) will cause a UAF when that same function tries
@@ -126,15 +126,14 @@
                      base::Unretained(this), callback));
     }
 
-    // Always pass ASYNC, even though native menus will be sync.
-    int run_types = MenuRunner::CONTEXT_MENU | MenuRunner::ASYNC;
-    return MaybeRunAsync(runner_->RunMenuAt(parent_, nullptr, gfx::Rect(),
-                                            MENU_ANCHOR_TOPLEFT, run_types));
+    runner_->RunMenuAt(parent_, nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT,
+                       MenuRunner::CONTEXT_MENU);
+    MaybeRunAsync();
   }
 
   // Runs then cancels a combobox menu and captures the frame of the anchoring
   // view.
-  MenuRunner::RunResult RunMenuAt(const gfx::Rect& anchor) {
+  void RunMenuAt(const gfx::Rect& anchor) {
     last_anchor_frame_ = NSZeroRect;
 
     // Should be one child (the compositor layer) before showing, and it should
@@ -149,13 +148,12 @@
     else
       menu_->set_menu_open_callback(callback);
 
-    MenuRunner::RunResult result = MaybeRunAsync(
-        runner_->RunMenuAt(parent_, nullptr, anchor, MENU_ANCHOR_TOPLEFT,
-                           MenuRunner::COMBOBOX | MenuRunner::ASYNC));
+    runner_->RunMenuAt(parent_, nullptr, anchor, MENU_ANCHOR_TOPLEFT,
+                       MenuRunner::COMBOBOX);
+    MaybeRunAsync();
 
     // Ensure the anchor view is removed.
     EXPECT_EQ(1u, [[parent_->GetNativeView() subviews] count]);
-    return result;
   }
 
   void MenuCancelCallback() {
@@ -213,12 +211,10 @@
 
   // Run a nested message loop so that async and sync menus can be tested the
   // same way.
-  MenuRunner::RunResult MaybeRunAsync(MenuRunner::RunResult run_result) {
+  void MaybeRunAsync() {
     if (!IsAsync())
-      return run_result;
+      return;
 
-    // Async menus should always return NORMAL_EXIT.
-    EXPECT_EQ(MenuRunner::NORMAL_EXIT, run_result);
     base::RunLoop run_loop;
     quit_closure_ = run_loop.QuitClosure();
     base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
@@ -227,7 +223,6 @@
 
     // |quit_closure_| should be run by QuitAsyncRunLoop(), not the timeout.
     EXPECT_TRUE(quit_closure_.is_null());
-    return run_result;
   }
 
   void QuitAsyncRunLoop() {
@@ -253,11 +248,10 @@
 TEST_P(MenuRunnerCocoaTest, RunMenuAndCancel) {
   base::TimeTicks min_time = ui::EventTimeForNow();
 
-  MenuRunner::RunResult result = RunMenu(base::Bind(
-      &MenuRunnerCocoaTest::MenuCancelCallback, base::Unretained(this)));
+  RunMenu(base::Bind(&MenuRunnerCocoaTest::MenuCancelCallback,
+                     base::Unretained(this)));
 
   EXPECT_EQ(1, menu_close_count_);
-  EXPECT_EQ(MenuRunner::NORMAL_EXIT, result);
   EXPECT_FALSE(runner_->IsRunning());
 
   if (GetParam() == MenuType::VIEWS) {
@@ -277,30 +271,21 @@
 }
 
 TEST_P(MenuRunnerCocoaTest, RunMenuAndDelete) {
-  MenuRunner::RunResult result = RunMenu(base::Bind(
-      &MenuRunnerCocoaTest::MenuDeleteCallback, base::Unretained(this)));
+  RunMenu(base::Bind(&MenuRunnerCocoaTest::MenuDeleteCallback,
+                     base::Unretained(this)));
   // Note the close callback is NOT invoked for deleted menus.
   EXPECT_EQ(0, menu_close_count_);
-
-  // Async menus always return NORMAL from RunMenuAt().
-  if (GetParam() == MenuType::VIEWS)
-    EXPECT_EQ(MenuRunner::NORMAL_EXIT, result);
-  else
-    EXPECT_EQ(MenuRunner::MENU_DELETED, result);
 }
 
 // Ensure a menu can be safely released immediately after a call to Cancel() in
 // the same run loop iteration.
 TEST_P(MenuRunnerCocoaTest, DestroyAfterCanceling) {
-  MenuRunner::RunResult result =
-      RunMenu(base::Bind(&MenuRunnerCocoaTest::MenuCancelAndDeleteCallback,
-                         base::Unretained(this)));
+  RunMenu(base::Bind(&MenuRunnerCocoaTest::MenuCancelAndDeleteCallback,
+                     base::Unretained(this)));
 
   if (IsAsync()) {
-    EXPECT_EQ(MenuRunner::NORMAL_EXIT, result);
     EXPECT_EQ(1, menu_close_count_);
   } else {
-    EXPECT_EQ(MenuRunner::MENU_DELETED, result);
     // For a synchronous menu, the deletion happens before the cancel can be
     // processed, so the close callback will not be invoked.
     EXPECT_EQ(0, menu_close_count_);
@@ -309,9 +294,8 @@
 
 TEST_P(MenuRunnerCocoaTest, RunMenuTwice) {
   for (int i = 0; i < 2; ++i) {
-    MenuRunner::RunResult result = RunMenu(base::Bind(
-        &MenuRunnerCocoaTest::MenuCancelCallback, base::Unretained(this)));
-    EXPECT_EQ(MenuRunner::NORMAL_EXIT, result);
+    RunMenu(base::Bind(&MenuRunnerCocoaTest::MenuCancelCallback,
+                       base::Unretained(this)));
     EXPECT_FALSE(runner_->IsRunning());
     EXPECT_EQ(i + 1, menu_close_count_);
   }
diff --git a/ui/views/controls/menu/menu_runner_handler.h b/ui/views/controls/menu/menu_runner_handler.h
index c91f6fc..e7499793 100644
--- a/ui/views/controls/menu/menu_runner_handler.h
+++ b/ui/views/controls/menu/menu_runner_handler.h
@@ -17,12 +17,12 @@
 class VIEWS_EXPORT MenuRunnerHandler {
  public:
   virtual ~MenuRunnerHandler() {}
-  virtual MenuRunner::RunResult RunMenuAt(Widget* parent,
-                                          MenuButton* button,
-                                          const gfx::Rect& bounds,
-                                          MenuAnchorPosition anchor,
-                                          ui::MenuSourceType source_type,
-                                          int32_t types) = 0;
+  virtual void RunMenuAt(Widget* parent,
+                         MenuButton* button,
+                         const gfx::Rect& bounds,
+                         MenuAnchorPosition anchor,
+                         ui::MenuSourceType source_type,
+                         int32_t types) = 0;
 };
 
 }  // namespace views
diff --git a/ui/views/controls/menu/menu_runner_impl.cc b/ui/views/controls/menu/menu_runner_impl.cc
index 7dd87fe7..b5f4ba2 100644
--- a/ui/views/controls/menu/menu_runner_impl.cc
+++ b/ui/views/controls/menu/menu_runner_impl.cc
@@ -73,16 +73,16 @@
   delete this;
 }
 
-MenuRunner::RunResult MenuRunnerImpl::RunMenuAt(Widget* parent,
-                                                MenuButton* button,
-                                                const gfx::Rect& bounds,
-                                                MenuAnchorPosition anchor,
-                                                int32_t run_types) {
+void MenuRunnerImpl::RunMenuAt(Widget* parent,
+                               MenuButton* button,
+                               const gfx::Rect& bounds,
+                               MenuAnchorPosition anchor,
+                               int32_t run_types) {
   closing_event_time_ = base::TimeTicks();
   if (running_) {
     // Ignore requests to show the menu while it's already showing. MenuItemView
     // doesn't handle this very well (meaning it crashes).
-    return MenuRunner::NORMAL_EXIT;
+    return;
   }
 
   MenuController* controller = MenuController::GetActiveInstance();
@@ -104,7 +104,7 @@
         // We can't open another menu, otherwise the message loop would become
         // twice nested. This isn't necessarily a problem, but generally isn't
         // expected.
-        return MenuRunner::NORMAL_EXIT;
+        return;
       }
       // Drop menus don't block the message loop, so it's ok to create a new
       // MenuController.
@@ -128,13 +128,9 @@
                        has_mnemonics,
                        !for_drop_ && ShouldShowMnemonics(button));
 
-  int mouse_event_flags = 0;
   controller->Run(parent, button, menu_, bounds, anchor,
                   (run_types & MenuRunner::CONTEXT_MENU) != 0,
-                  (run_types & MenuRunner::NESTED_DRAG) != 0,
-                  &mouse_event_flags);
-  // We finish processing results in OnMenuClosed.
-  return MenuRunner::NORMAL_EXIT;
+                  (run_types & MenuRunner::NESTED_DRAG) != 0);
 }
 
 void MenuRunnerImpl::Cancel() {
@@ -177,10 +173,8 @@
                                            mouse_event_flags);
     }
     // Only notify the delegate if it did not delete this.
-    if (!ref)
-      return;
-    else if (type == NOTIFY_DELEGATE)
-      menu_->GetDelegate()->OnMenuClosed(menu, MenuRunner::NORMAL_EXIT);
+    if (ref && type == NOTIFY_DELEGATE)
+      menu_->GetDelegate()->OnMenuClosed(menu);
   }
 }
 
diff --git a/ui/views/controls/menu/menu_runner_impl.h b/ui/views/controls/menu/menu_runner_impl.h
index 5f4872ca..78fcff7 100644
--- a/ui/views/controls/menu/menu_runner_impl.h
+++ b/ui/views/controls/menu/menu_runner_impl.h
@@ -40,11 +40,11 @@
 
   bool IsRunning() const override;
   void Release() override;
-  MenuRunner::RunResult RunMenuAt(Widget* parent,
-                                  MenuButton* button,
-                                  const gfx::Rect& bounds,
-                                  MenuAnchorPosition anchor,
-                                  int32_t run_types) override;
+  void RunMenuAt(Widget* parent,
+                 MenuButton* button,
+                 const gfx::Rect& bounds,
+                 MenuAnchorPosition anchor,
+                 int32_t run_types) override;
   void Cancel() override;
   base::TimeTicks GetClosingEventTime() const override;
 
@@ -59,12 +59,6 @@
 
   ~MenuRunnerImpl() override;
 
-  // Cleans up after the menu is no longer showing. |result| is the menu that
-  // the user selected, or NULL if nothing was selected.
-  MenuRunner::RunResult MenuDone(NotifyType type,
-                                 MenuItemView* result,
-                                 int mouse_event_flags);
-
   // Returns true if mnemonics should be shown in the menu.
   bool ShouldShowMnemonics(MenuButton* button);
 
diff --git a/ui/views/controls/menu/menu_runner_impl_adapter.cc b/ui/views/controls/menu/menu_runner_impl_adapter.cc
index 31241bfa..08b8b81 100644
--- a/ui/views/controls/menu/menu_runner_impl_adapter.cc
+++ b/ui/views/controls/menu/menu_runner_impl_adapter.cc
@@ -26,13 +26,12 @@
   delete this;
 }
 
-MenuRunner::RunResult MenuRunnerImplAdapter::RunMenuAt(
-    Widget* parent,
-    MenuButton* button,
-    const gfx::Rect& bounds,
-    MenuAnchorPosition anchor,
-    int32_t types) {
-  return impl_->RunMenuAt(parent, button, bounds, anchor, types);
+void MenuRunnerImplAdapter::RunMenuAt(Widget* parent,
+                                      MenuButton* button,
+                                      const gfx::Rect& bounds,
+                                      MenuAnchorPosition anchor,
+                                      int32_t types) {
+  impl_->RunMenuAt(parent, button, bounds, anchor, types);
 }
 
 void MenuRunnerImplAdapter::Cancel() {
diff --git a/ui/views/controls/menu/menu_runner_impl_adapter.h b/ui/views/controls/menu/menu_runner_impl_adapter.h
index 1475b98a..bcb31cf 100644
--- a/ui/views/controls/menu/menu_runner_impl_adapter.h
+++ b/ui/views/controls/menu/menu_runner_impl_adapter.h
@@ -29,11 +29,11 @@
   // MenuRunnerImplInterface:
   bool IsRunning() const override;
   void Release() override;
-  MenuRunner::RunResult RunMenuAt(Widget* parent,
-                                  MenuButton* button,
-                                  const gfx::Rect& bounds,
-                                  MenuAnchorPosition anchor,
-                                  int32_t types) override;
+  void RunMenuAt(Widget* parent,
+                 MenuButton* button,
+                 const gfx::Rect& bounds,
+                 MenuAnchorPosition anchor,
+                 int32_t types) override;
   void Cancel() override;
   base::TimeTicks GetClosingEventTime() const override;
 
diff --git a/ui/views/controls/menu/menu_runner_impl_cocoa.h b/ui/views/controls/menu/menu_runner_impl_cocoa.h
index 4b4184c..6ae5149 100644
--- a/ui/views/controls/menu/menu_runner_impl_cocoa.h
+++ b/ui/views/controls/menu/menu_runner_impl_cocoa.h
@@ -26,11 +26,11 @@
 
   bool IsRunning() const override;
   void Release() override;
-  MenuRunner::RunResult RunMenuAt(Widget* parent,
-                                  MenuButton* button,
-                                  const gfx::Rect& bounds,
-                                  MenuAnchorPosition anchor,
-                                  int32_t run_types) override;
+  void RunMenuAt(Widget* parent,
+                 MenuButton* button,
+                 const gfx::Rect& bounds,
+                 MenuAnchorPosition anchor,
+                 int32_t run_types) override;
   void Cancel() override;
   base::TimeTicks GetClosingEventTime() const override;
 
diff --git a/ui/views/controls/menu/menu_runner_impl_cocoa.mm b/ui/views/controls/menu/menu_runner_impl_cocoa.mm
index f4ac01b..62a758fc 100644
--- a/ui/views/controls/menu/menu_runner_impl_cocoa.mm
+++ b/ui/views/controls/menu/menu_runner_impl_cocoa.mm
@@ -121,11 +121,11 @@
   }
 }
 
-MenuRunner::RunResult MenuRunnerImplCocoa::RunMenuAt(Widget* parent,
-                                                     MenuButton* button,
-                                                     const gfx::Rect& bounds,
-                                                     MenuAnchorPosition anchor,
-                                                     int32_t run_types) {
+void MenuRunnerImplCocoa::RunMenuAt(Widget* parent,
+                                    MenuButton* button,
+                                    const gfx::Rect& bounds,
+                                    MenuAnchorPosition anchor,
+                                    int32_t run_types) {
   DCHECK(run_types & kNativeRunTypes);
   DCHECK(!IsRunning());
   DCHECK(parent);
@@ -155,15 +155,13 @@
 
   if (delete_after_run_) {
     delete this;
-    return MenuRunner::MENU_DELETED;
+    return;
   }
 
   // Don't invoke the callback if Release() was called, since that usually means
   // the owning instance is being destroyed.
   if (!on_menu_closed_callback_.is_null())
     on_menu_closed_callback_.Run();
-
-  return MenuRunner::NORMAL_EXIT;
 }
 
 void MenuRunnerImplCocoa::Cancel() {
diff --git a/ui/views/controls/menu/menu_runner_impl_interface.h b/ui/views/controls/menu/menu_runner_impl_interface.h
index e55f92c..bac3058c 100644
--- a/ui/views/controls/menu/menu_runner_impl_interface.h
+++ b/ui/views/controls/menu/menu_runner_impl_interface.h
@@ -34,12 +34,11 @@
   virtual void Release() = 0;
 
   // Runs the menu. See MenuRunner::RunMenuAt for more details.
-  virtual MenuRunner::RunResult RunMenuAt(Widget* parent,
-                                          MenuButton* button,
-                                          const gfx::Rect& bounds,
-                                          MenuAnchorPosition anchor,
-                                          int32_t run_types)
-      WARN_UNUSED_RESULT = 0;
+  virtual void RunMenuAt(Widget* parent,
+                         MenuButton* button,
+                         const gfx::Rect& bounds,
+                         MenuAnchorPosition anchor,
+                         int32_t run_types) = 0;
 
   // Hides and cancels the menu.
   virtual void Cancel() = 0;
diff --git a/ui/views/controls/menu/menu_runner_unittest.cc b/ui/views/controls/menu/menu_runner_unittest.cc
index 4a96bdb..7c40fe9 100644
--- a/ui/views/controls/menu/menu_runner_unittest.cc
+++ b/ui/views/controls/menu/menu_runner_unittest.cc
@@ -117,14 +117,13 @@
 }
 
 // Tests that MenuRunner is still running after the call to RunMenuAt when
-// initialized with MenuRunner::ASYNC, and that MenuDelegate is notified upon
+// initialized with , and that MenuDelegate is notified upon
 // the closing of the menu.
 TEST_F(MenuRunnerTest, AsynchronousRun) {
-  InitMenuRunner(MenuRunner::ASYNC);
+  InitMenuRunner(0);
   MenuRunner* runner = menu_runner();
-  MenuRunner::RunResult result = runner->RunMenuAt(
-      owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT, ui::MENU_SOURCE_NONE);
-  EXPECT_EQ(MenuRunner::NORMAL_EXIT, result);
+  runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT,
+                    ui::MENU_SOURCE_NONE);
   EXPECT_TRUE(runner->IsRunning());
 
   runner->Cancel();
@@ -132,7 +131,6 @@
   TestMenuDelegate* delegate = menu_delegate();
   EXPECT_EQ(1, delegate->on_menu_closed_called());
   EXPECT_EQ(nullptr, delegate->on_menu_closed_menu());
-  EXPECT_EQ(MenuRunner::NORMAL_EXIT, delegate->on_menu_closed_run_result());
 }
 
 // Tests that when a menu is run asynchronously, key events are handled properly
@@ -143,11 +141,10 @@
   if (IsMus())
     return;
 
-  InitMenuRunner(MenuRunner::ASYNC);
+  InitMenuRunner(0);
   MenuRunner* runner = menu_runner();
-  MenuRunner::RunResult result = runner->RunMenuAt(
-      owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT, ui::MENU_SOURCE_NONE);
-  EXPECT_EQ(MenuRunner::NORMAL_EXIT, result);
+  runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT,
+                    ui::MENU_SOURCE_NONE);
   EXPECT_TRUE(runner->IsRunning());
 
   ui::test::EventGenerator generator(GetContext(), owner()->GetNativeWindow());
@@ -156,7 +153,6 @@
   TestMenuDelegate* delegate = menu_delegate();
   EXPECT_EQ(1, delegate->on_menu_closed_called());
   EXPECT_EQ(nullptr, delegate->on_menu_closed_menu());
-  EXPECT_EQ(MenuRunner::NORMAL_EXIT, delegate->on_menu_closed_run_result());
 }
 
 // Tests that a key press on a US keyboard layout activates the correct menu
@@ -167,11 +163,10 @@
   if (IsMus())
     return;
 
-  InitMenuRunner(MenuRunner::ASYNC);
+  InitMenuRunner(0);
   MenuRunner* runner = menu_runner();
-  MenuRunner::RunResult result = runner->RunMenuAt(
-      owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT, ui::MENU_SOURCE_NONE);
-  EXPECT_EQ(MenuRunner::NORMAL_EXIT, result);
+  runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT,
+                    ui::MENU_SOURCE_NONE);
   EXPECT_TRUE(runner->IsRunning());
 
   ui::test::EventGenerator generator(GetContext(), owner()->GetNativeWindow());
@@ -181,7 +176,6 @@
   EXPECT_EQ(1, delegate->execute_command_id());
   EXPECT_EQ(1, delegate->on_menu_closed_called());
   EXPECT_NE(nullptr, delegate->on_menu_closed_menu());
-  EXPECT_EQ(MenuRunner::NORMAL_EXIT, delegate->on_menu_closed_run_result());
 }
 
 // Tests that a key press on a non-US keyboard layout activates the correct menu
@@ -192,11 +186,10 @@
   if (IsMus())
     return;
 
-  InitMenuRunner(MenuRunner::ASYNC);
+  InitMenuRunner(0);
   MenuRunner* runner = menu_runner();
-  MenuRunner::RunResult result = runner->RunMenuAt(
-      owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT, ui::MENU_SOURCE_NONE);
-  EXPECT_EQ(MenuRunner::NORMAL_EXIT, result);
+  runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT,
+                    ui::MENU_SOURCE_NONE);
   EXPECT_TRUE(runner->IsRunning());
 
   ui::test::EventGenerator generator(GetContext(), owner()->GetNativeWindow());
@@ -207,7 +200,6 @@
   EXPECT_EQ(2, delegate->execute_command_id());
   EXPECT_EQ(1, delegate->on_menu_closed_called());
   EXPECT_NE(nullptr, delegate->on_menu_closed_menu());
-  EXPECT_EQ(MenuRunner::NORMAL_EXIT, delegate->on_menu_closed_run_result());
 }
 
 // Tests that attempting to nest a menu within a drag-and-drop menu does not
@@ -216,24 +208,21 @@
 TEST_F(MenuRunnerTest, NestingDuringDrag) {
   InitMenuRunner(MenuRunner::FOR_DROP);
   MenuRunner* runner = menu_runner();
-  MenuRunner::RunResult result = runner->RunMenuAt(
-      owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT, ui::MENU_SOURCE_NONE);
-  EXPECT_EQ(MenuRunner::NORMAL_EXIT, result);
+  runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT,
+                    ui::MENU_SOURCE_NONE);
   EXPECT_TRUE(runner->IsRunning());
 
   std::unique_ptr<TestMenuDelegate> nested_delegate(new TestMenuDelegate);
   MenuItemView* nested_menu = new MenuItemView(nested_delegate.get());
   std::unique_ptr<MenuRunner> nested_runner(
-      new MenuRunner(nested_menu, MenuRunner::IS_NESTED | MenuRunner::ASYNC));
-  result = nested_runner->RunMenuAt(owner(), nullptr, gfx::Rect(),
-                                    MENU_ANCHOR_TOPLEFT, ui::MENU_SOURCE_NONE);
-  EXPECT_EQ(MenuRunner::NORMAL_EXIT, result);
+      new MenuRunner(nested_menu, MenuRunner::IS_NESTED));
+  nested_runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT,
+                           ui::MENU_SOURCE_NONE);
   EXPECT_TRUE(nested_runner->IsRunning());
   EXPECT_FALSE(runner->IsRunning());
   TestMenuDelegate* delegate = menu_delegate();
   EXPECT_EQ(1, delegate->on_menu_closed_called());
   EXPECT_NE(nullptr, delegate->on_menu_closed_menu());
-  EXPECT_EQ(MenuRunner::NORMAL_EXIT, delegate->on_menu_closed_run_result());
 }
 
 namespace {
@@ -297,7 +286,7 @@
     event_count_view_->SetBounds(0, 0, 300, 300);
     widget_->GetRootView()->AddChildView(event_count_view_);
 
-    InitMenuRunner(MenuRunner::ASYNC);
+    InitMenuRunner(0);
   }
 
   void TearDown() override {
@@ -380,9 +369,7 @@
 TEST_F(MenuRunnerImplTest, NestedMenuRunnersDestroyedOutOfOrder) {
   internal::MenuRunnerImpl* menu_runner =
       new internal::MenuRunnerImpl(menu_item_view());
-  EXPECT_EQ(MenuRunner::NORMAL_EXIT,
-            menu_runner->RunMenuAt(owner(), nullptr, gfx::Rect(),
-                                   MENU_ANCHOR_TOPLEFT, MenuRunner::ASYNC));
+  menu_runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT, 0);
 
   std::unique_ptr<TestMenuDelegate> menu_delegate2(new TestMenuDelegate);
   MenuItemView* menu_item_view2 = new MenuItemView(menu_delegate2.get());
@@ -390,10 +377,8 @@
 
   internal::MenuRunnerImpl* menu_runner2 =
       new internal::MenuRunnerImpl(menu_item_view2);
-  EXPECT_EQ(MenuRunner::NORMAL_EXIT,
-            menu_runner2->RunMenuAt(owner(), nullptr, gfx::Rect(),
-                                    MENU_ANCHOR_TOPLEFT,
-                                    MenuRunner::ASYNC | MenuRunner::IS_NESTED));
+  menu_runner2->RunMenuAt(owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT,
+                          MenuRunner::IS_NESTED);
 
   // Hide the controller so we can test out of order destruction.
   MenuControllerTestApi menu_controller;
@@ -415,9 +400,7 @@
 TEST_F(MenuRunnerImplTest, MenuRunnerDestroyedWithNoActiveController) {
   internal::MenuRunnerImpl* menu_runner =
       new internal::MenuRunnerImpl(menu_item_view());
-  EXPECT_EQ(MenuRunner::NORMAL_EXIT,
-            menu_runner->RunMenuAt(owner(), nullptr, gfx::Rect(),
-                                   MENU_ANCHOR_TOPLEFT, MenuRunner::ASYNC));
+  menu_runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT, 0);
 
   // Hide the menu, and clear its item selection state.
   MenuControllerTestApi menu_controller;
@@ -430,10 +413,8 @@
 
   internal::MenuRunnerImpl* menu_runner2 =
       new internal::MenuRunnerImpl(menu_item_view2);
-  EXPECT_EQ(MenuRunner::NORMAL_EXIT,
-            menu_runner2->RunMenuAt(owner(), nullptr, gfx::Rect(),
-                                    MENU_ANCHOR_TOPLEFT,
-                                    MenuRunner::ASYNC | MenuRunner::FOR_DROP));
+  menu_runner2->RunMenuAt(owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT,
+                          MenuRunner::FOR_DROP);
 
   EXPECT_NE(menu_controller.controller(), MenuController::GetActiveInstance());
   menu_controller.SetShowing(true);
@@ -492,9 +473,7 @@
 TEST_F(MenuRunnerDestructionTest, MenuRunnerDestroyedDuringReleaseRef) {
   internal::MenuRunnerImpl* menu_runner =
       new internal::MenuRunnerImpl(menu_item_view());
-  EXPECT_EQ(MenuRunner::NORMAL_EXIT,
-            menu_runner->RunMenuAt(owner(), nullptr, gfx::Rect(),
-                                   MENU_ANCHOR_TOPLEFT, MenuRunner::ASYNC));
+  menu_runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT, 0);
 
   views_delegate()->set_menu_runner(menu_runner);
 
diff --git a/ui/views/controls/scrollbar/base_scroll_bar.cc b/ui/views/controls/scrollbar/base_scroll_bar.cc
index f343948..1510e95a 100644
--- a/ui/views/controls/scrollbar/base_scroll_bar.cc
+++ b/ui/views/controls/scrollbar/base_scroll_bar.cc
@@ -273,9 +273,8 @@
 
   views::MenuItemView* menu = new views::MenuItemView(this);
   // MenuRunner takes ownership of |menu|.
-  menu_runner_.reset(new MenuRunner(menu, MenuRunner::HAS_MNEMONICS |
-                                              views::MenuRunner::CONTEXT_MENU |
-                                              views::MenuRunner::ASYNC));
+  menu_runner_.reset(new MenuRunner(
+      menu, MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU));
   menu->AppendDelegateMenuItem(ScrollBarContextMenuCommand_ScrollHere);
   menu->AppendSeparator();
   menu->AppendDelegateMenuItem(ScrollBarContextMenuCommand_ScrollStart);
diff --git a/ui/views/controls/textfield/textfield.cc b/ui/views/controls/textfield/textfield.cc
index c94481c..99df371 100644
--- a/ui/views/controls/textfield/textfield.cc
+++ b/ui/views/controls/textfield/textfield.cc
@@ -1051,11 +1051,9 @@
                                        const gfx::Point& point,
                                        ui::MenuSourceType source_type) {
   UpdateContextMenu();
-  ignore_result(context_menu_runner_->RunMenuAt(GetWidget(),
-                                                NULL,
-                                                gfx::Rect(point, gfx::Size()),
-                                                MENU_ANCHOR_TOPLEFT,
-                                                source_type));
+  context_menu_runner_->RunMenuAt(GetWidget(), NULL,
+                                  gfx::Rect(point, gfx::Size()),
+                                  MENU_ANCHOR_TOPLEFT, source_type);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -2038,10 +2036,9 @@
     if (controller_)
       controller_->UpdateContextMenu(context_menu_contents_.get());
   }
-  context_menu_runner_.reset(new MenuRunner(context_menu_contents_.get(),
-                                            MenuRunner::HAS_MNEMONICS |
-                                                MenuRunner::CONTEXT_MENU |
-                                                MenuRunner::ASYNC));
+  context_menu_runner_.reset(
+      new MenuRunner(context_menu_contents_.get(),
+                     MenuRunner::HAS_MNEMONICS | MenuRunner::CONTEXT_MENU));
 }
 
 bool Textfield::ImeEditingAllowed() const {
diff --git a/ui/views/examples/menu_example.cc b/ui/views/examples/menu_example.cc
index c8c8368..df66a42 100644
--- a/ui/views/examples/menu_example.cc
+++ b/ui/views/examples/menu_example.cc
@@ -179,8 +179,7 @@
 void ExampleMenuButton::OnMenuButtonClicked(MenuButton* source,
                                             const gfx::Point& point,
                                             const ui::Event* event) {
-  menu_runner_.reset(new MenuRunner(
-      GetMenuModel(), MenuRunner::HAS_MNEMONICS | MenuRunner::ASYNC));
+  menu_runner_.reset(new MenuRunner(GetMenuModel(), MenuRunner::HAS_MNEMONICS));
 
   menu_runner_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(), this,
                           gfx::Rect(point, gfx::Size()), MENU_ANCHOR_TOPRIGHT,
diff --git a/ui/views/examples/tree_view_example.cc b/ui/views/examples/tree_view_example.cc
index 14d3dce..8e0f6a1 100644
--- a/ui/views/examples/tree_view_example.cc
+++ b/ui/views/examples/tree_view_example.cc
@@ -133,8 +133,7 @@
   context_menu_model_->AddItem(ID_EDIT, ASCIIToUTF16("Edit"));
   context_menu_model_->AddItem(ID_REMOVE, ASCIIToUTF16("Remove"));
   context_menu_model_->AddItem(ID_ADD, ASCIIToUTF16("Add"));
-  context_menu_runner_.reset(
-      new MenuRunner(context_menu_model_.get(), MenuRunner::ASYNC));
+  context_menu_runner_.reset(new MenuRunner(context_menu_model_.get(), 0));
   context_menu_runner_->RunMenuAt(source->GetWidget(), nullptr,
                                   gfx::Rect(point, gfx::Size()),
                                   MENU_ANCHOR_TOPLEFT, source_type);
diff --git a/ui/views/test/combobox_test_api.cc b/ui/views/test/combobox_test_api.cc
index 744651618..0e747ece 100644
--- a/ui/views/test/combobox_test_api.cc
+++ b/ui/views/test/combobox_test_api.cc
@@ -24,14 +24,13 @@
  public:
   explicit TestMenuRunnerHandler(int* show_counter)
       : show_counter_(show_counter) {}
-  MenuRunner::RunResult RunMenuAt(Widget* parent,
-                                  MenuButton* button,
-                                  const gfx::Rect& bounds,
-                                  MenuAnchorPosition anchor,
-                                  ui::MenuSourceType source_type,
-                                  int32_t types) override {
+  void RunMenuAt(Widget* parent,
+                 MenuButton* button,
+                 const gfx::Rect& bounds,
+                 MenuAnchorPosition anchor,
+                 ui::MenuSourceType source_type,
+                 int32_t types) override {
     *show_counter_ += 1;
-    return MenuRunner::NORMAL_EXIT;
   }
 
  private:
diff --git a/ui/views/test/menu_test_utils.cc b/ui/views/test/menu_test_utils.cc
index 6e64f744..b103a01 100644
--- a/ui/views/test/menu_test_utils.cc
+++ b/ui/views/test/menu_test_utils.cc
@@ -15,7 +15,6 @@
     : execute_command_id_(0),
       on_menu_closed_called_count_(0),
       on_menu_closed_menu_(nullptr),
-      on_menu_closed_run_result_(MenuRunner::MENU_DELETED),
       on_perform_drop_called_(false) {}
 
 TestMenuDelegate::~TestMenuDelegate() {}
@@ -33,11 +32,9 @@
   execute_command_id_ = id;
 }
 
-void TestMenuDelegate::OnMenuClosed(MenuItemView* menu,
-                                    MenuRunner::RunResult result) {
+void TestMenuDelegate::OnMenuClosed(MenuItemView* menu) {
   on_menu_closed_called_count_++;
   on_menu_closed_menu_ = menu;
-  on_menu_closed_run_result_ = result;
 }
 
 int TestMenuDelegate::OnPerformDrop(MenuItemView* menu,
diff --git a/ui/views/test/menu_test_utils.h b/ui/views/test/menu_test_utils.h
index e05b2ab..b0a9623 100644
--- a/ui/views/test/menu_test_utils.h
+++ b/ui/views/test/menu_test_utils.h
@@ -27,9 +27,6 @@
   int execute_command_id() const { return execute_command_id_; }
   int on_menu_closed_called() const { return on_menu_closed_called_count_; }
   MenuItemView* on_menu_closed_menu() const { return on_menu_closed_menu_; }
-  MenuRunner::RunResult on_menu_closed_run_result() const {
-    return on_menu_closed_run_result_;
-  }
   bool on_perform_drop_called() { return on_perform_drop_called_; }
   int will_hide_menu_count() { return will_hide_menu_count_; }
   MenuItemView* will_hide_menu() { return will_hide_menu_; }
@@ -40,7 +37,7 @@
                        const gfx::Point& p,
                        ui::MenuSourceType source_type) override;
   void ExecuteCommand(int id) override;
-  void OnMenuClosed(MenuItemView* menu, MenuRunner::RunResult result) override;
+  void OnMenuClosed(MenuItemView* menu) override;
   int OnPerformDrop(MenuItemView* menu,
                     DropPosition position,
                     const ui::DropTargetEvent& event) override;
@@ -61,9 +58,8 @@
   // The number of times OnMenuClosed was called.
   int on_menu_closed_called_count_;
 
-  // The values of the last call to OnMenuClosed.
+  // The value of the last call to OnMenuClosed.
   MenuItemView* on_menu_closed_menu_;
-  MenuRunner::RunResult on_menu_closed_run_result_;
 
   // The number of times WillHideMenu was called.
   int will_hide_menu_count_ = 0;
diff --git a/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.cc b/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.cc
index 0c829ba..1a8a4aa 100644
--- a/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.cc
+++ b/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.cc
@@ -1319,7 +1319,10 @@
     const gfx::ImageSkia& image) {
   Widget* widget = new Widget;
   Widget::InitParams params(Widget::InitParams::TYPE_DRAG);
-  params.opacity = Widget::InitParams::TRANSLUCENT_WINDOW;
+  if (ui::IsCompositingManagerPresent())
+    params.opacity = Widget::InitParams::TRANSLUCENT_WINDOW;
+  else
+    params.opacity = Widget::InitParams::OPAQUE_WINDOW;
   params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
   params.accept_events = false;
 
@@ -1329,7 +1332,8 @@
   widget->set_focus_on_creation(false);
   widget->set_frame_type(Widget::FRAME_TYPE_FORCE_NATIVE);
   widget->Init(params);
-  widget->SetOpacity(kDragWidgetOpacity);
+  if (params.opacity == Widget::InitParams::TRANSLUCENT_WINDOW)
+    widget->SetOpacity(kDragWidgetOpacity);
   widget->GetNativeWindow()->SetName("DragWindow");
 
   drag_image_size_ = image.size();